Okular

document.cpp
1/*
2 SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
3 SPDX-FileCopyrightText: 2004-2008 Albert Astals Cid <aacid@kde.org>
4
5 Work sponsored by the LiMux project of the city of Munich:
6 SPDX-FileCopyrightText: 2017, 2018 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include "document.h"
12#include "document_p.h"
13#include "documentcommands_p.h"
14
15#include <limits.h>
16#include <memory>
17#ifdef Q_OS_WIN
18#define _WIN32_WINNT 0x0500
19#include <windows.h>
20#elif defined(Q_OS_FREEBSD)
21// clang-format off
22// FreeBSD really wants this include order
23#include <sys/types.h>
24#include <sys/sysctl.h>
25// clang-format on
26#include <vm/vm_param.h>
27#endif
28
29// qt/kde/system includes
30#include <QApplication>
31#include <QDesktopServices>
32#include <QDir>
33#include <QFile>
34#include <QFileInfo>
35#include <QLabel>
36#include <QMap>
37#include <QMimeDatabase>
38#include <QPageSize>
39#include <QPrintDialog>
40#include <QRegularExpression>
41#include <QScreen>
42#include <QStack>
43#include <QStandardPaths>
44#include <QTemporaryFile>
45#include <QTextStream>
46#include <QTimer>
47#include <QUndoCommand>
48#include <QWindow>
49#include <QtAlgorithms>
50
51#include <KApplicationTrader>
52#include <KAuthorized>
53#include <KConfigDialog>
54#include <KFormat>
55#include <KIO/Global>
56#include <KIO/JobUiDelegate>
57#include <KIO/JobUiDelegateFactory>
58#include <KIO/OpenUrlJob>
59#include <KLocalizedString>
60#include <KMacroExpander>
61#include <KPluginMetaData>
62#include <KProcess>
63#include <KShell>
64#include <kio_version.h>
65#include <kzip.h>
66
67// local includes
68#include "action.h"
69#include "annotations.h"
70#include "annotations_p.h"
71#include "audioplayer.h"
72#include "bookmarkmanager.h"
73#include "chooseenginedialog_p.h"
74#include "debug_p.h"
75#include "form.h"
76#include "generator_p.h"
77#include "interfaces/configinterface.h"
78#include "interfaces/guiinterface.h"
79#include "interfaces/printinterface.h"
80#include "interfaces/saveinterface.h"
81#include "misc.h"
82#include "observer.h"
83#include "page.h"
84#include "page_p.h"
85#include "pagecontroller_p.h"
86#include "script/event_p.h"
87#include "scripter.h"
88#include "settings_core.h"
89#include "sourcereference.h"
90#include "sourcereference_p.h"
91#include "texteditors_p.h"
92#include "tile.h"
93#include "tilesmanager_p.h"
94#include "utils.h"
95#include "utils_p.h"
96#include "view.h"
97#include "view_p.h"
98
99#include <config-okular.h>
100
101#if HAVE_MALLOC_TRIM
102#include "malloc.h"
103#endif
104
105using namespace Okular;
106
107struct AllocatedPixmap {
108 // owner of the page
109 DocumentObserver *observer;
110 int page;
111 qulonglong memory;
112 // public constructor: initialize data
113 AllocatedPixmap(DocumentObserver *o, int p, qulonglong m)
114 : observer(o)
115 , page(p)
116 , memory(m)
117 {
118 }
119};
120
121struct ArchiveData {
122 ArchiveData()
123 {
124 }
125
126 QString originalFileName;
127 QTemporaryFile document;
128 QTemporaryFile metadataFile;
129};
130
131struct RunningSearch {
132 // store search properties
133 int continueOnPage;
134 RegularAreaRect continueOnMatch;
135 QSet<int> highlightedPages;
136
137 // fields related to previous searches (used for 'continueSearch')
138 QString cachedString;
139 Document::SearchType cachedType;
140 Qt::CaseSensitivity cachedCaseSensitivity;
141 bool cachedViewportMove : 1;
142 bool isCurrentlySearching : 1;
143 QColor cachedColor;
144 int pagesDone;
145};
146
147#define foreachObserver(cmd) \
148 { \
149 QSet<DocumentObserver *>::const_iterator it = d->m_observers.constBegin(), end = d->m_observers.constEnd(); \
150 for (; it != end; ++it) { \
151 (*it)->cmd; \
152 } \
153 }
154
155#define foreachObserverD(cmd) \
156 { \
157 QSet<DocumentObserver *>::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd(); \
158 for (; it != end; ++it) { \
159 (*it)->cmd; \
160 } \
161 }
162
163#define OKULAR_HISTORY_MAXSTEPS 100
164#define OKULAR_HISTORY_SAVEDSTEPS 10
165
166// how often to run slotTimedMemoryCheck
167constexpr int kMemCheckTime = 2000; // in msec
168// getFreeMemory is called every two seconds when checking to see if the system is low on memory. If this timeout was left at kMemCheckTime, half of these checks are useless (when okular is idle) since the cache is used when the cache is
169// <=2 seconds old. This means that after the system is out of memory, up to 4 seconds (instead of 2) could go by before okular starts to free memory.
170constexpr int kFreeMemCacheTimeout = kMemCheckTime - 100;
171
172/***** Document ******/
173
174QString DocumentPrivate::pagesSizeString() const
175{
176 if (m_generator) {
177 if (m_generator->pagesSizeMetric() != Generator::None) {
178 QSizeF size = m_parent->allPagesSize();
179 // Single page size
180 if (size.isValid()) {
181 return localizedSize(size);
182 }
183
184 // Multiple page sizes
185 QHash<QString, int> pageSizeFrequencies;
186
187 // Compute frequencies of each page size
188 for (int i = 0; i < m_pagesVector.count(); ++i) {
189 const Page *p = m_pagesVector.at(i);
190 QString sizeString = localizedSize(QSizeF(p->width(), p->height()));
191 pageSizeFrequencies[sizeString] = pageSizeFrequencies.value(sizeString, 0) + 1;
192 }
193
194 // Figure out which page size is most frequent
195 int largestFrequencySeen = 0;
196 QString mostCommonPageSize = QString();
197 QHash<QString, int>::const_iterator i = pageSizeFrequencies.constBegin();
198 while (i != pageSizeFrequencies.constEnd()) {
199 if (i.value() > largestFrequencySeen) {
200 largestFrequencySeen = i.value();
201 mostCommonPageSize = i.key();
202 }
203 ++i;
204 }
205 QString finalText = i18nc("@info %1 is a page size", "Most pages are %1.", mostCommonPageSize);
206
207 return finalText;
208 } else {
209 return QString();
210 }
211 } else {
212 return QString();
213 }
214}
215
216QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const
217{
218 const QPageLayout::Orientation orientation = inchesWidth > inchesHeight ? QPageLayout::Landscape : QPageLayout::Portrait;
219
220 const QSize pointsSize(inchesWidth * 72.0, inchesHeight * 72.0);
222
223 const QString paperName = QPageSize::name(paperSize);
224
225 if (orientation == QPageLayout::Portrait) {
226 return i18nc("paper type and orientation (eg: Portrait A4)", "Portrait %1", paperName);
227 } else {
228 return i18nc("paper type and orientation (eg: Portrait A4)", "Landscape %1", paperName);
229 }
230}
231
232QString DocumentPrivate::localizedSize(const QSizeF size) const
233{
234 double inchesWidth = 0, inchesHeight = 0;
235 switch (m_generator->pagesSizeMetric()) {
237 inchesWidth = size.width() / 72.0;
238 inchesHeight = size.height() / 72.0;
239 break;
240
241 case Generator::Pixels: {
242 const QSizeF dpi = m_generator->dpi();
243 inchesWidth = size.width() / dpi.width();
244 inchesHeight = size.height() / dpi.height();
245 } break;
246
247 case Generator::None:
248 break;
249 }
250 if (QLocale::system().measurementSystem() == QLocale::ImperialSystem) {
251 return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 × %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight));
252 } else {
253 return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 × %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight));
254 }
255}
256
257qulonglong DocumentPrivate::calculateMemoryToFree()
258{
259 // [MEM] choose memory parameters based on configuration profile
260 qulonglong clipValue = 0;
261 qulonglong memoryToFree = 0;
262
263 switch (SettingsCore::memoryLevel()) {
264 case SettingsCore::EnumMemoryLevel::Low:
265 memoryToFree = m_allocatedPixmapsTotalMemory;
266 break;
267
268 case SettingsCore::EnumMemoryLevel::Normal: {
269 qulonglong thirdTotalMemory = getTotalMemory() / 3;
270 qulonglong freeMemory = getFreeMemory();
271 if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) {
272 memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory;
273 }
274 if (m_allocatedPixmapsTotalMemory > freeMemory) {
275 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
276 }
277 } break;
278
279 case SettingsCore::EnumMemoryLevel::Aggressive: {
280 qulonglong freeMemory = getFreeMemory();
281 if (m_allocatedPixmapsTotalMemory > freeMemory) {
282 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
283 }
284 } break;
285 case SettingsCore::EnumMemoryLevel::Greedy: {
286 qulonglong freeSwap;
287 qulonglong freeMemory = getFreeMemory(&freeSwap);
288 const qulonglong memoryLimit = qMin(qMax(freeMemory, getTotalMemory() / 2), freeMemory + freeSwap);
289 if (m_allocatedPixmapsTotalMemory > memoryLimit) {
290 clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2;
291 }
292 } break;
293 }
294
295 if (clipValue > memoryToFree) {
296 memoryToFree = clipValue;
297 }
298
299 return memoryToFree;
300}
301
302void DocumentPrivate::cleanupPixmapMemory()
303{
304 cleanupPixmapMemory(calculateMemoryToFree());
305}
306
307void DocumentPrivate::cleanupPixmapMemory(qulonglong memoryToFree)
308{
309 if (memoryToFree < 1) {
310 return;
311 }
312
313 const int currentViewportPage = (*m_viewportIterator).pageNumber;
314
315 // Create a QMap of visible rects, indexed by page number
316 QMap<int, VisiblePageRect *> visibleRects;
317 QVector<Okular::VisiblePageRect *>::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
318 for (; vIt != vEnd; ++vIt) {
319 visibleRects.insert((*vIt)->pageNumber, (*vIt));
320 }
321
322 // Free memory starting from pages that are farthest from the current one
323 int pagesFreed = 0;
324 while (memoryToFree > 0) {
325 AllocatedPixmap *p = searchLowestPriorityPixmap(true, true);
326 if (!p) { // No pixmap to remove
327 break;
328 }
329
330 qCDebug(OkularCoreDebug).nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page;
331
332 // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove
333 // the memory used by the AllocatedPixmap so at most it can reach zero
334 m_allocatedPixmapsTotalMemory -= p->memory;
335 // Make sure memoryToFree does not underflow
336 if (p->memory > memoryToFree) {
337 memoryToFree = 0;
338 } else {
339 memoryToFree -= p->memory;
340 }
341 pagesFreed++;
342 // delete pixmap
343 m_pagesVector.at(p->page)->deletePixmap(p->observer);
344 // delete allocation descriptor
345 delete p;
346 }
347
348 // If we're still on low memory, try to free individual tiles
349
350 // Store pages that weren't completely removed
351
352 std::list<AllocatedPixmap *> pixmapsToKeep;
353 while (memoryToFree > 0) {
354 int clean_hits = 0;
355 for (DocumentObserver *observer : std::as_const(m_observers)) {
356 AllocatedPixmap *p = searchLowestPriorityPixmap(false, true, observer);
357 if (!p) { // No pixmap to remove
358 continue;
359 }
360
361 clean_hits++;
362
363 TilesManager *tilesManager = m_pagesVector.at(p->page)->d->tilesManager(observer);
364 if (tilesManager && tilesManager->totalMemory() > 0) {
365 qulonglong memoryDiff = p->memory;
366 NormalizedRect visibleRect;
367 if (visibleRects.contains(p->page)) {
368 visibleRect = visibleRects[p->page]->rect;
369 }
370
371 // Free non visible tiles
372 tilesManager->cleanupPixmapMemory(memoryToFree, visibleRect, currentViewportPage);
373
374 p->memory = tilesManager->totalMemory();
375 memoryDiff -= p->memory;
376 memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0;
377 m_allocatedPixmapsTotalMemory -= memoryDiff;
378
379 if (p->memory > 0) {
380 pixmapsToKeep.push_back(p);
381 } else {
382 delete p;
383 }
384 } else {
385 pixmapsToKeep.push_back(p);
386 }
387 }
388
389 if (clean_hits == 0) {
390 break;
391 }
392 }
393
394 m_allocatedPixmaps.splice(m_allocatedPixmaps.end(), pixmapsToKeep);
395 Q_UNUSED(pagesFreed);
396 // p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() );
397}
398
399/* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap
400 * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If
401 * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before
402 * returning it
403 */
404AllocatedPixmap *DocumentPrivate::searchLowestPriorityPixmap(bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer)
405{
406 std::list<AllocatedPixmap *>::iterator pIt = m_allocatedPixmaps.begin();
407 std::list<AllocatedPixmap *>::iterator pEnd = m_allocatedPixmaps.end();
408 std::list<AllocatedPixmap *>::iterator farthestPixmap = pEnd;
409 const int currentViewportPage = (*m_viewportIterator).pageNumber;
410
411 /* Find the pixmap that is farthest from the current viewport */
412 int maxDistance = -1;
413 while (pIt != pEnd) {
414 const AllocatedPixmap *p = *pIt;
415 // Filter by observer
416 if (observer == nullptr || p->observer == observer) {
417 const int distance = qAbs(p->page - currentViewportPage);
418 if (maxDistance < distance && (!unloadableOnly || p->observer->canUnloadPixmap(p->page))) {
419 maxDistance = distance;
420 farthestPixmap = pIt;
421 }
422 }
423 ++pIt;
424 }
425
426 /* No pixmap to remove */
427 if (farthestPixmap == pEnd) {
428 return nullptr;
429 }
430
431 AllocatedPixmap *selectedPixmap = *farthestPixmap;
432 if (thenRemoveIt) {
433 m_allocatedPixmaps.erase(farthestPixmap);
434 }
435 return selectedPixmap;
436}
437
438qulonglong DocumentPrivate::getTotalMemory()
439{
440 static qulonglong cachedValue = 0;
441 if (cachedValue) {
442 return cachedValue;
443 }
444
445#if defined(Q_OS_LINUX)
446 // if /proc/meminfo doesn't exist, return 128MB
447 QFile memFile(QStringLiteral("/proc/meminfo"));
448 if (!memFile.open(QIODevice::ReadOnly)) {
449 return (cachedValue = 134217728);
450 }
451
452 QTextStream readStream(&memFile);
453 while (true) {
454 QString entry = readStream.readLine();
455 if (entry.isNull()) {
456 break;
457 }
458 if (entry.startsWith(QLatin1String("MemTotal:"))) {
459 return (cachedValue = (Q_UINT64_C(1024) * entry.section(QLatin1Char(' '), -2, -2).toULongLong()));
460 }
461 }
462#elif defined(Q_OS_FREEBSD)
463 qulonglong physmem;
464 int mib[] = {CTL_HW, HW_PHYSMEM};
465 size_t len = sizeof(physmem);
466 if (sysctl(mib, 2, &physmem, &len, NULL, 0) == 0)
467 return (cachedValue = physmem);
468#elif defined(Q_OS_WIN)
469 MEMORYSTATUSEX stat;
470 stat.dwLength = sizeof(stat);
471 GlobalMemoryStatusEx(&stat);
472
473 return (cachedValue = stat.ullTotalPhys);
474#endif
475 return (cachedValue = 134217728);
476}
477
478qulonglong DocumentPrivate::getFreeMemory(qulonglong *freeSwap)
479{
480 static QDeadlineTimer cacheTimer(0);
481 static qulonglong cachedValue = 0;
482 static qulonglong cachedFreeSwap = 0;
483
484 if (!cacheTimer.hasExpired()) {
485 if (freeSwap) {
486 *freeSwap = cachedFreeSwap;
487 }
488 return cachedValue;
489 }
490
491 /* Initialize the returned free swap value to 0. It is overwritten if the
492 * actual value is available */
493 if (freeSwap) {
494 *freeSwap = 0;
495 }
496
497#if defined(Q_OS_LINUX)
498 // if /proc/meminfo doesn't exist, return MEMORY FULL
499 QFile memFile(QStringLiteral("/proc/meminfo"));
500 if (!memFile.open(QIODevice::ReadOnly)) {
501 return 0;
502 }
503
504 // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
505 // and 'Cached' fields. consider swapped memory as used memory.
506 qulonglong memoryFree = 0;
507 QString entry;
508 QTextStream readStream(&memFile);
509 static const int nElems = 5;
510 QString names[nElems] = {QStringLiteral("MemFree:"), QStringLiteral("Buffers:"), QStringLiteral("Cached:"), QStringLiteral("SwapFree:"), QStringLiteral("SwapTotal:")};
511 qulonglong values[nElems] = {0, 0, 0, 0, 0};
512 bool foundValues[nElems] = {false, false, false, false, false};
513 while (true) {
514 entry = readStream.readLine();
515 if (entry.isNull()) {
516 break;
517 }
518 for (int i = 0; i < nElems; ++i) {
519 if (entry.startsWith(names[i])) {
520 values[i] = entry.section(QLatin1Char(' '), -2, -2).toULongLong(&foundValues[i]);
521 }
522 }
523 }
524 memFile.close();
525 bool found = true;
526 for (int i = 0; found && i < nElems; ++i) {
527 found = found && foundValues[i];
528 }
529 if (found) {
530 /* MemFree + Buffers + Cached - SwapUsed =
531 * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) =
532 * = MemFree + Buffers + Cached + SwapFree - SwapTotal */
533 memoryFree = values[0] + values[1] + values[2] + values[3];
534 if (values[4] > memoryFree) {
535 memoryFree = 0;
536 } else {
537 memoryFree -= values[4];
538 }
539 } else {
540 return 0;
541 }
542
543 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
544
545 if (freeSwap) {
546 *freeSwap = (cachedFreeSwap = (Q_UINT64_C(1024) * values[3]));
547 }
548 return (cachedValue = (Q_UINT64_C(1024) * memoryFree));
549#elif defined(Q_OS_FREEBSD)
550 qulonglong cache, inact, free, psize;
551 size_t cachelen, inactlen, freelen, psizelen;
552 cachelen = sizeof(cache);
553 inactlen = sizeof(inact);
554 freelen = sizeof(free);
555 psizelen = sizeof(psize);
556 // sum up inactive, cached and free memory
557 if (sysctlbyname("vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0) == 0 && sysctlbyname("vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0) == 0 &&
558 sysctlbyname("vm.stats.vm.v_free_count", &free, &freelen, NULL, 0) == 0 && sysctlbyname("vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0) == 0) {
559 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
560 return (cachedValue = (cache + inact + free) * psize);
561 } else {
562 return 0;
563 }
564#elif defined(Q_OS_WIN)
565 MEMORYSTATUSEX stat;
566 stat.dwLength = sizeof(stat);
567 GlobalMemoryStatusEx(&stat);
568
569 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
570
571 if (freeSwap)
572 *freeSwap = (cachedFreeSwap = stat.ullAvailPageFile);
573 return (cachedValue = stat.ullAvailPhys);
574#else
575 // tell the memory is full.. will act as in LOW profile
576 return 0;
577#endif
578}
579
580bool DocumentPrivate::loadDocumentInfo(LoadDocumentInfoFlags loadWhat)
581// note: load data and stores it internally (document or pages). observers
582// are still uninitialized at this point so don't access them
583{
584 // qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file.";
585 if (m_xmlFileName.isEmpty()) {
586 return false;
587 }
588
589 QFile infoFile(m_xmlFileName);
590 return loadDocumentInfo(infoFile, loadWhat);
591}
592
593bool DocumentPrivate::loadDocumentInfo(QFile &infoFile, LoadDocumentInfoFlags loadWhat)
594{
595 if (!infoFile.exists() || !infoFile.open(QIODevice::ReadOnly)) {
596 // Use the default layout provided by the generator
597 if (loadWhat & LoadGeneralInfo) {
598 Generator::PageLayout defaultViewMode = m_generator->defaultPageLayout();
599 if (defaultViewMode == Generator::NoLayout) {
600 return false;
601 }
602
603 for (View *view : std::as_const(m_views)) {
604 setDefaultViewMode(view, defaultViewMode);
605 }
606 }
607 return false;
608 }
609
610 // Load DOM from XML file
611 QDomDocument doc(QStringLiteral("documentInfo"));
612 if (!doc.setContent(&infoFile)) {
613 qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml.";
614 infoFile.close();
615 return false;
616 }
617 infoFile.close();
618
619 QDomElement root = doc.documentElement();
620
621 if (root.tagName() != QLatin1String("documentInfo")) {
622 return false;
623 }
624
625 bool loadedAnything = false; // set if something gets actually loaded
626
627 // Parse the DOM tree
628 QDomNode topLevelNode = root.firstChild();
629 while (topLevelNode.isElement()) {
630 QString catName = topLevelNode.toElement().tagName();
631
632 // Restore page attributes (bookmark, annotations, ...) from the DOM
633 if (catName == QLatin1String("pageList") && (loadWhat & LoadPageInfo)) {
634 QDomNode pageNode = topLevelNode.firstChild();
635 while (pageNode.isElement()) {
636 QDomElement pageElement = pageNode.toElement();
637 if (pageElement.hasAttribute(QStringLiteral("number"))) {
638 // get page number (node's attribute)
639 bool ok;
640 int pageNumber = pageElement.attribute(QStringLiteral("number")).toInt(&ok);
641
642 // pass the domElement to the right page, to read config data from
643 if (ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count()) {
644 if (m_pagesVector[pageNumber]->d->restoreLocalContents(pageElement)) {
645 loadedAnything = true;
646 }
647 }
648 }
649 pageNode = pageNode.nextSibling();
650 }
651 }
652
653 // Restore 'general info' from the DOM
654 else if (catName == QLatin1String("generalInfo") && (loadWhat & LoadGeneralInfo)) {
655 QDomNode infoNode = topLevelNode.firstChild();
656 while (infoNode.isElement()) {
657 QDomElement infoElement = infoNode.toElement();
658
659 // restore viewports history
660 if (infoElement.tagName() == QLatin1String("history")) {
661 // clear history
662 m_viewportHistory.clear();
663 // append old viewports
664 QDomNode historyNode = infoNode.firstChild();
665 while (historyNode.isElement()) {
666 QDomElement historyElement = historyNode.toElement();
667 if (historyElement.hasAttribute(QStringLiteral("viewport"))) {
668 QString vpString = historyElement.attribute(QStringLiteral("viewport"));
669 m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(), DocumentViewport(vpString));
670 loadedAnything = true;
671 }
672 historyNode = historyNode.nextSibling();
673 }
674 // consistency check
675 if (m_viewportHistory.empty()) {
676 m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(), DocumentViewport());
677 }
678 } else if (infoElement.tagName() == QLatin1String("rotation")) {
679 QString str = infoElement.text();
680 bool ok = true;
681 int newrotation = !str.isEmpty() ? (str.toInt(&ok) % 4) : 0;
682 if (ok && newrotation != 0) {
683 setRotationInternal(newrotation, false);
684 loadedAnything = true;
685 }
686 } else if (infoElement.tagName() == QLatin1String("views")) {
687 QDomNode viewNode = infoNode.firstChild();
688 while (viewNode.isElement()) {
689 QDomElement viewElement = viewNode.toElement();
690 if (viewElement.tagName() == QLatin1String("view")) {
691 const QString viewName = viewElement.attribute(QStringLiteral("name"));
692 for (View *view : std::as_const(m_views)) {
693 if (view->name() == viewName) {
694 loadViewsInfo(view, viewElement);
695 loadedAnything = true;
696 break;
697 }
698 }
699 }
700 viewNode = viewNode.nextSibling();
701 }
702 }
703 infoNode = infoNode.nextSibling();
704 }
705 }
706
707 topLevelNode = topLevelNode.nextSibling();
708 } // </documentInfo>
709
710 return loadedAnything;
711}
712
713void DocumentPrivate::loadViewsInfo(View *view, const QDomElement &e)
714{
715 QDomNode viewNode = e.firstChild();
716 while (viewNode.isElement()) {
717 QDomElement viewElement = viewNode.toElement();
718
719 if (viewElement.tagName() == QLatin1String("zoom")) {
720 const QString valueString = viewElement.attribute(QStringLiteral("value"));
721 bool newzoom_ok = true;
722 const double newzoom = !valueString.isEmpty() ? valueString.toDouble(&newzoom_ok) : 1.0;
723 if (newzoom_ok && newzoom != 0 && view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable))) {
724 view->setCapability(View::Zoom, newzoom);
725 }
726 const QString modeString = viewElement.attribute(QStringLiteral("mode"));
727 bool newmode_ok = true;
728 const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
729 if (newmode_ok && view->supportsCapability(View::ZoomModality) && (view->capabilityFlags(View::ZoomModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
730 view->setCapability(View::ZoomModality, newmode);
731 }
732 } else if (viewElement.tagName() == QLatin1String("viewMode")) {
733 const QString modeString = viewElement.attribute(QStringLiteral("mode"));
734 bool newmode_ok = true;
735 const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
736 if (newmode_ok && view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
737 view->setCapability(View::ViewModeModality, newmode);
738 }
739 } else if (viewElement.tagName() == QLatin1String("continuous")) {
740 const QString modeString = viewElement.attribute(QStringLiteral("mode"));
741 bool newmode_ok = true;
742 const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
743 if (newmode_ok && view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
744 view->setCapability(View::Continuous, newmode);
745 }
746 } else if (viewElement.tagName() == QLatin1String("trimMargins")) {
747 const QString valueString = viewElement.attribute(QStringLiteral("value"));
748 bool newmode_ok = true;
749 const int newmode = !valueString.isEmpty() ? valueString.toInt(&newmode_ok) : 2;
750 if (newmode_ok && view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
751 view->setCapability(View::TrimMargins, newmode);
752 }
753 }
754
755 viewNode = viewNode.nextSibling();
756 }
757}
758
759void DocumentPrivate::setDefaultViewMode(View *view, Generator::PageLayout defaultViewMode)
760{
761 if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
762 view->setCapability(View::ViewModeModality, (int)defaultViewMode);
763 }
764
765 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
766 view->setCapability(View::Continuous, (int)m_generator->defaultPageContinuous());
767 }
768}
769
770void DocumentPrivate::saveViewsInfo(View *view, QDomElement &e) const
771{
772 if (view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable)) && view->supportsCapability(View::ZoomModality) &&
774 QDomElement zoomEl = e.ownerDocument().createElement(QStringLiteral("zoom"));
775 e.appendChild(zoomEl);
776 bool ok = true;
777 const double zoom = view->capability(View::Zoom).toDouble(&ok);
778 if (ok && zoom != 0) {
779 zoomEl.setAttribute(QStringLiteral("value"), QString::number(zoom));
780 }
781 const int mode = view->capability(View::ZoomModality).toInt(&ok);
782 if (ok) {
783 zoomEl.setAttribute(QStringLiteral("mode"), mode);
784 }
785 }
786 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
787 QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("continuous"));
788 e.appendChild(contEl);
789 const bool mode = view->capability(View::Continuous).toBool();
790 contEl.setAttribute(QStringLiteral("mode"), mode);
791 }
792 if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
793 QDomElement viewEl = e.ownerDocument().createElement(QStringLiteral("viewMode"));
794 e.appendChild(viewEl);
795 bool ok = true;
796 const int mode = view->capability(View::ViewModeModality).toInt(&ok);
797 if (ok) {
798 viewEl.setAttribute(QStringLiteral("mode"), mode);
799 }
800 }
801 if (view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
802 QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("trimMargins"));
803 e.appendChild(contEl);
804 const bool value = view->capability(View::TrimMargins).toBool();
805 contEl.setAttribute(QStringLiteral("value"), value);
806 }
807}
808
809QUrl DocumentPrivate::giveAbsoluteUrl(const QString &fileName) const
810{
811 if (!QDir::isRelativePath(fileName)) {
812 return QUrl::fromLocalFile(fileName);
813 }
814
815 if (!m_url.isValid()) {
816 return QUrl();
817 }
818
819 return QUrl(KIO::upUrl(m_url).toString() + fileName);
820}
821
822bool DocumentPrivate::openRelativeFile(const QString &fileName)
823{
824 const QUrl newUrl = giveAbsoluteUrl(fileName);
825 if (newUrl.isEmpty()) {
826 return false;
827 }
828
829 qCDebug(OkularCoreDebug).nospace() << "openRelativeFile: '" << newUrl << "'";
830
831 Q_EMIT m_parent->openUrl(newUrl);
832 return m_url == newUrl;
833}
834
835Generator *DocumentPrivate::loadGeneratorLibrary(const KPluginMetaData &service)
836{
837 const auto result = KPluginFactory::instantiatePlugin<Okular::Generator>(service);
838
839 if (!result) {
840 qCWarning(OkularCoreDebug).nospace() << "Failed to load plugin " << service.fileName() << ": " << result.errorText;
841 return nullptr;
842 }
843
844 GeneratorInfo info(result.plugin, service);
845 m_loadedGenerators.insert(service.pluginId(), info);
846 return result.plugin;
847}
848
849void DocumentPrivate::loadAllGeneratorLibraries()
850{
851 if (m_generatorsLoaded) {
852 return;
853 }
854
855 loadServiceList(availableGenerators());
856
857 m_generatorsLoaded = true;
858}
859
860void DocumentPrivate::loadServiceList(const QVector<KPluginMetaData> &offers)
861{
862 int count = offers.count();
863 if (count <= 0) {
864 return;
865 }
866
867 for (int i = 0; i < count; ++i) {
868 QString id = offers.at(i).pluginId();
869 // don't load already loaded generators
870 QHash<QString, GeneratorInfo>::const_iterator genIt = m_loadedGenerators.constFind(id);
871 if (!m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd()) {
872 continue;
873 }
874
875 Generator *g = loadGeneratorLibrary(offers.at(i));
876 (void)g;
877 }
878}
879
880void DocumentPrivate::unloadGenerator(const GeneratorInfo &info)
881{
882 delete info.generator;
883}
884
885void DocumentPrivate::cacheExportFormats()
886{
887 if (m_exportCached) {
888 return;
889 }
890
891 const ExportFormat::List formats = m_generator->exportFormats();
892 for (int i = 0; i < formats.count(); ++i) {
893 if (formats.at(i).mimeType().name() == QLatin1String("text/plain")) {
894 m_exportToText = formats.at(i);
895 } else {
896 m_exportFormats.append(formats.at(i));
897 }
898 }
899
900 m_exportCached = true;
901}
902
903ConfigInterface *DocumentPrivate::generatorConfig(GeneratorInfo &info)
904{
905 if (info.configChecked) {
906 return info.config;
907 }
908
909 info.config = qobject_cast<Okular::ConfigInterface *>(info.generator);
910 info.configChecked = true;
911 return info.config;
912}
913
914SaveInterface *DocumentPrivate::generatorSave(GeneratorInfo &info)
915{
916 if (info.saveChecked) {
917 return info.save;
918 }
919
920 info.save = qobject_cast<Okular::SaveInterface *>(info.generator);
921 info.saveChecked = true;
922 return info.save;
923}
924
925Document::OpenResult DocumentPrivate::openDocumentInternal(const KPluginMetaData &offer, bool isstdin, const QString &docFile, const QByteArray &filedata, const QString &password)
926{
927 QString propName = offer.pluginId();
928 QHash<QString, GeneratorInfo>::const_iterator genIt = m_loadedGenerators.constFind(propName);
929 m_walletGenerator = nullptr;
930 if (genIt != m_loadedGenerators.constEnd()) {
931 m_generator = genIt.value().generator;
932 } else {
933 m_generator = loadGeneratorLibrary(offer);
934 if (!m_generator) {
935 return Document::OpenError;
936 }
937 genIt = m_loadedGenerators.constFind(propName);
938 Q_ASSERT(genIt != m_loadedGenerators.constEnd());
939 }
940 Q_ASSERT_X(m_generator, "Document::load()", "null generator?!");
941
942 m_generator->d_func()->m_document = this;
943
944 // connect error reporting signals
945 m_openError.clear();
946 QMetaObject::Connection errorToOpenErrorConnection = QObject::connect(m_generator, &Generator::error, m_parent, [this](const QString &message) { m_openError = message; });
947 QObject::connect(m_generator, &Generator::warning, m_parent, &Document::warning);
948 QObject::connect(m_generator, &Generator::notice, m_parent, &Document::notice);
949
951
952 const QWindow *window = m_widget && m_widget->window() ? m_widget->window()->windowHandle() : nullptr;
953 const QSizeF dpi = Utils::realDpi(window);
954 qCDebug(OkularCoreDebug) << "Output DPI:" << dpi;
955 m_generator->setDPI(dpi);
956
957 Document::OpenResult openResult = Document::OpenError;
958 if (!isstdin) {
959 openResult = m_generator->loadDocumentWithPassword(docFile, m_pagesVector, password);
960 } else if (!filedata.isEmpty()) {
961 if (m_generator->hasFeature(Generator::ReadRawData)) {
962 openResult = m_generator->loadDocumentFromDataWithPassword(filedata, m_pagesVector, password);
963 } else {
964 m_tempFile = new QTemporaryFile();
965 if (!m_tempFile->open()) {
966 delete m_tempFile;
967 m_tempFile = nullptr;
968 } else {
969 m_tempFile->write(filedata);
970 QString tmpFileName = m_tempFile->fileName();
971 m_tempFile->close();
972 openResult = m_generator->loadDocumentWithPassword(tmpFileName, m_pagesVector, password);
973 }
974 }
975 }
976
978 if (openResult != Document::OpenSuccess || m_pagesVector.size() <= 0) {
979 m_generator->d_func()->m_document = nullptr;
980 QObject::disconnect(m_generator, nullptr, m_parent, nullptr);
981
982 // TODO this is a bit of a hack, since basically means that
983 // you can only call walletDataForFile after calling openDocument
984 // but since in reality it's what happens I've decided not to refactor/break API
985 // One solution is just kill walletDataForFile and make OpenResult be an object
986 // where the wallet data is also returned when OpenNeedsPassword
987 m_walletGenerator = m_generator;
988 m_generator = nullptr;
989
990 qDeleteAll(m_pagesVector);
991 m_pagesVector.clear();
992 delete m_tempFile;
993 m_tempFile = nullptr;
994
995 // TODO: Q_EMIT a message telling the document is empty
996 if (openResult == Document::OpenSuccess) {
997 openResult = Document::OpenError;
998 }
999 } else {
1000 /*
1001 * Now that the documen is opened, the tab (if using tabs) is visible, which mean that
1002 * we can now connect the error reporting signal directly to the parent
1003 */
1004
1005 QObject::disconnect(errorToOpenErrorConnection);
1006 QObject::connect(m_generator, &Generator::error, m_parent, &Document::error);
1007 }
1008
1009 return openResult;
1010}
1011
1012bool DocumentPrivate::savePageDocumentInfo(QTemporaryFile *infoFile, int what) const
1013{
1014 if (infoFile->open()) {
1015 // 1. Create DOM
1016 QDomDocument doc(QStringLiteral("documentInfo"));
1017 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
1018 doc.appendChild(xmlPi);
1019 QDomElement root = doc.createElement(QStringLiteral("documentInfo"));
1020 doc.appendChild(root);
1021
1022 // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
1023 QDomElement pageList = doc.createElement(QStringLiteral("pageList"));
1024 root.appendChild(pageList);
1025 // <page list><page number='x'>.... </page> save pages that hold data
1026 QVector<Page *>::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd();
1027 for (; pIt != pEnd; ++pIt) {
1028 (*pIt)->d->saveLocalContents(pageList, doc, PageItems(what));
1029 }
1030
1031 // 3. Save DOM to XML file
1032 QString xml = doc.toString();
1033
1034 QTextStream os(infoFile);
1035 os.setEncoding(QStringConverter::Utf8);
1036 os << xml;
1037 return true;
1038 }
1039 return false;
1040}
1041
1042DocumentViewport DocumentPrivate::nextDocumentViewport() const
1043{
1044 DocumentViewport ret = m_nextDocumentViewport;
1045 if (!m_nextDocumentDestination.isEmpty() && m_generator) {
1046 DocumentViewport vp(m_parent->metaData(QStringLiteral("NamedViewport"), m_nextDocumentDestination).toString());
1047 if (vp.isValid()) {
1048 ret = vp;
1049 }
1050 }
1051 return ret;
1052}
1053
1054void DocumentPrivate::performAddPageAnnotation(int page, Annotation *annotation)
1055{
1056 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1057 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1058
1059 // find out the page to attach annotation
1060 Page *kp = m_pagesVector[page];
1061 if (!m_generator || !kp) {
1062 return;
1063 }
1064
1065 // the annotation belongs already to a page
1066 if (annotation->d_ptr->m_page) {
1067 return;
1068 }
1069
1070 // add annotation to the page
1071 kp->addAnnotation(annotation);
1072
1073 // tell the annotation proxy
1074 if (proxy && proxy->supports(AnnotationProxy::Addition)) {
1075 proxy->notifyAddition(annotation, page);
1076 }
1077
1078 // notify observers about the change
1079 notifyAnnotationChanges(page);
1080
1081 if (annotation->flags() & Annotation::ExternallyDrawn) {
1082 // Redraw everything, including ExternallyDrawn annotations
1083 refreshPixmaps(page);
1084 }
1085}
1086
1087void DocumentPrivate::performRemovePageAnnotation(int page, Annotation *annotation)
1088{
1089 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1090 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1091 bool isExternallyDrawn;
1092
1093 // find out the page
1094 Page *kp = m_pagesVector[page];
1095 if (!m_generator || !kp) {
1096 return;
1097 }
1098
1099 if (annotation->flags() & Annotation::ExternallyDrawn) {
1100 isExternallyDrawn = true;
1101 } else {
1102 isExternallyDrawn = false;
1103 }
1104
1105 // try to remove the annotation
1106 if (m_parent->canRemovePageAnnotation(annotation)) {
1107 // tell the annotation proxy
1108 if (proxy && proxy->supports(AnnotationProxy::Removal)) {
1109 proxy->notifyRemoval(annotation, page);
1110 }
1111
1112 kp->removeAnnotation(annotation); // Also destroys the object
1113
1114 // in case of success, notify observers about the change
1115 notifyAnnotationChanges(page);
1116
1117 if (isExternallyDrawn) {
1118 // Redraw everything, including ExternallyDrawn annotations
1119 refreshPixmaps(page);
1120 }
1121 }
1122}
1123
1124void DocumentPrivate::performModifyPageAnnotation(int page, Annotation *annotation, bool appearanceChanged)
1125{
1126 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1127 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1128
1129 // find out the page
1130 const Page *kp = m_pagesVector[page];
1131 if (!m_generator || !kp) {
1132 return;
1133 }
1134
1135 // tell the annotation proxy
1136 if (proxy && proxy->supports(AnnotationProxy::Modification)) {
1137 proxy->notifyModification(annotation, page, appearanceChanged);
1138 }
1139
1140 // notify observers about the change
1141 notifyAnnotationChanges(page);
1142 if (appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn)) {
1143 /* When an annotation is being moved, the generator will not render it.
1144 * Therefore there's no need to refresh pixmaps after the first time */
1145 if (annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized)) {
1146 if (m_annotationBeingModified) {
1147 return;
1148 } else { // First time: take note
1149 m_annotationBeingModified = true;
1150 }
1151 } else {
1152 m_annotationBeingModified = false;
1153 }
1154
1155 // Redraw everything, including ExternallyDrawn annotations
1156 qCDebug(OkularCoreDebug) << "Refreshing Pixmaps";
1157 refreshPixmaps(page);
1158 }
1159}
1160
1161void DocumentPrivate::performSetAnnotationContents(const QString &newContents, Annotation *annot, int pageNumber)
1162{
1163 bool appearanceChanged = false;
1164
1165 // Check if appearanceChanged should be true
1166 switch (annot->subType()) {
1167 // If it's an in-place TextAnnotation, set the inplace text
1169 const Okular::TextAnnotation *txtann = static_cast<Okular::TextAnnotation *>(annot);
1170 if (txtann->textType() == Okular::TextAnnotation::InPlace) {
1171 appearanceChanged = true;
1172 }
1173 break;
1174 }
1175 // If it's a LineAnnotation, check if caption text is visible
1177 const Okular::LineAnnotation *lineann = static_cast<Okular::LineAnnotation *>(annot);
1178 if (lineann->showCaption()) {
1179 appearanceChanged = true;
1180 }
1181 break;
1182 }
1183 default:
1184 break;
1185 }
1186
1187 // Set contents
1188 annot->setContents(newContents);
1189
1190 // Tell the document the annotation has been modified
1191 performModifyPageAnnotation(pageNumber, annot, appearanceChanged);
1192}
1193
1194void DocumentPrivate::recalculateForms()
1195{
1196 const QVariant fco = m_parent->metaData(QStringLiteral("FormCalculateOrder"));
1197 const QVector<int> formCalculateOrder = fco.value<QVector<int>>();
1198 for (int formId : formCalculateOrder) {
1199 for (uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++) {
1200 const Page *p = m_parent->page(pageIdx);
1201 if (p) {
1202 bool pageNeedsRefresh = false;
1203 const QList<Okular::FormField *> forms = p->formFields();
1204 for (FormField *form : forms) {
1205 if (form->id() == formId) {
1206 const Action *action = form->additionalAction(FormField::CalculateField);
1207 if (action) {
1208 std::shared_ptr<Event> event;
1209 QString oldVal;
1210 if (dynamic_cast<FormFieldText *>(form) || dynamic_cast<FormFieldChoice *>(form)) {
1211 // Prepare text calculate event
1212 event = Event::createFormCalculateEvent(form, m_pagesVector[pageIdx]);
1213 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
1214 executeScriptEvent(event, linkscript);
1215 // The value maybe changed in javascript so save it first.
1216 oldVal = form->value().toString();
1217
1218 if (event) {
1219 // Update text field from calculate
1220 const QString newVal = event->value().toString();
1221 if (newVal != oldVal) {
1222 form->setValue(QVariant(newVal));
1223 form->setAppearanceValue(QVariant(newVal));
1224 bool returnCode = true;
1225 if (form->additionalAction(Okular::FormField::FieldModified) && !form->isReadOnly()) {
1226 m_parent->processKeystrokeCommitAction(form->additionalAction(Okular::FormField::FieldModified), form, returnCode);
1227 }
1228 if (const Okular::Action *action = form->additionalAction(Okular::FormField::ValidateField)) {
1229 if (returnCode) {
1230 m_parent->processValidateAction(action, form, returnCode);
1231 }
1232 }
1233 if (!returnCode) {
1234 continue;
1235 } else {
1236 form->commitValue(form->value().toString());
1237 }
1238 if (const Okular::Action *action = form->additionalAction(Okular::FormField::FormatField)) {
1239 // The format action handles the refresh.
1240 m_parent->processFormatAction(action, form);
1241 } else {
1242 form->commitFormattedValue(form->value().toString());
1243 Q_EMIT m_parent->refreshFormWidget(form);
1244 pageNeedsRefresh = true;
1245 }
1246 }
1247 }
1248 }
1249 } else {
1250 qWarning() << "Form that is part of calculate order doesn't have a calculate action";
1251 }
1252 }
1253 }
1254 if (pageNeedsRefresh) {
1255 refreshPixmaps(p->number());
1256 }
1257 }
1258 }
1259 }
1260}
1261
1262void DocumentPrivate::saveDocumentInfo() const
1263{
1264 if (m_xmlFileName.isEmpty()) {
1265 return;
1266 }
1267
1268 QFile infoFile(m_xmlFileName);
1269 qCDebug(OkularCoreDebug) << "About to save document info to" << m_xmlFileName;
1271 qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName;
1272 return;
1273 }
1274 // 1. Create DOM
1275 QDomDocument doc(QStringLiteral("documentInfo"));
1276 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
1277 doc.appendChild(xmlPi);
1278 QDomElement root = doc.createElement(QStringLiteral("documentInfo"));
1279 root.setAttribute(QStringLiteral("url"), m_url.toDisplayString(QUrl::PreferLocalFile));
1280 doc.appendChild(root);
1281
1282 // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
1283 // -> do this if there are not-yet-migrated annots or forms in docdata/
1284 if (m_docdataMigrationNeeded) {
1285 QDomElement pageList = doc.createElement(QStringLiteral("pageList"));
1286 root.appendChild(pageList);
1287 // OriginalAnnotationPageItems and OriginalFormFieldPageItems tell to
1288 // store the same unmodified annotation list and form contents that we
1289 // read when we opened the file and ignore any change made by the user.
1290 // Since we don't store annotations and forms in docdata/ any more, this is
1291 // necessary to preserve annotations/forms that previous Okular version
1292 // had stored there.
1293 const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems;
1294 // <page list><page number='x'>.... </page> save pages that hold data
1295 QVector<Page *>::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd();
1296 for (; pIt != pEnd; ++pIt) {
1297 (*pIt)->d->saveLocalContents(pageList, doc, saveWhat);
1298 }
1299 }
1300
1301 // 2.2. Save document info (current viewport, history, ... ) to DOM
1302 QDomElement generalInfo = doc.createElement(QStringLiteral("generalInfo"));
1303 root.appendChild(generalInfo);
1304 // create rotation node
1305 if (m_rotation != Rotation0) {
1306 QDomElement rotationNode = doc.createElement(QStringLiteral("rotation"));
1307 generalInfo.appendChild(rotationNode);
1308 rotationNode.appendChild(doc.createTextNode(QString::number((int)m_rotation)));
1309 }
1310 // <general info><history> ... </history> save history up to OKULAR_HISTORY_SAVEDSTEPS viewports
1311 const auto currentViewportIterator = std::list<DocumentViewport>::const_iterator(m_viewportIterator);
1312 std::list<DocumentViewport>::const_iterator backIterator = currentViewportIterator;
1313 if (backIterator != m_viewportHistory.end()) {
1314 // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator
1315 int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
1316 while (backSteps-- && backIterator != m_viewportHistory.begin()) {
1317 --backIterator;
1318 }
1319
1320 // create history root node
1321 QDomElement historyNode = doc.createElement(QStringLiteral("history"));
1322 generalInfo.appendChild(historyNode);
1323
1324 // add old[backIterator] and present[viewportIterator] items
1325 std::list<DocumentViewport>::const_iterator endIt = currentViewportIterator;
1326 ++endIt;
1327 while (backIterator != endIt) {
1328 QString name = (backIterator == currentViewportIterator) ? QStringLiteral("current") : QStringLiteral("oldPage");
1329 QDomElement historyEntry = doc.createElement(name);
1330 historyEntry.setAttribute(QStringLiteral("viewport"), (*backIterator).toString());
1331 historyNode.appendChild(historyEntry);
1332 ++backIterator;
1333 }
1334 }
1335 // create views root node
1336 QDomElement viewsNode = doc.createElement(QStringLiteral("views"));
1337 generalInfo.appendChild(viewsNode);
1338 for (View *view : std::as_const(m_views)) {
1339 QDomElement viewEntry = doc.createElement(QStringLiteral("view"));
1340 viewEntry.setAttribute(QStringLiteral("name"), view->name());
1341 viewsNode.appendChild(viewEntry);
1342 saveViewsInfo(view, viewEntry);
1343 }
1344
1345 // 3. Save DOM to XML file
1346 QString xml = doc.toString();
1347 QTextStream os(&infoFile);
1348 os.setEncoding(QStringConverter::Utf8);
1349 os << xml;
1350 infoFile.close();
1351}
1352
1353void DocumentPrivate::slotTimedMemoryCheck()
1354{
1355 // [MEM] clean memory (for 'free mem dependent' profiles only)
1356 if (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024 * 1024) {
1357 cleanupPixmapMemory();
1358 }
1359}
1360
1361void DocumentPrivate::sendGeneratorPixmapRequest()
1362{
1363 /* If the pixmap cache will have to be cleaned in order to make room for the
1364 * next request, get the distance from the current viewport of the page
1365 * whose pixmap will be removed. We will ignore preload requests for pages
1366 * that are at the same distance or farther */
1367 const qulonglong memoryToFree = calculateMemoryToFree();
1368 const int currentViewportPage = (*m_viewportIterator).pageNumber;
1369 int maxDistance = INT_MAX; // Default: No maximum
1370 if (memoryToFree) {
1371 const AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap(true);
1372 if (pixmapToReplace) {
1373 maxDistance = qAbs(pixmapToReplace->page - currentViewportPage);
1374 }
1375 }
1376
1377 // find a request
1378 PixmapRequest *request = nullptr;
1379 m_pixmapRequestsMutex.lock();
1380 while (!m_pixmapRequestsStack.empty() && !request) {
1381 PixmapRequest *r = m_pixmapRequestsStack.back();
1382 if (!r) {
1383 m_pixmapRequestsStack.pop_back();
1384 continue;
1385 }
1386
1387 QRect requestRect = r->isTile() ? r->normalizedRect().geometry(r->width(), r->height()) : QRect(0, 0, r->width(), r->height());
1388 TilesManager *tilesManager = r->d->tilesManager();
1389 const double normalizedArea = r->normalizedRect().width() * r->normalizedRect().height();
1390 const QScreen *screen = nullptr;
1391 if (m_widget) {
1392 const QWindow *window = m_widget->window()->windowHandle();
1393 if (window) {
1394 screen = window->screen();
1395 }
1396 }
1397 if (!screen) {
1399 }
1400 const long screenSize = screen->devicePixelRatio() * screen->size().width() * screen->devicePixelRatio() * screen->size().height();
1401
1402 // Make sure the page is the right size to receive the pixmap
1403 r->page()->setPageSize(r->observer(), r->width(), r->height());
1404
1405 // If it's a preload but the generator is not threaded no point in trying to preload
1406 if (r->preload() && !m_generator->hasFeature(Generator::Threaded)) {
1407 m_pixmapRequestsStack.pop_back();
1408 delete r;
1409 }
1410 // request only if page isn't already present and request has valid id
1411 else if ((!r->d->mForce && r->page()->hasPixmap(r->observer(), r->width(), r->height(), r->normalizedRect())) || !m_observers.contains(r->observer())) {
1412 m_pixmapRequestsStack.pop_back();
1413 delete r;
1414 } else if (!r->d->mForce && r->preload() && qAbs(r->pageNumber() - currentViewportPage) >= maxDistance) {
1415 m_pixmapRequestsStack.pop_back();
1416 // qCDebug(OkularCoreDebug) << "Ignoring request that doesn't fit in cache";
1417 delete r;
1418 }
1419 // Ignore requests for pixmaps that are already being generated
1420 else if (tilesManager && tilesManager->isRequesting(r->normalizedRect(), r->width(), r->height())) {
1421 m_pixmapRequestsStack.pop_back();
1422 delete r;
1423 }
1424 // If the requested area is above 4*screenSize pixels, and we're not rendering most of the page, switch on the tile manager
1425 else if (!tilesManager && m_generator->hasFeature(Generator::TiledRendering) && (long)r->width() * (long)r->height() > 4L * screenSize && normalizedArea < 0.75) {
1426 // if the image is too big. start using tiles
1427 qCDebug(OkularCoreDebug).nospace() << "Start using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1428
1429 // fill the tiles manager with the last rendered pixmap
1430 const QPixmap *pixmap = r->page()->_o_nearestPixmap(r->observer(), r->width(), r->height());
1431 if (pixmap) {
1432 tilesManager = new TilesManager(r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation());
1433 tilesManager->setPixmap(pixmap, NormalizedRect(0, 0, 1, 1), true /*isPartialPixmap*/);
1434 tilesManager->setSize(r->width(), r->height());
1435 } else {
1436 // create new tiles manager
1437 tilesManager = new TilesManager(r->pageNumber(), r->width(), r->height(), r->page()->rotation());
1438 }
1439 tilesManager->setRequest(r->normalizedRect(), r->width(), r->height());
1440 r->page()->deletePixmap(r->observer());
1441 r->page()->d->setTilesManager(r->observer(), tilesManager);
1442 r->setTile(true);
1443
1444 // Change normalizedRect to the smallest rect that contains all
1445 // visible tiles.
1446 if (!r->normalizedRect().isNull()) {
1447 NormalizedRect tilesRect;
1448 const QList<Tile> tiles = tilesManager->tilesAt(r->normalizedRect(), TilesManager::TerminalTile);
1449 QList<Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
1450 while (tIt != tEnd) {
1451 Tile tile = *tIt;
1452 if (tilesRect.isNull()) {
1453 tilesRect = tile.rect();
1454 } else {
1455 tilesRect |= tile.rect();
1456 }
1457
1458 ++tIt;
1459 }
1460
1461 r->setNormalizedRect(tilesRect);
1462 request = r;
1463 } else {
1464 // Discard request if normalizedRect is null. This happens in
1465 // preload requests issued by PageView if the requested page is
1466 // not visible and the user has just switched from a non-tiled
1467 // zoom level to a tiled one
1468 m_pixmapRequestsStack.pop_back();
1469 delete r;
1470 }
1471 }
1472 // If the requested area is below 3*screenSize pixels, switch off the tile manager
1473 else if (tilesManager && (long)r->width() * (long)r->height() < 3L * screenSize) {
1474 qCDebug(OkularCoreDebug).nospace() << "Stop using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1475
1476 // page is too small. stop using tiles.
1477 r->page()->deletePixmap(r->observer());
1478 r->setTile(false);
1479
1480 request = r;
1481 } else if ((long)requestRect.width() * (long)requestRect.height() > 100L * screenSize && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy)) {
1482 m_pixmapRequestsStack.pop_back();
1483 if (!m_warnedOutOfMemory) {
1484 qCWarning(OkularCoreDebug).nospace() << "Running out of memory on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1485 qCWarning(OkularCoreDebug) << "this message will be reported only once.";
1486 m_warnedOutOfMemory = true;
1487 }
1488 delete r;
1489 } else {
1490 request = r;
1491 }
1492 }
1493
1494 // if no request found (or already generated), return
1495 if (!request) {
1496 m_pixmapRequestsMutex.unlock();
1497 return;
1498 }
1499
1500 // [MEM] preventive memory freeing
1501 qulonglong pixmapBytes = 0;
1502 TilesManager *tm = request->d->tilesManager();
1503 if (tm) {
1504 pixmapBytes = tm->totalMemory();
1505 } else {
1506 pixmapBytes = 4 * request->width() * request->height();
1507 }
1508
1509 if (pixmapBytes > (1024 * 1024)) {
1510 cleanupPixmapMemory(memoryToFree /* previously calculated value */);
1511 }
1512
1513 // submit the request to the generator
1514 if (m_generator->canGeneratePixmap()) {
1515 QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height()) : request->normalizedRect().geometry(request->width(), request->height());
1516 qCDebug(OkularCoreDebug).nospace() << "sending request observer=" << request->observer() << " " << requestRect.width() << "x" << requestRect.height() << "@" << request->pageNumber() << " async == " << request->asynchronous()
1517 << " isTile == " << request->isTile();
1518 m_pixmapRequestsStack.remove(request);
1519
1520 if (tm) {
1521 tm->setRequest(request->normalizedRect(), request->width(), request->height());
1522 }
1523
1524 if ((int)m_rotation % 2) {
1525 request->d->swap();
1526 }
1527
1528 if (m_rotation != Rotation0 && !request->normalizedRect().isNull()) {
1529 request->setNormalizedRect(TilesManager::fromRotatedRect(request->normalizedRect(), m_rotation));
1530 }
1531
1532 // If set elsewhere we already know we want it to be partial
1533 if (!request->partialUpdatesWanted()) {
1534 request->setPartialUpdatesWanted(request->asynchronous() && !request->page()->hasPixmap(request->observer()));
1535 }
1536
1537 // we always have to unlock _before_ the generatePixmap() because
1538 // a sync generation would end with requestDone() -> deadlock, and
1539 // we can not really know if the generator can do async requests
1540 m_executingPixmapRequests.push_back(request);
1541 m_pixmapRequestsMutex.unlock();
1542 m_generator->generatePixmap(request);
1543 } else {
1544 m_pixmapRequestsMutex.unlock();
1545 // pino (7/4/2006): set the polling interval from 10 to 30
1546 QTimer::singleShot(30, m_parent, [this] { sendGeneratorPixmapRequest(); });
1547 }
1548}
1549
1550void DocumentPrivate::rotationFinished(int page, Okular::Page *okularPage)
1551{
1552 const Okular::Page *wantedPage = m_pagesVector.value(page, nullptr);
1553 if (!wantedPage || wantedPage != okularPage) {
1554 return;
1555 }
1556
1557 for (DocumentObserver *o : std::as_const(m_observers)) {
1558 o->notifyPageChanged(page, DocumentObserver::Pixmap | DocumentObserver::Annotations);
1559 }
1560}
1561
1562void DocumentPrivate::slotFontReadingProgress(int page)
1563{
1564 Q_EMIT m_parent->fontReadingProgress(page);
1565
1566 if (page >= (int)m_parent->pages() - 1) {
1567 Q_EMIT m_parent->fontReadingEnded();
1568 m_fontThread = nullptr;
1569 m_fontsCached = true;
1570 }
1571}
1572
1573void DocumentPrivate::fontReadingGotFont(const Okular::FontInfo &font)
1574{
1575 // Try to avoid duplicate fonts
1576 if (m_fontsCache.indexOf(font) == -1) {
1577 m_fontsCache.append(font);
1578
1579 Q_EMIT m_parent->gotFont(font);
1580 }
1581}
1582
1583void DocumentPrivate::slotGeneratorConfigChanged()
1584{
1585 if (!m_generator) {
1586 return;
1587 }
1588
1589 // reparse generator config and if something changed clear Pages
1590 bool configchanged = false;
1591 QHash<QString, GeneratorInfo>::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end();
1592 for (; it != itEnd; ++it) {
1593 Okular::ConfigInterface *iface = generatorConfig(it.value());
1594 if (iface) {
1595 bool it_changed = iface->reparseConfig();
1596 if (it_changed && (m_generator == it.value().generator)) {
1597 configchanged = true;
1598 }
1599 }
1600 }
1601 if (configchanged) {
1602 // invalidate pixmaps
1603 QVector<Page *>::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd();
1604 for (; it != end; ++it) {
1605 (*it)->deletePixmaps();
1606 }
1607
1608 // [MEM] remove allocation descriptors
1609 qDeleteAll(m_allocatedPixmaps);
1610 m_allocatedPixmaps.clear();
1611 m_allocatedPixmapsTotalMemory = 0;
1612
1613 // send reload signals to observers
1614 foreachObserverD(notifyContentsCleared(DocumentObserver::Pixmap));
1615 }
1616
1617 // free memory if in 'low' profile
1618 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.empty() && !m_pagesVector.isEmpty()) {
1619 cleanupPixmapMemory();
1620 }
1621}
1622
1623void DocumentPrivate::refreshPixmaps(int pageNumber)
1624{
1625 Page *page = m_pagesVector.value(pageNumber, nullptr);
1626 if (!page) {
1627 return;
1628 }
1629
1630 QMap<DocumentObserver *, PagePrivate::PixmapObject>::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd();
1631 QVector<Okular::PixmapRequest *> pixmapsToRequest;
1632 for (; it != itEnd; ++it) {
1633 const QSize size = (*it).m_pixmap->size();
1634 PixmapRequest *p = new PixmapRequest(it.key(), pageNumber, size.width(), size.height(), 1 /* dpr */, 1, PixmapRequest::Asynchronous);
1635 p->d->mForce = true;
1636 pixmapsToRequest << p;
1637 }
1638
1639 // Need to do this ↑↓ in two steps since requestPixmaps can end up calling cancelRenderingBecauseOf
1640 // which changes m_pixmaps and thus breaks the loop above
1641 for (PixmapRequest *pr : std::as_const(pixmapsToRequest)) {
1642 QList<Okular::PixmapRequest *> requestedPixmaps;
1643 requestedPixmaps.push_back(pr);
1644 m_parent->requestPixmaps(requestedPixmaps, Okular::Document::NoOption);
1645 }
1646
1647 for (DocumentObserver *observer : std::as_const(m_observers)) {
1648 QList<Okular::PixmapRequest *> requestedPixmaps;
1649
1650 TilesManager *tilesManager = page->d->tilesManager(observer);
1651 if (tilesManager) {
1652 tilesManager->markDirty();
1653
1654 PixmapRequest *p = new PixmapRequest(observer, pageNumber, tilesManager->width(), tilesManager->height(), 1 /* dpr */, 1, PixmapRequest::Asynchronous);
1655
1656 // Get the visible page rect
1657 NormalizedRect visibleRect;
1658 QVector<Okular::VisiblePageRect *>::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
1659 for (; vIt != vEnd; ++vIt) {
1660 if ((*vIt)->pageNumber == pageNumber) {
1661 visibleRect = (*vIt)->rect;
1662 break;
1663 }
1664 }
1665
1666 if (!visibleRect.isNull()) {
1667 p->setNormalizedRect(visibleRect);
1668 p->setTile(true);
1669 p->d->mForce = true;
1670 requestedPixmaps.push_back(p);
1671 } else {
1672 delete p;
1673 }
1674 }
1675
1676 m_parent->requestPixmaps(requestedPixmaps, Okular::Document::NoOption);
1677 }
1678}
1679
1680void DocumentPrivate::_o_configChanged()
1681{
1682 // free text pages if needed
1683 calculateMaxTextPages();
1684 while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) {
1685 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
1686 m_pagesVector.at(pageToKick)->setTextPage(nullptr); // deletes the textpage
1687 }
1688}
1689
1690void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct)
1691{
1692 DoContinueDirectionMatchSearchStruct *searchStruct = static_cast<DoContinueDirectionMatchSearchStruct *>(doContinueDirectionMatchSearchStruct);
1693 RunningSearch *search = m_searches.value(searchStruct->searchID);
1694
1695 if ((m_searchCancelled && !searchStruct->match) || !search) {
1696 // if the user cancelled but he just got a match, give him the match!
1698
1699 if (search) {
1700 search->isCurrentlySearching = false;
1701 }
1702
1703 Q_EMIT m_parent->searchFinished(searchStruct->searchID, Document::SearchCancelled);
1704 delete searchStruct->pagesToNotify;
1705 delete searchStruct;
1706 return;
1707 }
1708
1709 const bool forward = search->cachedType == Document::NextMatch;
1710 bool doContinue = false;
1711 // if no match found, loop through the whole doc, starting from currentPage
1712 if (!searchStruct->match) {
1713 const int pageCount = m_pagesVector.count();
1714 if (search->pagesDone < pageCount) {
1715 doContinue = true;
1716 if (searchStruct->currentPage >= pageCount) {
1717 searchStruct->currentPage = 0;
1718 Q_EMIT m_parent->notice(i18n("Continuing search from beginning"), 3000);
1719 } else if (searchStruct->currentPage < 0) {
1720 searchStruct->currentPage = pageCount - 1;
1721 Q_EMIT m_parent->notice(i18n("Continuing search from bottom"), 3000);
1722 }
1723 }
1724 }
1725
1726 if (doContinue) {
1727 // get page
1728 const Page *page = m_pagesVector[searchStruct->currentPage];
1729 // request search page if needed
1730 if (!page->hasTextPage()) {
1731 m_parent->requestTextPage(page->number());
1732 }
1733
1734 // if found a match on the current page, end the loop
1735 searchStruct->match = page->findText(searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity);
1736 if (!searchStruct->match) {
1737 if (forward) {
1738 searchStruct->currentPage++;
1739 } else {
1740 searchStruct->currentPage--;
1741 }
1742 search->pagesDone++;
1743 } else {
1744 search->pagesDone = 1;
1745 }
1746
1747 // Both of the previous if branches need to call doContinueDirectionMatchSearch
1748 QTimer::singleShot(0, m_parent, [this, searchStruct] { doContinueDirectionMatchSearch(searchStruct); });
1749 } else {
1750 doProcessSearchMatch(searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor);
1751 delete searchStruct;
1752 }
1753}
1754
1755void DocumentPrivate::doProcessSearchMatch(RegularAreaRect *match, RunningSearch *search, QSet<int> *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor &color)
1756{
1757 // reset cursor to previous shape
1759
1760 bool foundAMatch = false;
1761
1762 search->isCurrentlySearching = false;
1763
1764 // if a match has been found..
1765 if (match) {
1766 // update the RunningSearch structure adding this match..
1767 foundAMatch = true;
1768 search->continueOnPage = currentPage;
1769 search->continueOnMatch = *match;
1770 search->highlightedPages.insert(currentPage);
1771 // ..add highlight to the page..
1772 m_pagesVector[currentPage]->d->setHighlight(searchID, match, color);
1773
1774 // ..queue page for notifying changes..
1775 pagesToNotify->insert(currentPage);
1776
1777 // Create a normalized rectangle around the search match that includes a 5% buffer on all sides.
1778 const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect(match->first().left - 0.05, match->first().top - 0.05, match->first().right + 0.05, match->first().bottom + 0.05);
1779
1780 const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible(matchRectWithBuffer, currentPage);
1781
1782 // ..move the viewport to show the first of the searched word sequence centered
1783 if (moveViewport && !matchRectFullyVisible) {
1784 DocumentViewport searchViewport(currentPage);
1785 searchViewport.rePos.enabled = true;
1786 searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0;
1787 searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0;
1788 m_parent->setViewport(searchViewport, nullptr, true);
1789 }
1790 delete match;
1791 }
1792
1793 // notify observers about highlights changes
1794 for (int pageNumber : std::as_const(*pagesToNotify)) {
1795 for (DocumentObserver *observer : std::as_const(m_observers)) {
1796 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
1797 }
1798 }
1799
1800 if (foundAMatch) {
1801 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1802 } else {
1803 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1804 }
1805
1806 delete pagesToNotify;
1807}
1808
1809void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID)
1810{
1811 QMap<Page *, QVector<RegularAreaRect *>> *pageMatches = static_cast<QMap<Page *, QVector<RegularAreaRect *>> *>(pageMatchesMap);
1812 QSet<int> *pagesToNotify = static_cast<QSet<int> *>(pagesToNotifySet);
1813 RunningSearch *search = m_searches.value(searchID);
1814
1815 if (m_searchCancelled || !search) {
1816 typedef QVector<RegularAreaRect *> MatchesVector;
1817
1819
1820 if (search) {
1821 search->isCurrentlySearching = false;
1822 }
1823
1824 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1825 for (const MatchesVector &mv : std::as_const(*pageMatches)) {
1826 qDeleteAll(mv);
1827 }
1828 delete pageMatches;
1829 delete pagesToNotify;
1830 return;
1831 }
1832
1833 if (currentPage < m_pagesVector.count()) {
1834 // get page (from the first to the last)
1835 Page *page = m_pagesVector.at(currentPage);
1836 int pageNumber = page->number(); // redundant? is it == currentPage ?
1837
1838 // request search page if needed
1839 if (!page->hasTextPage()) {
1840 m_parent->requestTextPage(pageNumber);
1841 }
1842
1843 // loop on a page adding highlights for all found items
1844 RegularAreaRect *lastMatch = nullptr;
1845 while (true) {
1846 if (lastMatch) {
1847 lastMatch = page->findText(searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch);
1848 } else {
1849 lastMatch = page->findText(searchID, search->cachedString, FromTop, search->cachedCaseSensitivity);
1850 }
1851
1852 if (!lastMatch) {
1853 break;
1854 }
1855
1856 // add highlight rect to the matches map
1857 (*pageMatches)[page].append(lastMatch);
1858 }
1859 delete lastMatch;
1860
1861 QTimer::singleShot(0, m_parent, [this, pagesToNotifySet, pageMatches, currentPage, searchID] { doContinueAllDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID); });
1862 } else {
1863 // reset cursor to previous shape
1865
1866 search->isCurrentlySearching = false;
1867 bool foundAMatch = pageMatches->count() != 0;
1868 QMap<Page *, QVector<RegularAreaRect *>>::const_iterator it, itEnd;
1869 it = pageMatches->constBegin();
1870 itEnd = pageMatches->constEnd();
1871 for (; it != itEnd; ++it) {
1872 for (RegularAreaRect *match : it.value()) {
1873 it.key()->d->setHighlight(searchID, match, search->cachedColor);
1874 delete match;
1875 }
1876 search->highlightedPages.insert(it.key()->number());
1877 pagesToNotify->insert(it.key()->number());
1878 }
1879
1880 for (DocumentObserver *observer : std::as_const(m_observers)) {
1881 observer->notifySetup(m_pagesVector, 0);
1882 }
1883
1884 // notify observers about highlights changes
1885 for (int pageNumber : std::as_const(*pagesToNotify)) {
1886 for (DocumentObserver *observer : std::as_const(m_observers)) {
1887 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
1888 }
1889 }
1890
1891 if (foundAMatch) {
1892 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1893 } else {
1894 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1895 }
1896
1897 delete pageMatches;
1898 delete pagesToNotify;
1899 }
1900}
1901
1902void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList &words)
1903{
1904 typedef QPair<RegularAreaRect *, QColor> MatchColor;
1905 QMap<Page *, QVector<MatchColor>> *pageMatches = static_cast<QMap<Page *, QVector<MatchColor>> *>(pageMatchesMap);
1906 QSet<int> *pagesToNotify = static_cast<QSet<int> *>(pagesToNotifySet);
1907 RunningSearch *search = m_searches.value(searchID);
1908
1909 if (m_searchCancelled || !search) {
1910 typedef QVector<MatchColor> MatchesVector;
1911
1913
1914 if (search) {
1915 search->isCurrentlySearching = false;
1916 }
1917
1918 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1919
1920 for (const MatchesVector &mv : std::as_const(*pageMatches)) {
1921 for (const MatchColor &mc : mv) {
1922 delete mc.first;
1923 }
1924 }
1925 delete pageMatches;
1926 delete pagesToNotify;
1927 return;
1928 }
1929
1930 const int wordCount = words.count();
1931 const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
1932 int baseHue, baseSat, baseVal;
1933 search->cachedColor.getHsv(&baseHue, &baseSat, &baseVal);
1934
1935 if (currentPage < m_pagesVector.count()) {
1936 // get page (from the first to the last)
1937 Page *page = m_pagesVector.at(currentPage);
1938 int pageNumber = page->number(); // redundant? is it == currentPage ?
1939
1940 // request search page if needed
1941 if (!page->hasTextPage()) {
1942 m_parent->requestTextPage(pageNumber);
1943 }
1944
1945 // loop on a page adding highlights for all found items
1946 bool allMatched = wordCount > 0, anyMatched = false;
1947 for (int w = 0; w < wordCount; w++) {
1948 const QString &word = words[w];
1949 int newHue = baseHue - w * hueStep;
1950 if (newHue < 0) {
1951 newHue += 360;
1952 }
1953 QColor wordColor = QColor::fromHsv(newHue, baseSat, baseVal);
1954 RegularAreaRect *lastMatch = nullptr;
1955 // add all highlights for current word
1956 bool wordMatched = false;
1957 while (true) {
1958 if (lastMatch) {
1959 lastMatch = page->findText(searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch);
1960 } else {
1961 lastMatch = page->findText(searchID, word, FromTop, search->cachedCaseSensitivity);
1962 }
1963
1964 if (!lastMatch) {
1965 break;
1966 }
1967
1968 // add highligh rect to the matches map
1969 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
1970 wordMatched = true;
1971 }
1972 allMatched = allMatched && wordMatched;
1973 anyMatched = anyMatched || wordMatched;
1974 }
1975
1976 // if not all words are present in page, remove partial highlights
1977 const bool matchAll = search->cachedType == Document::GoogleAll;
1978 if (!allMatched && matchAll) {
1979 const QVector<MatchColor> &matches = (*pageMatches)[page];
1980 for (const MatchColor &mc : matches) {
1981 delete mc.first;
1982 }
1983 pageMatches->remove(page);
1984 }
1985
1986 QTimer::singleShot(0, m_parent, [this, pagesToNotifySet, pageMatches, currentPage, searchID, words] { doContinueGooglesDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID, words); });
1987 } else {
1988 // reset cursor to previous shape
1990
1991 search->isCurrentlySearching = false;
1992 bool foundAMatch = pageMatches->count() != 0;
1993 QMap<Page *, QVector<MatchColor>>::const_iterator it, itEnd;
1994 it = pageMatches->constBegin();
1995 itEnd = pageMatches->constEnd();
1996 for (; it != itEnd; ++it) {
1997 for (const MatchColor &mc : it.value()) {
1998 it.key()->d->setHighlight(searchID, mc.first, mc.second);
1999 delete mc.first;
2000 }
2001 search->highlightedPages.insert(it.key()->number());
2002 pagesToNotify->insert(it.key()->number());
2003 }
2004
2005 // send page lists to update observers (since some filter on bookmarks)
2006 for (DocumentObserver *observer : std::as_const(m_observers)) {
2007 observer->notifySetup(m_pagesVector, 0);
2008 }
2009
2010 // notify observers about highlights changes
2011 for (int pageNumber : std::as_const(*pagesToNotify)) {
2012 for (DocumentObserver *observer : std::as_const(m_observers)) {
2013 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
2014 }
2015 }
2016
2017 if (foundAMatch) {
2018 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
2019 } else {
2020 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
2021 }
2022
2023 delete pageMatches;
2024 delete pagesToNotify;
2025 }
2026}
2027
2028QVariant DocumentPrivate::documentMetaData(const Generator::DocumentMetaDataKey key, const QVariant &option) const
2029{
2030 switch (key) {
2032 bool giveDefault = option.toBool();
2033 QColor color;
2034 if ((SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper) && SettingsCore::changeColors()) {
2035 color = SettingsCore::paperColor();
2036 } else if (giveDefault) {
2037 color = Qt::white;
2038 }
2039 return color;
2040 } break;
2041
2043 switch (SettingsCore::textAntialias()) {
2044 case SettingsCore::EnumTextAntialias::Enabled:
2045 return true;
2046 break;
2047 case SettingsCore::EnumTextAntialias::Disabled:
2048 return false;
2049 break;
2050 }
2051 break;
2052
2054 switch (SettingsCore::graphicsAntialias()) {
2055 case SettingsCore::EnumGraphicsAntialias::Enabled:
2056 return true;
2057 break;
2058 case SettingsCore::EnumGraphicsAntialias::Disabled:
2059 return false;
2060 break;
2061 }
2062 break;
2063
2065 switch (SettingsCore::textHinting()) {
2066 case SettingsCore::EnumTextHinting::Enabled:
2067 return true;
2068 break;
2069 case SettingsCore::EnumTextHinting::Disabled:
2070 return false;
2071 break;
2072 }
2073 break;
2074 }
2075 return QVariant();
2076}
2077
2078bool DocumentPrivate::isNormalizedRectangleFullyVisible(const Okular::NormalizedRect &rectOfInterest, int rectPage)
2079{
2080 bool rectFullyVisible = false;
2081 const QVector<Okular::VisiblePageRect *> &visibleRects = m_parent->visiblePageRects();
2084
2085 for (; (vIt != vEnd) && !rectFullyVisible; ++vIt) {
2086 if ((*vIt)->pageNumber == rectPage && (*vIt)->rect.contains(rectOfInterest.left, rectOfInterest.top) && (*vIt)->rect.contains(rectOfInterest.right, rectOfInterest.bottom)) {
2087 rectFullyVisible = true;
2088 }
2089 }
2090 return rectFullyVisible;
2091}
2092
2093struct pdfsyncpoint {
2094 QString file;
2095 qlonglong x;
2096 qlonglong y;
2097 int row;
2098 int column;
2099 int page;
2100};
2101
2102void DocumentPrivate::loadSyncFile(const QString &filePath)
2103{
2104 QFile f(filePath + QLatin1String("sync"));
2105 if (!f.open(QIODevice::ReadOnly)) {
2106 return;
2107 }
2108
2109 QTextStream ts(&f);
2110 // first row: core name of the pdf output
2111 const QString coreName = ts.readLine();
2112 // second row: version string, in the form 'Version %u'
2113 const QString versionstr = ts.readLine();
2114 // anchor the pattern with \A and \z to match the entire subject string
2115 // TODO: with Qt 5.12 QRegularExpression::anchoredPattern() can be used instead
2116 static QRegularExpression versionre(QStringLiteral("\\AVersion \\d+\\z"), QRegularExpression::CaseInsensitiveOption);
2117 QRegularExpressionMatch match = versionre.match(versionstr);
2118 if (!match.hasMatch()) {
2119 return;
2120 }
2121
2123 QStack<QString> fileStack;
2124 int currentpage = -1;
2125 const QLatin1String texStr(".tex");
2126 const QChar spaceChar = QChar::fromLatin1(' ');
2127
2128 fileStack.push(coreName + texStr);
2129
2130 const QSizeF dpi = m_generator->dpi();
2131
2132 QString line;
2133 while (!ts.atEnd()) {
2134 line = ts.readLine();
2135 const QStringList tokens = line.split(spaceChar, Qt::SkipEmptyParts);
2136 const int tokenSize = tokens.count();
2137 if (tokenSize < 1) {
2138 continue;
2139 }
2140 if (tokens.first() == QLatin1String("l") && tokenSize >= 3) {
2141 int id = tokens.at(1).toInt();
2143 if (it == points.constEnd()) {
2144 pdfsyncpoint pt;
2145 pt.x = 0;
2146 pt.y = 0;
2147 pt.row = tokens.at(2).toInt();
2148 pt.column = 0; // TODO
2149 pt.page = -1;
2150 pt.file = fileStack.top();
2151 points[id] = pt;
2152 }
2153 } else if (tokens.first() == QLatin1String("s") && tokenSize >= 2) {
2154 currentpage = tokens.at(1).toInt() - 1;
2155 } else if (tokens.first() == QLatin1String("p*") && tokenSize >= 4) {
2156 // TODO
2157 qCDebug(OkularCoreDebug) << "PdfSync: 'p*' line ignored";
2158 } else if (tokens.first() == QLatin1String("p") && tokenSize >= 4) {
2159 int id = tokens.at(1).toInt();
2161 if (it != points.end()) {
2162 it->x = tokens.at(2).toInt();
2163 it->y = tokens.at(3).toInt();
2164 it->page = currentpage;
2165 }
2166 } else if (line.startsWith(QLatin1Char('(')) && tokenSize == 1) {
2167 QString newfile = line;
2168 // chop the leading '('
2169 newfile.remove(0, 1);
2170 if (!newfile.endsWith(texStr)) {
2171 newfile += texStr;
2172 }
2173 fileStack.push(newfile);
2174 } else if (line == QLatin1String(")")) {
2175 if (!fileStack.isEmpty()) {
2176 fileStack.pop();
2177 } else {
2178 qCDebug(OkularCoreDebug) << "PdfSync: going one level down too much";
2179 }
2180 } else {
2181 qCDebug(OkularCoreDebug).nospace() << "PdfSync: unknown line format: '" << line << "'";
2182 }
2183 }
2184
2185 QVector<QList<Okular::SourceRefObjectRect *>> refRects(m_pagesVector.size());
2186 for (const pdfsyncpoint &pt : std::as_const(points)) {
2187 // drop pdfsync points not completely valid
2188 if (pt.page < 0 || pt.page >= m_pagesVector.size()) {
2189 continue;
2190 }
2191
2192 // magic numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels
2193 Okular::NormalizedPoint p((pt.x * dpi.width()) / (72.27 * 65536.0 * m_pagesVector[pt.page]->width()), (pt.y * dpi.height()) / (72.27 * 65536.0 * m_pagesVector[pt.page]->height()));
2194 QString file = pt.file;
2195 Okular::SourceReference *sourceRef = new Okular::SourceReference(file, pt.row, pt.column);
2196 refRects[pt.page].append(new Okular::SourceRefObjectRect(p, sourceRef));
2197 }
2198 for (int i = 0; i < refRects.size(); ++i) {
2199 if (!refRects.at(i).isEmpty()) {
2200 m_pagesVector[i]->setSourceReferences(refRects.at(i));
2201 }
2202 }
2203}
2204
2205void DocumentPrivate::clearAndWaitForRequests()
2206{
2207 m_pixmapRequestsMutex.lock();
2208 std::list<PixmapRequest *>::const_iterator sIt = m_pixmapRequestsStack.begin();
2209 std::list<PixmapRequest *>::const_iterator sEnd = m_pixmapRequestsStack.end();
2210 for (; sIt != sEnd; ++sIt) {
2211 delete *sIt;
2212 }
2213 m_pixmapRequestsStack.clear();
2214 m_pixmapRequestsMutex.unlock();
2215
2216 QEventLoop loop;
2217 bool startEventLoop = false;
2218 do {
2219 m_pixmapRequestsMutex.lock();
2220 startEventLoop = !m_executingPixmapRequests.empty();
2221
2222 if (m_generator->hasFeature(Generator::SupportsCancelling)) {
2223 for (PixmapRequest *executingRequest : std::as_const(m_executingPixmapRequests)) {
2224 executingRequest->d->mShouldAbortRender = 1;
2225 }
2226
2227 if (m_generator->d_ptr->mTextPageGenerationThread) {
2228 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
2229 }
2230 }
2231
2232 m_pixmapRequestsMutex.unlock();
2233 if (startEventLoop) {
2234 m_closingLoop = &loop;
2235 loop.exec();
2236 m_closingLoop = nullptr;
2237 }
2238 } while (startEventLoop);
2239}
2240
2241int DocumentPrivate::findFieldPageNumber(Okular::FormField *field)
2242{
2243 // Lookup the page of the FormField
2244 int foundPage = -1;
2245 for (uint pageIdx = 0, nPages = m_parent->pages(); pageIdx < nPages; pageIdx++) {
2246 const Page *p = m_parent->page(pageIdx);
2247 if (p && p->formFields().contains(field)) {
2248 foundPage = static_cast<int>(pageIdx);
2249 break;
2250 }
2251 }
2252 return foundPage;
2253}
2254
2255void DocumentPrivate::executeScriptEvent(const std::shared_ptr<Event> &event, const Okular::ScriptAction *linkscript)
2256{
2257 if (!m_scripter) {
2258 m_scripter = new Scripter(this);
2259 }
2260 m_scripter->setEvent(event.get());
2261 m_scripter->execute(linkscript->scriptType(), linkscript->script());
2262
2263 // Clear out the event after execution
2264 m_scripter->setEvent(nullptr);
2265}
2266
2268 : QObject(nullptr)
2269 , d(new DocumentPrivate(this))
2270{
2271 d->m_widget = widget;
2272 d->m_bookmarkManager = new BookmarkManager(d);
2273 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(), DocumentViewport());
2274 d->m_undoStack = new QUndoStack(this);
2275
2276 connect(SettingsCore::self(), &SettingsCore::configChanged, this, [this] { d->_o_configChanged(); });
2280
2281 qRegisterMetaType<Okular::FontInfo>();
2282}
2283
2285{
2286 // delete generator, pages, and related stuff
2287 closeDocument();
2288
2289 QSet<View *>::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd();
2290 for (; viewIt != viewEnd; ++viewIt) {
2291 View *v = *viewIt;
2292 v->d_func()->document = nullptr;
2293 }
2294
2295 // delete the bookmark manager
2296 delete d->m_bookmarkManager;
2297
2298 // delete the loaded generators
2299 QHash<QString, GeneratorInfo>::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd();
2300 for (; it != itEnd; ++it) {
2301 d->unloadGenerator(it.value());
2302 }
2303 d->m_loadedGenerators.clear();
2304
2305 // delete the private structure
2306 delete d;
2307}
2308
2309QString DocumentPrivate::docDataFileName(const QUrl &url, qint64 document_size)
2310{
2311 QString fn = url.fileName();
2312 fn = QString::number(document_size) + QLatin1Char('.') + fn + QStringLiteral(".xml");
2313 QString docdataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/okular/docdata");
2314 // make sure that the okular/docdata/ directory exists (probably this used to be handled by KStandardDirs)
2315 if (!QFileInfo::exists(docdataDir)) {
2316 qCDebug(OkularCoreDebug) << "creating docdata folder" << docdataDir;
2317 QDir().mkpath(docdataDir);
2318 }
2319 QString newokularfile = docdataDir + QLatin1Char('/') + fn;
2320
2321 return newokularfile;
2322}
2323
2324QVector<KPluginMetaData> DocumentPrivate::availableGenerators()
2325{
2326 static QVector<KPluginMetaData> result;
2327 if (result.isEmpty()) {
2328 result = KPluginMetaData::findPlugins(QStringLiteral("okular_generators"));
2329 }
2330 return result;
2331}
2332
2333KPluginMetaData DocumentPrivate::generatorForMimeType(const QMimeType &type, QWidget *widget, const QVector<KPluginMetaData> &triedOffers)
2334{
2335 // First try to find an exact match, and then look for more general ones (e. g. the plain text one)
2336 // Ideally we would rank these by "closeness", but that might be overdoing it
2337
2338 const QVector<KPluginMetaData> available = availableGenerators();
2339 QVector<KPluginMetaData> offers;
2340 QVector<KPluginMetaData> exactMatches;
2341
2342 QMimeDatabase mimeDatabase;
2343
2344 for (const KPluginMetaData &md : available) {
2345 if (triedOffers.contains(md)) {
2346 continue;
2347 }
2348
2349 const QStringList mimetypes = md.mimeTypes();
2350 for (const QString &supported : mimetypes) {
2351 QMimeType mimeType = mimeDatabase.mimeTypeForName(supported);
2352 if (mimeType == type && !exactMatches.contains(md)) {
2353 exactMatches << md;
2354 }
2355
2356 if (type.inherits(supported) && !offers.contains(md)) {
2357 offers << md;
2358 }
2359 }
2360 }
2361
2362 if (!exactMatches.isEmpty()) {
2363 offers = exactMatches;
2364 }
2365
2366 if (offers.isEmpty()) {
2367 return KPluginMetaData();
2368 }
2369 int hRank = 0;
2370 // best ranked offer search
2371 int offercount = offers.size();
2372 if (offercount > 1) {
2373 // sort the offers: the offers with an higher priority come before
2374 auto cmp = [](const KPluginMetaData &s1, const KPluginMetaData &s2) {
2375 const QString property = QStringLiteral("X-KDE-Priority");
2376 return s1.rawData().value(property).toInt() > s2.rawData().value(property).toInt();
2377 };
2378 std::stable_sort(offers.begin(), offers.end(), cmp);
2379
2380 if (SettingsCore::chooseGenerators()) {
2381 QStringList list;
2382 for (int i = 0; i < offercount; ++i) {
2383 list << offers.at(i).pluginId();
2384 }
2385 ChooseEngineDialog choose(list, type, widget);
2386
2387 if (choose.exec() == QDialog::Rejected) {
2388 return KPluginMetaData();
2389 }
2390
2391 hRank = choose.selectedGenerator();
2392 }
2393 }
2394 Q_ASSERT(hRank < offers.size());
2395 return offers.at(hRank);
2396}
2397
2398Document::OpenResult Document::openDocument(const QString &docFile, const QUrl &url, const QMimeType &_mime, const QString &password)
2399{
2400 QMimeDatabase db;
2401 QMimeType mime = _mime;
2402 QByteArray filedata;
2403 int fd = -1;
2404 if (url.scheme() == QLatin1String("fd")) {
2405 bool ok;
2406 fd = QStringView {url.path()}.mid(1).toInt(&ok);
2407 if (!ok) {
2408 return OpenError;
2409 }
2410 } else if (url.fileName() == QLatin1String("-")) {
2411 fd = 0;
2412 }
2413 bool triedMimeFromFileContent = false;
2414 if (fd < 0) {
2415 if (!mime.isValid()) {
2416 return OpenError;
2417 }
2418
2419 d->m_url = url;
2420 d->m_docFileName = docFile;
2421
2422 if (!d->updateMetadataXmlNameAndDocSize()) {
2423 return OpenError;
2424 }
2425 } else {
2426 QFile qstdin;
2427 const bool ret = qstdin.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle);
2428 if (!ret) {
2429 qWarning() << "failed to read" << url << filedata;
2430 return OpenError;
2431 }
2432
2433 filedata = qstdin.readAll();
2434 mime = db.mimeTypeForData(filedata);
2435 if (!mime.isValid() || mime.isDefault()) {
2436 return OpenError;
2437 }
2438 d->m_docSize = filedata.size();
2439 triedMimeFromFileContent = true;
2440 }
2441
2442 const bool fromFileDescriptor = fd >= 0;
2443
2444 // 0. load Generator
2445 // request only valid non-disabled plugins suitable for the mimetype
2446 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2447 if (!offer.isValid() && !triedMimeFromFileContent) {
2449 triedMimeFromFileContent = true;
2450 if (newmime != mime) {
2451 mime = newmime;
2452 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2453 }
2454 if (!offer.isValid()) {
2455 // There's still no offers, do a final mime search based on the filename
2456 // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we
2457 // use is the one fed by the server, that may be wrong
2458 newmime = db.mimeTypeForUrl(url);
2459
2460 if (!newmime.isDefault() && newmime != mime) {
2461 mime = newmime;
2462 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2463 }
2464 }
2465 }
2466 if (!offer.isValid()) {
2467 d->m_openError = i18n("Can not find a plugin which is able to handle the document being passed.");
2468 Q_EMIT error(d->m_openError, -1);
2469 qCWarning(OkularCoreDebug).nospace() << "No plugin for mimetype '" << mime.name() << "'.";
2470 return OpenError;
2471 }
2472
2473 // 1. load Document
2474 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2475 if (openResult == OpenError) {
2476 QVector<KPluginMetaData> triedOffers;
2477 triedOffers << offer;
2478 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2479
2480 while (offer.isValid()) {
2481 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2482
2483 if (openResult == OpenError) {
2484 triedOffers << offer;
2485 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2486 } else {
2487 break;
2488 }
2489 }
2490
2491 if (openResult == OpenError && !triedMimeFromFileContent) {
2493 triedMimeFromFileContent = true;
2494 if (newmime != mime) {
2495 mime = newmime;
2496 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2497 while (offer.isValid()) {
2498 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2499
2500 if (openResult == OpenError) {
2501 triedOffers << offer;
2502 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2503 } else {
2504 break;
2505 }
2506 }
2507 }
2508 }
2509
2510 if (openResult == OpenSuccess) {
2511 // Clear errors, since we're trying various generators, maybe one of them errored out
2512 // but we finally succeeded
2513 // TODO one can still see the error message animating out but since this is a very rare
2514 // condition we can leave this for future work
2515 Q_EMIT error(QString(), -1);
2516 }
2517 }
2518 if (openResult != OpenSuccess) {
2519 return openResult;
2520 }
2521
2522 // no need to check for the existence of a synctex file, no parser will be
2523 // created if none exists
2524 d->m_synctex_scanner = synctex_scanner_new_with_output_file(QFile::encodeName(docFile).constData(), nullptr, 1);
2525 if (!d->m_synctex_scanner && QFile::exists(docFile + QLatin1String("sync"))) {
2526 d->loadSyncFile(docFile);
2527 }
2528
2529 d->m_generatorName = offer.pluginId();
2530 d->m_pageController = new PageController();
2531 connect(d->m_pageController, &PageController::rotationFinished, this, [this](int p, Okular::Page *op) { d->rotationFinished(p, op); });
2532
2533 for (Page *p : std::as_const(d->m_pagesVector)) {
2534 p->d->m_doc = d;
2535 }
2536
2537 d->m_docdataMigrationNeeded = false;
2538
2539 // 2. load Additional Data (bookmarks, local annotations and metadata) about the document
2540 if (d->m_archiveData) {
2541 // QTemporaryFile is weird and will return false in exists if fileName wasn't called before
2542 d->m_archiveData->metadataFile.fileName();
2543 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2544 d->loadDocumentInfo(LoadGeneralInfo);
2545 } else {
2546 if (d->loadDocumentInfo(LoadPageInfo)) {
2547 d->m_docdataMigrationNeeded = true;
2548 }
2549 d->loadDocumentInfo(LoadGeneralInfo);
2550 }
2551
2552 d->m_bookmarkManager->setUrl(d->m_url);
2553
2554 // 3. setup observers internal lists and data
2555 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged));
2556
2557 // 4. set initial page (restoring the page saved in xml if loaded)
2558 DocumentViewport loadedViewport = (*d->m_viewportIterator);
2559 if (loadedViewport.isValid()) {
2560 (*d->m_viewportIterator) = DocumentViewport();
2561 if (loadedViewport.pageNumber >= (int)d->m_pagesVector.size()) {
2562 loadedViewport.pageNumber = d->m_pagesVector.size() - 1;
2563 }
2564 } else {
2565 loadedViewport.pageNumber = 0;
2566 }
2567 setViewport(loadedViewport);
2568
2569 // start bookmark saver timer
2570 if (!d->m_saveBookmarksTimer) {
2571 d->m_saveBookmarksTimer = new QTimer(this);
2572 connect(d->m_saveBookmarksTimer, &QTimer::timeout, this, [this] { d->saveDocumentInfo(); });
2573 }
2574 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2575
2576 // start memory check timer
2577 if (!d->m_memCheckTimer) {
2578 d->m_memCheckTimer = new QTimer(this);
2579 connect(d->m_memCheckTimer, &QTimer::timeout, this, [this] { d->slotTimedMemoryCheck(); });
2580 }
2581 d->m_memCheckTimer->start(kMemCheckTime);
2582
2583 const DocumentViewport nextViewport = d->nextDocumentViewport();
2584 if (nextViewport.isValid()) {
2585 setViewport(nextViewport);
2586 d->m_nextDocumentViewport = DocumentViewport();
2587 d->m_nextDocumentDestination = QString();
2588 }
2589
2590 AudioPlayer::instance()->setDocument(fromFileDescriptor ? QUrl() : d->m_url, this);
2591
2592 const QStringList docScripts = d->m_generator->metaData(QStringLiteral("DocumentScripts"), QStringLiteral("JavaScript")).toStringList();
2593 if (!docScripts.isEmpty()) {
2594 d->m_scripter = new Scripter(d);
2595 for (const QString &docscript : docScripts) {
2596 const Okular::ScriptAction *linkScript = new Okular::ScriptAction(Okular::JavaScript, docscript);
2597 std::shared_ptr<Event> event = Event::createDocEvent(Event::DocOpen);
2598 d->executeScriptEvent(event, linkScript);
2599 }
2600 }
2601
2602 return OpenSuccess;
2603}
2604
2605bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2606{
2607 // m_docFileName is always local so we can use QFileInfo on it
2608 QFileInfo fileReadTest(m_docFileName);
2609 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2610 return false;
2611 }
2612
2613 m_docSize = fileReadTest.size();
2614
2615 // determine the related "xml document-info" filename
2616 if (m_url.isLocalFile()) {
2617 const QString filePath = docDataFileName(m_url, m_docSize);
2618 qCDebug(OkularCoreDebug) << "Metadata file is now:" << filePath;
2619 m_xmlFileName = filePath;
2620 } else {
2621 qCDebug(OkularCoreDebug) << "Metadata file: disabled";
2622 m_xmlFileName = QString();
2623 }
2624
2625 return true;
2626}
2627
2629{
2630 if (d->m_generator) {
2632 if (iface) {
2633 return iface->guiClient();
2634 }
2635 }
2636 return nullptr;
2637}
2638
2640{
2641 // check if there's anything to close...
2642 if (!d->m_generator) {
2643 return;
2644 }
2645
2646 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(CloseDocument)) {
2647 processDocumentAction(action, CloseDocument);
2648 }
2649
2651
2652 delete d->m_pageController;
2653 d->m_pageController = nullptr;
2654
2655 delete d->m_scripter;
2656 d->m_scripter = nullptr;
2657
2658 // remove requests left in queue
2659 d->clearAndWaitForRequests();
2660
2661 if (d->m_fontThread) {
2662 disconnect(d->m_fontThread, nullptr, this, nullptr);
2663 d->m_fontThread->stopExtraction();
2664 d->m_fontThread->wait();
2665 d->m_fontThread = nullptr;
2666 }
2667
2668 // stop any audio playback
2670
2671 // close the current document and save document info if a document is still opened
2672 if (d->m_generator && d->m_pagesVector.size() > 0) {
2673 d->saveDocumentInfo();
2674
2675 // free the content of the opaque backend actions (if any)
2676 // this is a bit awkward since backends can store "random stuff" in the
2677 // BackendOpaqueAction nativeId qvariant so we need to tell them to free it
2678 // ideally we would just do that in the BackendOpaqueAction destructor
2679 // but that's too late in the cleanup process, i.e. the generator has already closed its document
2680 // and the document generator is nullptr
2681 for (const Page *p : std::as_const(d->m_pagesVector)) {
2682 const QList<ObjectRect *> &oRects = p->objectRects();
2683 for (const ObjectRect *oRect : oRects) {
2684 if (oRect->objectType() == ObjectRect::Action) {
2685 const Action *a = static_cast<const Action *>(oRect->object());
2686 const BackendOpaqueAction *backendAction = dynamic_cast<const BackendOpaqueAction *>(a);
2687 if (backendAction) {
2688 d->m_generator->freeOpaqueActionContents(*backendAction);
2689 }
2690 }
2691 }
2692
2693 const QList<FormField *> forms = p->formFields();
2694 for (const FormField *form : forms) {
2695 const QList<Action *> additionalActions = form->additionalActions();
2696 for (const Action *a : additionalActions) {
2697 const BackendOpaqueAction *backendAction = dynamic_cast<const BackendOpaqueAction *>(a);
2698 if (backendAction) {
2699 d->m_generator->freeOpaqueActionContents(*backendAction);
2700 }
2701 }
2702 }
2703 }
2704
2705 d->m_generator->closeDocument();
2706 }
2707
2708 if (d->m_synctex_scanner) {
2709 synctex_scanner_free(d->m_synctex_scanner);
2710 d->m_synctex_scanner = nullptr;
2711 }
2712
2713 // stop timers
2714 if (d->m_memCheckTimer) {
2715 d->m_memCheckTimer->stop();
2716 }
2717 if (d->m_saveBookmarksTimer) {
2718 d->m_saveBookmarksTimer->stop();
2719 }
2720
2721 if (d->m_generator) {
2722 // disconnect the generator from this document ...
2723 d->m_generator->d_func()->m_document = nullptr;
2724 // .. and this document from the generator signals
2725 disconnect(d->m_generator, nullptr, this, nullptr);
2726
2727 QHash<QString, GeneratorInfo>::const_iterator genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
2728 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2729 }
2730 d->m_generator = nullptr;
2731 d->m_generatorName = QString();
2732 d->m_url = QUrl();
2733 d->m_walletGenerator = nullptr;
2734 d->m_docFileName = QString();
2735 d->m_xmlFileName = QString();
2736 delete d->m_tempFile;
2737 d->m_tempFile = nullptr;
2738 delete d->m_archiveData;
2739 d->m_archiveData = nullptr;
2740 d->m_docSize = -1;
2741 d->m_exportCached = false;
2742 d->m_exportFormats.clear();
2743 d->m_exportToText = ExportFormat();
2744 d->m_fontsCached = false;
2745 d->m_fontsCache.clear();
2746 d->m_rotation = Rotation0;
2747
2748 // send an empty list to observers (to free their data)
2750
2751 // delete pages and clear 'd->m_pagesVector' container
2752 QVector<Page *>::const_iterator pIt = d->m_pagesVector.constBegin();
2753 QVector<Page *>::const_iterator pEnd = d->m_pagesVector.constEnd();
2754 for (; pIt != pEnd; ++pIt) {
2755 delete *pIt;
2756 }
2757 d->m_pagesVector.clear();
2758
2759 // clear 'memory allocation' descriptors
2760 qDeleteAll(d->m_allocatedPixmaps);
2761 d->m_allocatedPixmaps.clear();
2762
2763 // clear 'running searches' descriptors
2764 QMap<int, RunningSearch *>::const_iterator rIt = d->m_searches.constBegin();
2765 QMap<int, RunningSearch *>::const_iterator rEnd = d->m_searches.constEnd();
2766 for (; rIt != rEnd; ++rIt) {
2767 delete *rIt;
2768 }
2769 d->m_searches.clear();
2770
2771 // clear the visible areas and notify the observers
2772 QVector<VisiblePageRect *>::const_iterator vIt = d->m_pageRects.constBegin();
2773 QVector<VisiblePageRect *>::const_iterator vEnd = d->m_pageRects.constEnd();
2774 for (; vIt != vEnd; ++vIt) {
2775 delete *vIt;
2776 }
2777 d->m_pageRects.clear();
2778 foreachObserver(notifyVisibleRectsChanged());
2779
2780 // reset internal variables
2781
2782 d->m_viewportHistory.clear();
2783 d->m_viewportHistory.emplace_back();
2784 d->m_viewportIterator = d->m_viewportHistory.begin();
2785 d->m_allocatedPixmapsTotalMemory = 0;
2786 d->m_allocatedTextPagesFifo.clear();
2787 d->m_pageSize = PageSize();
2788 d->m_pageSizes.clear();
2789
2790 d->m_documentInfo = DocumentInfo();
2791 d->m_documentInfoAskedKeys.clear();
2792
2793 AudioPlayer::instance()->resetDocument();
2794
2795 d->m_undoStack->clear();
2796 d->m_docdataMigrationNeeded = false;
2797
2798#if HAVE_MALLOC_TRIM
2799 // trim unused memory, glibc should do this but it seems it does not
2800 // this can greatly decrease the [perceived] memory consumption of okular
2801 // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827
2802 malloc_trim(0);
2803#endif
2804}
2805
2807{
2808 Q_ASSERT(!d->m_observers.contains(pObserver));
2809 d->m_observers << pObserver;
2810
2811 // if the observer is added while a document is already opened, tell it
2812 if (!d->m_pagesVector.isEmpty()) {
2814 pObserver->notifyViewportChanged(false /*disables smoothMove*/);
2815 }
2816}
2817
2819{
2820 // remove observer from the set. it won't receive notifications anymore
2821 if (d->m_observers.contains(pObserver)) {
2822 // free observer's pixmap data
2823 QVector<Page *>::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd();
2824 for (; it != end; ++it) {
2825 (*it)->deletePixmap(pObserver);
2826 }
2827
2828 // [MEM] free observer's allocation descriptors
2829 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2830 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2831 while (aIt != aEnd) {
2832 AllocatedPixmap *p = *aIt;
2833 if (p->observer == pObserver) {
2834 aIt = d->m_allocatedPixmaps.erase(aIt);
2835 delete p;
2836 } else {
2837 ++aIt;
2838 }
2839 }
2840
2841 for (PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
2842 if (executingRequest->observer() == pObserver) {
2843 d->cancelRenderingBecauseOf(executingRequest, nullptr);
2844 }
2845 }
2846
2847 // remove observer entry from the set
2848 d->m_observers.remove(pObserver);
2849 }
2850}
2851
2853{
2854 // reparse generator config and if something changed clear Pages
2855 bool configchanged = false;
2856 if (d->m_generator) {
2858 if (iface) {
2859 configchanged = iface->reparseConfig();
2860 }
2861 }
2862 if (configchanged) {
2863 // invalidate pixmaps
2864 QVector<Page *>::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd();
2865 for (; it != end; ++it) {
2866 (*it)->deletePixmaps();
2867 }
2868
2869 // [MEM] remove allocation descriptors
2870 qDeleteAll(d->m_allocatedPixmaps);
2871 d->m_allocatedPixmaps.clear();
2872 d->m_allocatedPixmapsTotalMemory = 0;
2873
2874 // send reload signals to observers
2875 foreachObserver(notifyContentsCleared(DocumentObserver::Pixmap));
2876 }
2877
2878 // free memory if in 'low' profile
2879 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2880 d->cleanupPixmapMemory();
2881 }
2882}
2883
2885{
2886 return d->m_generator;
2887}
2888
2890{
2891 if (d->m_generator) {
2893 return iface ? true : false;
2894 } else {
2895 return false;
2896 }
2897}
2898
2899bool Document::sign(const NewSignatureData &data, const QString &newPath)
2900{
2901 if (d->m_generator->canSign()) {
2902 return d->m_generator->sign(data, newPath);
2903 } else {
2904 return false;
2905 }
2906}
2907
2909{
2910 return d->m_generator ? d->m_generator->certificateStore() : nullptr;
2911}
2912
2914{
2915 d->editorCommandOverride = editCmd;
2916}
2917
2919{
2920 return d->editorCommandOverride;
2921}
2922
2924{
2927 keys << ks;
2928 }
2929
2930 return documentInfo(keys);
2931}
2932
2934{
2935 DocumentInfo result = d->m_documentInfo;
2936 const QSet<DocumentInfo::Key> missingKeys = keys - d->m_documentInfoAskedKeys;
2937
2938 if (d->m_generator && !missingKeys.isEmpty()) {
2939 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2940
2941 if (missingKeys.contains(DocumentInfo::FilePath)) {
2942 info.set(DocumentInfo::FilePath, currentDocument().toDisplayString());
2943 }
2944
2945 if (d->m_docSize != -1 && missingKeys.contains(DocumentInfo::DocumentSize)) {
2946 const QString sizeString = KFormat().formatByteSize(d->m_docSize);
2947 info.set(DocumentInfo::DocumentSize, sizeString);
2948 }
2949 if (missingKeys.contains(DocumentInfo::PagesSize)) {
2950 const QString pagesSize = d->pagesSizeString();
2951 if (!pagesSize.isEmpty()) {
2952 info.set(DocumentInfo::PagesSize, pagesSize);
2953 }
2954 }
2955
2956 if (missingKeys.contains(DocumentInfo::Pages) && info.get(DocumentInfo::Pages).isEmpty()) {
2958 }
2959
2960 d->m_documentInfo.d->values.insert(info.d->values);
2961 d->m_documentInfo.d->titles.insert(info.d->titles);
2962 result.d->values.insert(info.d->values);
2963 result.d->titles.insert(info.d->titles);
2964 }
2965 d->m_documentInfoAskedKeys += keys;
2966
2967 return result;
2968}
2969
2971{
2972 return d->m_generator ? d->m_generator->generateDocumentSynopsis() : nullptr;
2973}
2974
2976{
2977 if (!d->m_generator || !d->m_generator->hasFeature(Generator::FontInfo) || d->m_fontThread) {
2978 return;
2979 }
2980
2981 if (d->m_fontsCached) {
2982 // in case we have cached fonts, simulate a reading
2983 // this way the API is the same, and users no need to care about the
2984 // internal caching
2985 for (int i = 0; i < d->m_fontsCache.count(); ++i) {
2986 Q_EMIT gotFont(d->m_fontsCache.at(i));
2988 }
2990 return;
2991 }
2992
2993 d->m_fontThread = new FontExtractionThread(d->m_generator, pages());
2994 connect(d->m_fontThread, &FontExtractionThread::gotFont, this, [this](const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2995 connect(d->m_fontThread.data(), &FontExtractionThread::progress, this, [this](int p) { d->slotFontReadingProgress(p); });
2996
2997 d->m_fontThread->startExtraction(/*d->m_generator->hasFeature( Generator::Threaded )*/ true);
2998}
2999
3001{
3002 if (!d->m_fontThread) {
3003 return;
3004 }
3005
3006 disconnect(d->m_fontThread, nullptr, this, nullptr);
3007 d->m_fontThread->stopExtraction();
3008 d->m_fontThread = nullptr;
3009 d->m_fontsCache.clear();
3010}
3011
3013{
3014 return d->m_generator ? d->m_generator->hasFeature(Generator::FontInfo) : false;
3015}
3016
3018{
3019 return d->m_generator ? d->m_generator->canSign() : false;
3020}
3021
3023{
3024 return d->m_generator ? d->m_generator->embeddedFiles() : nullptr;
3025}
3026
3027const Page *Document::page(int n) const
3028{
3029 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) : nullptr;
3030}
3031
3033{
3034 return (*d->m_viewportIterator);
3035}
3036
3038{
3039 return d->m_pageRects;
3040}
3041
3043{
3044 QVector<VisiblePageRect *>::const_iterator vIt = d->m_pageRects.constBegin();
3045 QVector<VisiblePageRect *>::const_iterator vEnd = d->m_pageRects.constEnd();
3046 for (; vIt != vEnd; ++vIt) {
3047 delete *vIt;
3048 }
3049 d->m_pageRects = visiblePageRects;
3050 // notify change to all other (different from id) observers
3051 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3052 if (o != excludeObserver) {
3053 o->notifyVisibleRectsChanged();
3054 }
3055 }
3056}
3057
3059{
3060 return (*d->m_viewportIterator).pageNumber;
3061}
3062
3064{
3065 return d->m_pagesVector.size();
3066}
3067
3069{
3070 return d->m_url;
3071}
3072
3074{
3075 if (action == Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3076 return false;
3077 }
3078 if (action == Okular::AllowFillForms && d->m_docdataMigrationNeeded) {
3079 return false;
3080 }
3081
3082#if !OKULAR_FORCE_DRM
3083 if (KAuthorized::authorize(QStringLiteral("skip_drm")) && !SettingsCore::obeyDRM()) {
3084 return true;
3085 }
3086#endif
3087
3088 return d->m_generator ? d->m_generator->isAllowed(action) : false;
3089}
3090
3092{
3093 return d->m_generator ? d->m_generator->hasFeature(Generator::TextExtraction) : false;
3094}
3095
3097{
3098 return d->m_generator ? d->m_generator->hasFeature(Generator::PageSizes) : false;
3099}
3100
3102{
3103 return d->m_generator ? d->m_generator->hasFeature(Generator::TiledRendering) : false;
3104}
3105
3106PageSize::List Document::pageSizes() const
3107{
3108 if (d->m_generator) {
3109 if (d->m_pageSizes.isEmpty()) {
3110 d->m_pageSizes = d->m_generator->pageSizes();
3111 }
3112 return d->m_pageSizes;
3113 }
3114 return PageSize::List();
3115}
3116
3118{
3119 if (!d->m_generator) {
3120 return false;
3121 }
3122
3123 d->cacheExportFormats();
3124 return !d->m_exportToText.isNull();
3125}
3126
3127bool Document::exportToText(const QString &fileName) const
3128{
3129 if (!d->m_generator) {
3130 return false;
3131 }
3132
3133 d->cacheExportFormats();
3134 if (d->m_exportToText.isNull()) {
3135 return false;
3136 }
3137
3138 return d->m_generator->exportTo(fileName, d->m_exportToText);
3139}
3140
3141ExportFormat::List Document::exportFormats() const
3142{
3143 if (!d->m_generator) {
3144 return ExportFormat::List();
3145 }
3146
3147 d->cacheExportFormats();
3148 return d->m_exportFormats;
3149}
3150
3151bool Document::exportTo(const QString &fileName, const ExportFormat &format) const
3152{
3153 return d->m_generator ? d->m_generator->exportTo(fileName, format) : false;
3154}
3155
3157{
3158 return d->m_viewportIterator == d->m_viewportHistory.begin();
3159}
3160
3162{
3163 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3164}
3165
3166QVariant Document::metaData(const QString &key, const QVariant &option) const
3167{
3168 // if option starts with "src:" assume that we are handling a
3169 // source reference
3170 if (key == QLatin1String("NamedViewport") && option.toString().startsWith(QLatin1String("src:"), Qt::CaseInsensitive) && d->m_synctex_scanner) {
3171 const QString reference = option.toString();
3172
3173 // The reference is of form "src:1111Filename", where "1111"
3174 // points to line number 1111 in the file "Filename".
3175 // Extract the file name and the numeral part from the reference string.
3176 // This will fail if Filename starts with a digit.
3177 QString name, lineString;
3178 // Remove "src:". Presence of substring has been checked before this
3179 // function is called.
3180 name = reference.mid(4);
3181 // split
3182 int nameLength = name.length();
3183 int i = 0;
3184 for (i = 0; i < nameLength; ++i) {
3185 if (!name[i].isDigit()) {
3186 break;
3187 }
3188 }
3189 lineString = name.left(i);
3190 name = name.mid(i);
3191 // Remove spaces.
3192 name = name.trimmed();
3193 lineString = lineString.trimmed();
3194 // Convert line to integer.
3195 bool ok;
3196 int line = lineString.toInt(&ok);
3197 if (!ok) {
3198 line = -1;
3199 }
3200
3201 // Use column == -1 for now.
3202 if (synctex_display_query(d->m_synctex_scanner, QFile::encodeName(name).constData(), line, -1, 0) > 0) {
3203 synctex_node_p node;
3204 // For now use the first hit. Could possibly be made smarter
3205 // in case there are multiple hits.
3206 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3208
3209 // TeX pages start at 1.
3210 viewport.pageNumber = synctex_node_page(node) - 1;
3211
3212 if (viewport.pageNumber >= 0) {
3213 const QSizeF dpi = d->m_generator->dpi();
3214
3215 // TeX small points ...
3216 double px = (synctex_node_visible_h(node) * dpi.width()) / 72.27;
3217 double py = (synctex_node_visible_v(node) * dpi.height()) / 72.27;
3218 viewport.rePos.normalizedX = px / page(viewport.pageNumber)->width();
3219 viewport.rePos.normalizedY = (py + 0.5) / page(viewport.pageNumber)->height();
3220 viewport.rePos.enabled = true;
3222
3223 return viewport.toString();
3224 }
3225 }
3226 }
3227 }
3228 return d->m_generator ? d->m_generator->metaData(key, option) : QVariant();
3229}
3230
3232{
3233 return d->m_rotation;
3234}
3235
3237{
3238 bool allPagesSameSize = true;
3239 QSizeF size;
3240 for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3241 const Page *p = d->m_pagesVector.at(i);
3242 if (i == 0) {
3243 size = QSizeF(p->width(), p->height());
3244 } else {
3245 allPagesSameSize = (size == QSizeF(p->width(), p->height()));
3246 }
3247 }
3248 if (allPagesSameSize) {
3249 return size;
3250 } else {
3251 return QSizeF();
3252 }
3253}
3254
3256{
3257 if (d->m_generator) {
3258 if (d->m_generator->pagesSizeMetric() != Generator::None) {
3259 const Page *p = d->m_pagesVector.at(page);
3260 return d->localizedSize(QSizeF(p->width(), p->height()));
3261 }
3262 }
3263 return QString();
3264}
3265
3266static bool shouldCancelRenderingBecauseOf(const PixmapRequest &executingRequest, const PixmapRequest &otherRequest)
3267{
3268 // New request has higher priority -> cancel
3269 if (executingRequest.priority() > otherRequest.priority()) {
3270 return true;
3271 }
3272
3273 // New request has lower priority -> don't cancel
3274 if (executingRequest.priority() < otherRequest.priority()) {
3275 return false;
3276 }
3277
3278 // New request has same priority and is from a different observer -> don't cancel
3279 // AFAIK this never happens since all observers have different priorities
3280 if (executingRequest.observer() != otherRequest.observer()) {
3281 return false;
3282 }
3283
3284 // Same priority and observer, different page number -> don't cancel
3285 // may still end up cancelled later in the parent caller if none of the requests
3286 // is of the executingRequest page and RemoveAllPrevious is specified
3287 if (executingRequest.pageNumber() != otherRequest.pageNumber()) {
3288 return false;
3289 }
3290
3291 // Same priority, observer, page, different size -> cancel
3292 if (executingRequest.width() != otherRequest.width()) {
3293 return true;
3294 }
3295
3296 // Same priority, observer, page, different size -> cancel
3297 if (executingRequest.height() != otherRequest.height()) {
3298 return true;
3299 }
3300
3301 // Same priority, observer, page, different tiling -> cancel
3302 if (executingRequest.isTile() != otherRequest.isTile()) {
3303 return true;
3304 }
3305
3306 // Same priority, observer, page, different tiling -> cancel
3307 if (executingRequest.isTile()) {
3308 const NormalizedRect bothRequestsRect = executingRequest.normalizedRect() | otherRequest.normalizedRect();
3309 if (!(bothRequestsRect == executingRequest.normalizedRect())) {
3310 return true;
3311 }
3312 }
3313
3314 return false;
3315}
3316
3317bool DocumentPrivate::cancelRenderingBecauseOf(PixmapRequest *executingRequest, PixmapRequest *newRequest)
3318{
3319 // No point in aborting the rendering already finished, let it go through
3320 if (!executingRequest->d->mResultImage.isNull()) {
3321 return false;
3322 }
3323
3324 if (newRequest && newRequest->asynchronous() && executingRequest->partialUpdatesWanted()) {
3325 newRequest->setPartialUpdatesWanted(true);
3326 }
3327
3328 TilesManager *tm = executingRequest->d->tilesManager();
3329 if (tm) {
3330 tm->setPixmap(nullptr, executingRequest->normalizedRect(), true /*isPartialPixmap*/);
3331 tm->setRequest(NormalizedRect(), 0, 0);
3332 }
3333 PagePrivate::PixmapObject object = executingRequest->page()->d->m_pixmaps.take(executingRequest->observer());
3334 delete object.m_pixmap;
3335
3336 if (executingRequest->d->mShouldAbortRender != 0) {
3337 return false;
3338 }
3339
3340 executingRequest->d->mShouldAbortRender = 1;
3341
3342 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->page()) {
3343 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3344 }
3345
3346 return true;
3347}
3348
3350{
3352}
3353
3354void Document::requestPixmaps(const QList<PixmapRequest *> &requests, PixmapRequestFlags reqOptions)
3355{
3356 if (requests.isEmpty()) {
3357 return;
3358 }
3359
3360 if (!d->m_pageController) {
3361 // delete requests..
3362 qDeleteAll(requests);
3363 // ..and return
3364 return;
3365 }
3366
3367 QSet<DocumentObserver *> observersPixmapCleared;
3368
3369 // 1. [CLEAN STACK] remove previous requests of requesterID
3370 const DocumentObserver *requesterObserver = requests.first()->observer();
3371 QSet<int> requestedPages;
3372 {
3373 for (const PixmapRequest *request : requests) {
3374 Q_ASSERT(request->observer() == requesterObserver);
3375 requestedPages.insert(request->pageNumber());
3376 }
3377 }
3378 const bool removeAllPrevious = reqOptions & RemoveAllPrevious;
3379 d->m_pixmapRequestsMutex.lock();
3380 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3381 while (sIt != sEnd) {
3382 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.contains((*sIt)->pageNumber()))) {
3383 // delete request and remove it from stack
3384 delete *sIt;
3385 sIt = d->m_pixmapRequestsStack.erase(sIt);
3386 } else {
3387 ++sIt;
3388 }
3389 }
3390
3391 // 1.B [PREPROCESS REQUESTS] tweak some values of the requests
3392 for (PixmapRequest *request : requests) {
3393 // set the 'page field' (see PixmapRequest) and check if it is valid
3394 qCDebug(OkularCoreDebug).nospace() << "request observer=" << request->observer() << " " << request->width() << "x" << request->height() << "@" << request->pageNumber();
3395 if (d->m_pagesVector.value(request->pageNumber()) == nullptr) {
3396 // skip requests referencing an invalid page (must not happen)
3397 delete request;
3398 continue;
3399 }
3400
3401 request->d->mPage = d->m_pagesVector.value(request->pageNumber());
3402
3403 if (request->isTile()) {
3404 // Change the current request rect so that only invalid tiles are
3405 // requested. Also make sure the rect is tile-aligned.
3406 NormalizedRect tilesRect;
3407 const QList<Tile> tiles = request->d->tilesManager()->tilesAt(request->normalizedRect(), TilesManager::TerminalTile);
3408 QList<Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
3409 while (tIt != tEnd) {
3410 const Tile &tile = *tIt;
3411 if (!tile.isValid()) {
3412 if (tilesRect.isNull()) {
3413 tilesRect = tile.rect();
3414 } else {
3415 tilesRect |= tile.rect();
3416 }
3417 }
3418
3419 tIt++;
3420 }
3421
3422 request->setNormalizedRect(tilesRect);
3423 }
3424
3425 if (!request->asynchronous()) {
3426 request->d->mPriority = 0;
3427 }
3428 }
3429
3430 // 1.C [CANCEL REQUESTS] cancel those requests that are running and should be cancelled because of the new requests coming in
3431 if (d->m_generator->hasFeature(Generator::SupportsCancelling)) {
3432 for (PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
3433 bool newRequestsContainExecutingRequestPage = false;
3434 bool requestCancelled = false;
3435 for (PixmapRequest *newRequest : requests) {
3436 if (newRequest->pageNumber() == executingRequest->pageNumber() && requesterObserver == executingRequest->observer()) {
3437 newRequestsContainExecutingRequestPage = true;
3438 }
3439
3440 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3441 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3442 }
3443 }
3444
3445 // If we were told to remove all the previous requests and the executing request page is not part of the new requests, cancel it
3446 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->observer() && !newRequestsContainExecutingRequestPage) {
3447 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, nullptr);
3448 }
3449
3450 if (requestCancelled) {
3451 observersPixmapCleared << executingRequest->observer();
3452 }
3453 }
3454 }
3455
3456 // 2. [ADD TO STACK] add requests to stack
3457 for (PixmapRequest *request : requests) {
3458 // add request to the 'stack' at the right place
3459 if (!request->priority()) {
3460 // add priority zero requests to the top of the stack
3461 d->m_pixmapRequestsStack.push_back(request);
3462 } else {
3463 // insert in stack sorted by priority
3464 sIt = d->m_pixmapRequestsStack.begin();
3465 sEnd = d->m_pixmapRequestsStack.end();
3466 while (sIt != sEnd && (*sIt)->priority() > request->priority()) {
3467 ++sIt;
3468 }
3469 d->m_pixmapRequestsStack.insert(sIt, request);
3470 }
3471 }
3472 d->m_pixmapRequestsMutex.unlock();
3473
3474 // 3. [START FIRST GENERATION] if <NO>generator is ready, start a new generation,
3475 // or else (if gen is running) it will be started when the new contents will
3476 // come from generator (in requestDone())</NO>
3477 // all handling of requests put into sendGeneratorPixmapRequest
3478 // if ( generator->canRequestPixmap() )
3479 d->sendGeneratorPixmapRequest();
3480
3481 for (DocumentObserver *o : std::as_const(observersPixmapCleared)) {
3482 o->notifyContentsCleared(Okular::DocumentObserver::Pixmap);
3483 }
3484}
3485
3486void Document::requestTextPage(uint pageNumber)
3487{
3488 Page *kp = d->m_pagesVector[pageNumber];
3489 if (!d->m_generator || !kp) {
3490 return;
3491 }
3492
3493 // Memory management for TextPages
3494
3495 d->m_generator->generateTextPage(kp);
3496}
3497
3498void DocumentPrivate::notifyAnnotationChanges(int page)
3499{
3500 foreachObserverD(notifyPageChanged(page, DocumentObserver::Annotations));
3501}
3502
3503void DocumentPrivate::notifyFormChanges(int /*page*/)
3504{
3505 recalculateForms();
3506}
3507
3509{
3510 d->recalculateForms();
3511}
3512
3514{
3515 // Transform annotation's base boundary rectangle into unrotated coordinates
3516 Page *p = d->m_pagesVector[page];
3517 QTransform t = p->d->rotationMatrix();
3518 annotation->d_ptr->baseTransform(t.inverted());
3519 QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page);
3520 d->m_undoStack->push(uc);
3521}
3522
3524{
3525 if (!annotation || (annotation->flags() & Annotation::DenyWrite)) {
3526 return false;
3527 }
3528
3530 return false;
3531 }
3532
3533 if ((annotation->flags() & Annotation::External) && !d->canModifyExternalAnnotations()) {
3534 return false;
3535 }
3536
3537 switch (annotation->subType()) {
3538 case Annotation::AText:
3539 case Annotation::ALine:
3540 case Annotation::AGeom:
3542 case Annotation::AStamp:
3543 case Annotation::AInk:
3544 return true;
3546#if HAVE_NEW_SIGNATURE_API
3547 return dynamic_cast<const SignatureAnnotation *>(annotation);
3548#endif
3549 return false;
3550 default:
3551 return false;
3552 }
3553}
3554
3556{
3557 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3558 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3559 qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3560 return;
3561 }
3562 d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode();
3563}
3564
3566{
3567 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3568 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3569 qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3570 return;
3571 }
3572 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3573 QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand(d, annotation, page, prevProps, annotation->getAnnotationPropertiesDomNode());
3574 d->m_undoStack->push(uc);
3575 d->m_prevPropsOfAnnotBeingModified.clear();
3576}
3577
3579{
3580 int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0;
3581 QUndoCommand *uc = new Okular::TranslateAnnotationCommand(d, annotation, page, delta, complete);
3582 d->m_undoStack->push(uc);
3583}
3584
3586{
3587 const bool complete = (annotation->flags() & Okular::Annotation::BeingResized) == 0;
3588 QUndoCommand *uc = new Okular::AdjustAnnotationCommand(d, annotation, page, delta1, delta2, complete);
3589 d->m_undoStack->push(uc);
3590}
3591
3592void Document::editPageAnnotationContents(int page, Annotation *annotation, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
3593{
3594 QString prevContents = annotation->contents();
3595 QUndoCommand *uc = new EditAnnotationContentsCommand(d, annotation, page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3596 d->m_undoStack->push(uc);
3597}
3598
3600{
3601 if (!annotation || (annotation->flags() & Annotation::DenyDelete)) {
3602 return false;
3603 }
3604
3605 if ((annotation->flags() & Annotation::External) && !d->canRemoveExternalAnnotations()) {
3606 return false;
3607 }
3608
3609 switch (annotation->subType()) {
3610 case Annotation::AText:
3611 case Annotation::ALine:
3612 case Annotation::AGeom:
3614 case Annotation::AStamp:
3615 case Annotation::AInk:
3616 case Annotation::ACaret:
3617 return true;
3618 default:
3619 return false;
3620 }
3621}
3622
3624{
3625 QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page);
3626 d->m_undoStack->push(uc);
3627}
3628
3630{
3631 d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations"));
3632 for (Annotation *annotation : annotations) {
3633 QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page);
3634 d->m_undoStack->push(uc);
3635 }
3636 d->m_undoStack->endMacro();
3637}
3638
3639bool DocumentPrivate::canAddAnnotationsNatively() const
3640{
3641 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3642
3644 return true;
3645 }
3646
3647 return false;
3648}
3649
3650bool DocumentPrivate::canModifyExternalAnnotations() const
3651{
3652 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3653
3655 return true;
3656 }
3657
3658 return false;
3659}
3660
3661bool DocumentPrivate::canRemoveExternalAnnotations() const
3662{
3663 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3664
3666 return true;
3667 }
3668
3669 return false;
3670}
3671
3672void Document::setPageTextSelection(int page, std::unique_ptr<RegularAreaRect> &&rect, const QColor &color)
3673{
3674 Page *kp = d->m_pagesVector[page];
3675 if (!d->m_generator || !kp) {
3676 return;
3677 }
3678
3679 // add or remove the selection basing whether rect is null or not
3680 if (rect) {
3681 kp->d->setTextSelections(*rect, color);
3682 } else {
3683 kp->d->deleteTextSelections();
3684 }
3685
3686 // notify observers about the change
3687 foreachObserver(notifyPageChanged(page, DocumentObserver::TextSelection));
3688}
3689
3691{
3692 return d->m_undoStack->canUndo();
3693}
3694
3696{
3697 return d->m_undoStack->canRedo();
3698}
3699
3700/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
3701void Document::setNextPage()
3702{
3703 // advance page and set viewport on observers
3704 if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
3705 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) );
3706}
3707
3708void Document::setPrevPage()
3709{
3710 // go to previous page and set viewport on observers
3711 if ( (*d->m_viewportIterator).pageNumber > 0 )
3712 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) );
3713}
3714*/
3715
3716void Document::setViewport(const DocumentViewport &viewport, DocumentObserver *excludeObserver, bool smoothMove, bool updateHistory)
3717{
3718 if (!viewport.isValid()) {
3719 qCDebug(OkularCoreDebug) << "invalid viewport:" << viewport.toString();
3720 return;
3721 }
3722 if (viewport.pageNumber >= int(d->m_pagesVector.count())) {
3723 // qCDebug(OkularCoreDebug) << "viewport out of document:" << viewport.toString();
3724 return;
3725 }
3726
3727 // if already broadcasted, don't redo it
3728 DocumentViewport &oldViewport = *d->m_viewportIterator;
3729 // disabled by enrico on 2005-03-18 (less debug output)
3730 // if ( viewport == oldViewport )
3731 // qCDebug(OkularCoreDebug) << "setViewport with the same viewport.";
3732
3733 const int oldPageNumber = oldViewport.pageNumber;
3734
3735 // set internal viewport taking care of history
3736 if (oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() || !updateHistory) {
3737 // if page is unchanged save the viewport at current position in queue
3738 oldViewport = viewport;
3739 } else {
3740 // remove elements after viewportIterator in queue
3741 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3742
3743 // keep the list to a reasonable size by removing head when needed
3744 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3745 d->m_viewportHistory.pop_front();
3746 }
3747
3748 // add the item at the end of the queue
3749 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(), viewport);
3750 }
3751
3752 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3753
3754 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3755
3756 // notify change to all other (different from id) observers
3757 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3758 if (o != excludeObserver) {
3759 o->notifyViewportChanged(smoothMove);
3760 }
3761
3762 if (currentPageChanged) {
3763 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3764 }
3765 }
3766}
3767
3768void Document::setViewportPage(int page, DocumentObserver *excludeObserver, bool smoothMove)
3769{
3770 // clamp page in range [0 ... numPages-1]
3771 if (page < 0) {
3772 page = 0;
3773 } else if (page > (int)d->m_pagesVector.count()) {
3774 page = d->m_pagesVector.count() - 1;
3775 }
3776
3777 // make a viewport from the page and broadcast it
3778 setViewport(DocumentViewport(page), excludeObserver, smoothMove);
3779}
3780
3781void Document::setZoom(int factor, DocumentObserver *excludeObserver)
3782{
3783 // notify change to all other (different from id) observers
3784 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3785 if (o != excludeObserver) {
3786 o->notifyZoom(factor);
3787 }
3788 }
3789}
3790
3792// restore viewport from the history
3793{
3794 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3795 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3796
3797 // restore previous viewport and notify it to observers
3798 --d->m_viewportIterator;
3799 foreachObserver(notifyViewportChanged(true));
3800
3801 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3802 if (oldViewportPage != currentViewportPage)
3803 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3804 }
3805}
3806
3808// restore next viewport from the history
3809{
3810 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3811 ++nextIterator;
3812 if (nextIterator != d->m_viewportHistory.end()) {
3813 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3814
3815 // restore next viewport and notify it to observers
3816 ++d->m_viewportIterator;
3817 foreachObserver(notifyViewportChanged(true));
3818
3819 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3820 if (oldViewportPage != currentViewportPage)
3821 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3822 }
3823}
3824
3826{
3827 d->m_nextDocumentViewport = viewport;
3828}
3829
3831{
3832 d->m_nextDocumentDestination = namedDestination;
3833}
3834
3835void Document::searchText(int searchID, const QString &text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor &color)
3836{
3837 d->m_searchCancelled = false;
3838
3839 // safety checks: don't perform searches on empty or unsearchable docs
3840 if (!d->m_generator || !d->m_generator->hasFeature(Generator::TextExtraction) || d->m_pagesVector.isEmpty()) {
3842 return;
3843 }
3844
3845 // if searchID search not recorded, create new descriptor and init params
3846 QMap<int, RunningSearch *>::iterator searchIt = d->m_searches.find(searchID);
3847 if (searchIt == d->m_searches.end()) {
3848 RunningSearch *search = new RunningSearch();
3849 search->continueOnPage = -1;
3850 searchIt = d->m_searches.insert(searchID, search);
3851 }
3852 RunningSearch *s = *searchIt;
3853
3854 // update search structure
3855 bool newText = text != s->cachedString;
3856 s->cachedString = text;
3857 s->cachedType = type;
3858 s->cachedCaseSensitivity = caseSensitivity;
3859 s->cachedViewportMove = moveViewport;
3860 s->cachedColor = color;
3861 s->isCurrentlySearching = true;
3862
3863 // global data for search
3864 QSet<int> *pagesToNotify = new QSet<int>;
3865
3866 // remove highlights from pages and queue them for notifying changes
3867 *pagesToNotify += s->highlightedPages;
3868 for (const int pageNumber : std::as_const(s->highlightedPages)) {
3869 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3870 }
3871 s->highlightedPages.clear();
3872
3873 // set hourglass cursor
3875
3876 // 1. ALLDOC - process all document marking pages
3877 if (type == AllDocument) {
3879
3880 // search and highlight 'text' (as a solid phrase) on all pages
3881 QTimer::singleShot(0, this, [this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3882 }
3883 // 2. NEXTMATCH - find next matching item (or start from top)
3884 // 3. PREVMATCH - find previous matching item (or start from bottom)
3885 else if (type == NextMatch || type == PreviousMatch) {
3886 // find out from where to start/resume search from
3887 const bool forward = type == NextMatch;
3888 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3889 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3890 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3891 const Page *lastPage = fromStart ? nullptr : d->m_pagesVector[currentPage];
3892 int pagesDone = 0;
3893
3894 // continue checking last TextPage first (if it is the current page)
3895 RegularAreaRect *match = nullptr;
3896 if (lastPage && lastPage->number() == s->continueOnPage) {
3897 if (newText) {
3898 match = lastPage->findText(searchID, text, forward ? FromTop : FromBottom, caseSensitivity);
3899 } else {
3900 match = lastPage->findText(searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch);
3901 }
3902 if (!match) {
3903 if (forward) {
3904 currentPage++;
3905 } else {
3906 currentPage--;
3907 }
3908 pagesDone++;
3909 }
3910 }
3911
3912 s->pagesDone = pagesDone;
3913
3914 DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct();
3915 searchStruct->pagesToNotify = pagesToNotify;
3916 searchStruct->match = match;
3917 searchStruct->currentPage = currentPage;
3918 searchStruct->searchID = searchID;
3919
3920 QTimer::singleShot(0, this, [this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3921 }
3922 // 4. GOOGLE* - process all document marking pages
3923 else if (type == GoogleAll || type == GoogleAny) {
3925 const QStringList words = text.split(QLatin1Char(' '), Qt::SkipEmptyParts);
3926
3927 // search and highlight every word in 'text' on all pages
3928 QTimer::singleShot(0, this, [this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3929 }
3930}
3931
3933{
3934 // check if searchID is present in runningSearches
3935 QMap<int, RunningSearch *>::const_iterator it = d->m_searches.constFind(searchID);
3936 if (it == d->m_searches.constEnd()) {
3938 return;
3939 }
3940
3941 // start search with cached parameters from last search by searchID
3942 RunningSearch *p = *it;
3943 if (!p->isCurrentlySearching) {
3944 searchText(searchID, p->cachedString, false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3945 }
3946}
3947
3948void Document::continueSearch(int searchID, SearchType type)
3949{
3950 // check if searchID is present in runningSearches
3951 QMap<int, RunningSearch *>::const_iterator it = d->m_searches.constFind(searchID);
3952 if (it == d->m_searches.constEnd()) {
3954 return;
3955 }
3956
3957 // start search with cached parameters from last search by searchID
3958 RunningSearch *p = *it;
3959 if (!p->isCurrentlySearching) {
3960 searchText(searchID, p->cachedString, false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3961 }
3962}
3963
3964void Document::resetSearch(int searchID)
3965{
3966 // if we are closing down, don't bother doing anything
3967 if (!d->m_generator) {
3968 return;
3969 }
3970
3971 // check if searchID is present in runningSearches
3972 QMap<int, RunningSearch *>::iterator searchIt = d->m_searches.find(searchID);
3973 if (searchIt == d->m_searches.end()) {
3974 return;
3975 }
3976
3977 // get previous parameters for search
3978 RunningSearch *s = *searchIt;
3979
3980 // unhighlight pages and inform observers about that
3981 for (const int pageNumber : std::as_const(s->highlightedPages)) {
3982 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3983 foreachObserver(notifyPageChanged(pageNumber, DocumentObserver::Highlights));
3984 }
3985
3986 // send the setup signal too (to update views that filter on matches)
3987 foreachObserver(notifySetup(d->m_pagesVector, 0));
3988
3989 // remove search from the runningSearches list and delete it
3990 d->m_searches.erase(searchIt);
3991 delete s;
3992}
3993
3995{
3996 d->m_searchCancelled = true;
3997}
3998
4000{
4001 d->m_undoStack->undo();
4002}
4003
4005{
4006 d->m_undoStack->redo();
4007}
4008
4009void Document::editFormText(int pageNumber, Okular::FormFieldText *form, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
4010{
4011 QUndoCommand *uc = new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos);
4012 d->m_undoStack->push(uc);
4013}
4014
4015void Document::editFormText(int pageNumber, Okular::FormFieldText *form, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos, const QString &oldContents)
4016{
4017 QUndoCommand *uc = new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, oldContents, prevCursorPos, prevAnchorPos);
4018 d->m_undoStack->push(uc);
4019}
4020
4021void Document::editFormList(int pageNumber, FormFieldChoice *form, const QList<int> &newChoices)
4022{
4023 const QList<int> prevChoices = form->currentChoices();
4024 QUndoCommand *uc = new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
4025 d->m_undoStack->push(uc);
4026}
4027
4028void Document::editFormCombo(int pageNumber, FormFieldChoice *form, const QString &newText, int newCursorPos, int prevCursorPos, int prevAnchorPos)
4029{
4030 QString prevText;
4031 if (form->currentChoices().isEmpty()) {
4032 prevText = form->editChoice();
4033 } else {
4034 prevText = form->choices().at(form->currentChoices().constFirst());
4035 }
4036
4037 QUndoCommand *uc = new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4038 d->m_undoStack->push(uc);
4039}
4040
4041void Document::editFormButtons(int pageNumber, const QList<FormFieldButton *> &formButtons, const QList<bool> &newButtonStates)
4042{
4043 QUndoCommand *uc = new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4044 d->m_undoStack->push(uc);
4045}
4046
4048{
4049 const int numOfPages = pages();
4050 for (int i = currentPage(); i >= 0; i--) {
4051 d->refreshPixmaps(i);
4052 }
4053 for (int i = currentPage() + 1; i < numOfPages; i++) {
4054 d->refreshPixmaps(i);
4055 }
4056}
4057
4059{
4060 return d->m_bookmarkManager;
4061}
4062
4064{
4065 QList<int> list;
4066 uint docPages = pages();
4067
4068 // pages are 0-indexed internally, but 1-indexed externally
4069 for (uint i = 0; i < docPages; i++) {
4070 if (bookmarkManager()->isBookmarked(i)) {
4071 list << i + 1;
4072 }
4073 }
4074 return list;
4075}
4076
4078{
4079 // Code formerly in Part::slotPrint()
4080 // range detecting
4081 QString range;
4082 uint docPages = pages();
4083 int startId = -1;
4084 int endId = -1;
4085
4086 for (uint i = 0; i < docPages; ++i) {
4087 if (bookmarkManager()->isBookmarked(i)) {
4088 if (startId < 0) {
4089 startId = i;
4090 }
4091 if (endId < 0) {
4092 endId = startId;
4093 } else {
4094 ++endId;
4095 }
4096 } else if (startId >= 0 && endId >= 0) {
4097 if (!range.isEmpty()) {
4098 range += QLatin1Char(',');
4099 }
4100
4101 if (endId - startId > 0) {
4102 range += QStringLiteral("%1-%2").arg(startId + 1).arg(endId + 1);
4103 } else {
4104 range += QString::number(startId + 1);
4105 }
4106 startId = -1;
4107 endId = -1;
4108 }
4109 }
4110 if (startId >= 0 && endId >= 0) {
4111 if (!range.isEmpty()) {
4112 range += QLatin1Char(',');
4113 }
4114
4115 if (endId - startId > 0) {
4116 range += QStringLiteral("%1-%2").arg(startId + 1).arg(endId + 1);
4117 } else {
4118 range += QString::number(startId + 1);
4119 }
4120 }
4121 return range;
4122}
4123
4124struct ExecuteNextActionsHelper : public QObject, private DocumentObserver {
4125 Q_OBJECT
4126public:
4127 explicit ExecuteNextActionsHelper(Document *doc)
4128 : m_doc(doc)
4129 {
4130 doc->addObserver(this);
4131 connect(doc, &Document::aboutToClose, this, [this] { b = false; });
4132 }
4133
4134 ~ExecuteNextActionsHelper() override
4135 {
4136 m_doc->removeObserver(this);
4137 }
4138
4139 void notifySetup(const QVector<Okular::Page *> & /*pages*/, int setupFlags) override
4140 {
4141 if (setupFlags == DocumentChanged || setupFlags == UrlChanged) {
4142 b = false;
4143 }
4144 }
4145
4146 bool shouldExecuteNextAction() const
4147 {
4148 return b;
4149 }
4150
4151private:
4152 Document *const m_doc;
4153 bool b = true;
4154};
4155
4157{
4158 if (!action) {
4159 return;
4160 }
4161
4162 // Don't execute next actions if the action itself caused the closing of the document
4163 const ExecuteNextActionsHelper executeNextActionsHelper(this);
4164
4165 switch (action->actionType()) {
4166 case Action::Goto: {
4167 const GotoAction *go = static_cast<const GotoAction *>(action);
4168 d->m_nextDocumentViewport = go->destViewport();
4169 d->m_nextDocumentDestination = go->destinationName();
4170
4171 // Explanation of why d->m_nextDocumentViewport is needed:
4172 // all openRelativeFile does is launch a signal telling we
4173 // want to open another URL, the problem is that when the file is
4174 // non local, the loading is done asynchronously so you can't
4175 // do a setViewport after the if as it was because you are doing the setViewport
4176 // on the old file and when the new arrives there is no setViewport for it and
4177 // it does not show anything
4178
4179 // first open filename if link is pointing outside this document
4180 const QString filename = go->fileName();
4181 if (go->isExternal() && !d->openRelativeFile(filename)) {
4182 qCWarning(OkularCoreDebug).nospace() << "Action: Error opening '" << filename << "'.";
4183 break;
4184 } else {
4185 const DocumentViewport nextViewport = d->nextDocumentViewport();
4186 // skip local links that point to nowhere (broken ones)
4187 if (!nextViewport.isValid()) {
4188 break;
4189 }
4190
4191 setViewport(nextViewport, nullptr, true);
4192 d->m_nextDocumentViewport = DocumentViewport();
4193 d->m_nextDocumentDestination = QString();
4194 }
4195
4196 } break;
4197
4198 case Action::Execute: {
4199 const ExecuteAction *exe = static_cast<const ExecuteAction *>(action);
4200 const QString fileName = exe->fileName();
4201 if (fileName.endsWith(QLatin1String(".pdf"), Qt::CaseInsensitive)) {
4202 d->openRelativeFile(fileName);
4203 break;
4204 }
4205
4206 // Albert: the only pdf i have that has that kind of link don't define
4207 // an application and use the fileName as the file to open
4208 QUrl url = d->giveAbsoluteUrl(fileName);
4209 QMimeDatabase db;
4210 QMimeType mime = db.mimeTypeForUrl(url);
4211 // Check executables
4212 if (KIO::OpenUrlJob::isExecutableFile(url, mime.name())) {
4213 // Don't have any pdf that uses this code path, just a guess on how it should work
4214 if (!exe->parameters().isEmpty()) {
4215 url = d->giveAbsoluteUrl(exe->parameters());
4216 mime = db.mimeTypeForUrl(url);
4217
4218 if (KIO::OpenUrlJob::isExecutableFile(url, mime.name())) {
4219 // this case is a link pointing to an executable with a parameter
4220 // that also is an executable, possibly a hand-crafted pdf
4221 Q_EMIT error(i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4222 break;
4223 }
4224 } else {
4225 // this case is a link pointing to an executable with no parameters
4226 // core developers find unacceptable executing it even after asking the user
4227 Q_EMIT error(i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4228 break;
4229 }
4230 }
4231
4232 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, mime.name());
4234 job->start();
4235 connect(job, &KIO::OpenUrlJob::result, this, [this, mime](KJob *job) {
4236 if (job->error()) {
4237 Q_EMIT error(i18n("No application found for opening file of mimetype %1.", mime.name()), -1);
4238 }
4239 });
4240 } break;
4241
4242 case Action::DocAction: {
4243 const DocumentAction *docaction = static_cast<const DocumentAction *>(action);
4244 switch (docaction->documentActionType()) {
4246 setViewportPage(0);
4247 break;
4249 if ((*d->m_viewportIterator).pageNumber > 0) {
4250 setViewportPage((*d->m_viewportIterator).pageNumber - 1);
4251 }
4252 break;
4254 if ((*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1) {
4255 setViewportPage((*d->m_viewportIterator).pageNumber + 1);
4256 }
4257 break;
4259 setViewportPage(d->m_pagesVector.count() - 1);
4260 break;
4263 break;
4266 break;
4268 Q_EMIT quit();
4269 break;
4272 break;
4275 break;
4277 Q_EMIT linkFind();
4278 break;
4281 break;
4283 Q_EMIT close();
4284 break;
4287 break;
4290 break;
4291 }
4292 } break;
4293
4294 case Action::Browse: {
4295 const BrowseAction *browse = static_cast<const BrowseAction *>(action);
4296 QString lilySource;
4297 int lilyRow = 0, lilyCol = 0;
4298 // if the url is a mailto one, invoke mailer
4299 if (browse->url().scheme() == QLatin1String("mailto")) {
4300 QDesktopServices::openUrl(browse->url());
4301 } else if (extractLilyPondSourceReference(browse->url(), &lilySource, &lilyRow, &lilyCol)) {
4302 const SourceReference ref(lilySource, lilyRow, lilyCol);
4304 } else {
4305 const QUrl url = browse->url();
4306
4307 // fix for #100366, documents with relative links that are the form of http:foo.pdf
4308 if ((url.scheme() == QLatin1String("http")) && url.host().isEmpty() && url.fileName().endsWith(QLatin1String("pdf"))) {
4309 d->openRelativeFile(url.fileName());
4310 break;
4311 }
4312
4313 // handle documents with relative path
4314 QUrl realUrl;
4315 if (d->m_url.isValid()) {
4316 realUrl = KIO::upUrl(d->m_url).resolved(url);
4317 } else if (!url.isRelative()) {
4318 realUrl = url;
4319 }
4320 if (realUrl.isValid()) {
4321 auto *job = new KIO::OpenUrlJob(realUrl);
4322 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->m_widget.data()));
4323 job->start();
4324 }
4325 }
4326 } break;
4327
4328 case Action::Sound: {
4329 const SoundAction *linksound = static_cast<const SoundAction *>(action);
4330 AudioPlayer::instance()->playSound(linksound->sound(), linksound);
4331 } break;
4332
4333 case Action::Script: {
4334 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4335 if (!d->m_scripter) {
4336 d->m_scripter = new Scripter(d);
4337 }
4338 d->m_scripter->execute(linkscript->scriptType(), linkscript->script());
4339 } break;
4340
4341 case Action::Movie:
4342 Q_EMIT processMovieAction(static_cast<const MovieAction *>(action));
4343 break;
4344 case Action::Rendition: {
4345 const RenditionAction *linkrendition = static_cast<const RenditionAction *>(action);
4346 if (!linkrendition->script().isEmpty()) {
4347 if (!d->m_scripter) {
4348 d->m_scripter = new Scripter(d);
4349 }
4350 d->m_scripter->execute(linkrendition->scriptType(), linkrendition->script());
4351 }
4352
4353 Q_EMIT processRenditionAction(static_cast<const RenditionAction *>(action));
4354 } break;
4355 case Action::BackendOpaque: {
4356 const BackendOpaqueAction *backendOpaqueAction = static_cast<const BackendOpaqueAction *>(action);
4357 Okular::BackendOpaqueAction::OpaqueActionResult res = d->m_generator->opaqueAction(backendOpaqueAction);
4358 if (res & Okular::BackendOpaqueAction::RefreshForms) {
4359 for (const Page *p : std::as_const(d->m_pagesVector)) {
4360 const QList<Okular::FormField *> forms = p->formFields();
4361 for (FormField *form : forms) {
4363 }
4364 d->refreshPixmaps(p->number());
4365 }
4366 }
4367 } break;
4368 }
4369
4370 if (executeNextActionsHelper.shouldExecuteNextAction()) {
4371 const QVector<Action *> nextActions = action->nextActions();
4372 for (const Action *a : nextActions) {
4373 processAction(a);
4374 }
4375 }
4376}
4377
4379{
4380 processFormatAction(action, static_cast<FormField *>(fft));
4381}
4382
4384{
4385 if (action->actionType() != Action::Script) {
4386 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for formatting.";
4387 return;
4388 }
4389
4390 // Lookup the page of the FormFieldText
4391 int foundPage = d->findFieldPageNumber(ff);
4392
4393 if (foundPage == -1) {
4394 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4395 return;
4396 }
4397
4398 const QString unformattedText = ff->value().toString();
4399
4400 std::shared_ptr<Event> event = Event::createFormatEvent(ff, d->m_pagesVector[foundPage]);
4401
4402 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4403
4404 d->executeScriptEvent(event, linkscript);
4405
4406 const QString formattedText = event->value().toString();
4407 ff->commitFormattedValue(formattedText);
4408 if (formattedText != unformattedText) {
4409 // We set the formattedText, because when we call refreshFormWidget
4410 // It will set the QLineEdit to this formattedText
4411 ff->setValue(QVariant(formattedText));
4412 ff->setAppearanceValue(QVariant(formattedText));
4414 d->refreshPixmaps(foundPage);
4415 // Then we make the form have the unformatted text, to use
4416 // in calculations and other things
4417 ff->setValue(QVariant(unformattedText));
4419 // When the field was calculated we need to refresh even
4420 // if the format script changed nothing. e.g. on error.
4421 // This is because the recalculateForms function delegated
4422 // the responsiblity for the refresh to us.
4424 d->refreshPixmaps(foundPage);
4425 }
4426}
4427
4428QString DocumentPrivate::evaluateKeystrokeEventChange(const QString &oldVal, const QString &newVal, int selStart, int selEnd)
4429{
4430 /*
4431 The change needs to be evaluated here in accordance with code points.
4432 selStart and selEnd parameters passed to this method should be been adjusted accordingly.
4433
4434 Since QString methods work in terms of code units, we convert the strings to UTF-32.
4435 */
4436 std::u32string oldUcs4 = oldVal.toStdU32String();
4437 std::u32string newUcs4 = newVal.toStdU32String();
4438 if (selStart < 0 || selEnd < 0 || (selEnd - selStart) + (static_cast<int>(newUcs4.size()) - static_cast<int>(oldUcs4.size())) < 0) {
4439 // Prevent Okular from crashing if incorrect parameters are passed or some bug causes incorrect calculation
4440 return {};
4441 }
4442 const size_t changeLength = (selEnd - selStart) + (newUcs4.size() - oldUcs4.size());
4443 auto subview = std::u32string_view {newUcs4}.substr(selStart, changeLength);
4444 if (subview.empty()) {
4445 // If subview is empty (in scenarios when selStart is at end and changeLength is non-zero) fromUcs4 returns \u0000.
4446 // This should not happen, but just a guard.
4447 return {};
4448 }
4449 Q_ASSERT(subview.length() == changeLength);
4450 return QString::fromUcs4(subview.data(), subview.length());
4451}
4452
4453void Document::processKeystrokeAction(const Action *action, Okular::FormField *ff, const QVariant &newValue, int prevCursorPos, int prevAnchorPos)
4454{
4455 if (action->actionType() != Action::Script) {
4456 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4457 return;
4458 }
4459 // Lookup the page of the FormFieldText
4460 int foundPage = d->findFieldPageNumber(ff);
4461
4462 if (foundPage == -1) {
4463 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4464 return;
4465 }
4466
4467 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4468
4469 /* Set the selStart and selEnd event properties
4470
4471 QString using UTF-16 counts a code point as made up of 1 or 2 16-bit code units.
4472
4473 When encoded using 2 code units, the units are referred to as surrogate pairs.
4474 selectionStart() and selectionEnd() methods evaluate prevCursorPos and prevAnchorPos based on code units during selection.
4475
4476 While this unit-based evaulation is suitable for detecting changes, for providing consistency with Adobe Reader for values of selStart and selEnd,
4477 it would be best to evaluate in terms of code points rather than the code units.
4478
4479 To correct the values of selStart and selEnd accordingly, we iterate over the code units. If a surrogate pair is encountered, then selStart and
4480 selEnd are accordingly decremented.
4481 */
4482 int selStart = std::min(prevCursorPos, prevAnchorPos);
4483 int selEnd = std::max(prevCursorPos, prevAnchorPos);
4484 int codeUnit;
4485 int initialSelStart = selStart;
4486 int initialSelEnd = selEnd;
4487 QString inputString = ff->value().toString();
4488 for (codeUnit = 0; codeUnit < initialSelStart && codeUnit < inputString.size(); codeUnit++) {
4489 if (inputString.at(codeUnit).isHighSurrogate()) {
4490 // skip the low surrogate and decrement selStart and selEnd
4491 codeUnit++;
4492 selStart--;
4493 selEnd--;
4494 }
4495 }
4496 for (; codeUnit < initialSelEnd && codeUnit < inputString.size(); codeUnit++) {
4497 if (inputString.at(codeUnit).isHighSurrogate()) {
4498 // skip the low surrogate and decrement selEnd
4499 codeUnit++;
4500 selEnd--;
4501 }
4502 }
4503 std::u32string oldUcs4 = inputString.toStdU32String();
4504 std::u32string newUcs4 = newValue.toString().toStdU32String();
4505 // It is necessary to count size in terms of code points rather than code units for deletion.
4506 if (oldUcs4.size() - newUcs4.size() == 1 && selStart == selEnd) {
4507 // consider a one character removal as selection of that character and then its removal.
4508 selStart--;
4509 }
4510 event->setSelStart(selStart);
4511 event->setSelEnd(selEnd);
4512 // Use the corrected selStart and selEnd for evaluating the change.
4513 event->setChange(DocumentPrivate::evaluateKeystrokeEventChange(inputString, newValue.toString(), selStart, selEnd));
4514 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4515
4516 d->executeScriptEvent(event, linkscript);
4517
4518 if (event->returnCode()) {
4519 ff->setValue(newValue);
4520 } else {
4522 }
4523}
4524
4526{
4527 // use -1 as default
4528 processKeystrokeAction(action, fft, newValue, -1, -1);
4529}
4530
4532{
4533 bool returnCode = false;
4534 processKeystrokeCommitAction(action, fft, returnCode);
4535}
4536
4537void Document::processKeystrokeCommitAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4538{
4539 if (action->actionType() != Action::Script) {
4540 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4541 return;
4542 }
4543 // Lookup the page of the FormFieldText
4544 int foundPage = d->findFieldPageNumber(ff);
4545
4546 if (foundPage == -1) {
4547 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4548 return;
4549 }
4550
4551 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4552 event->setWillCommit(true);
4553
4554 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4555
4556 d->executeScriptEvent(event, linkscript);
4557
4558 if (!event->returnCode()) {
4561 ff->setValue(QVariant(ff->committedValue()));
4562 } else {
4563 ff->setValue(QVariant(event->value().toString()));
4565 }
4566 returnCode = event->returnCode();
4567}
4568
4570{
4571 if (!action || action->actionType() != Action::Script) {
4572 return;
4573 }
4574
4575 // Lookup the page of the FormFieldText
4576 int foundPage = d->findFieldPageNumber(field);
4577
4578 if (foundPage == -1) {
4579 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4580 return;
4581 }
4582
4583 std::shared_ptr<Event> event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4584
4585 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4586
4587 d->executeScriptEvent(event, linkscript);
4588}
4589
4590void Document::processValidateAction(const Action *action, Okular::FormFieldText *fft, bool &returnCode)
4591{
4592 processValidateAction(action, static_cast<FormField *>(fft), returnCode);
4593}
4594
4595void Document::processValidateAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4596{
4597 if (!action || action->actionType() != Action::Script) {
4598 return;
4599 }
4600
4601 // Lookup the page of the FormFieldText
4602 int foundPage = d->findFieldPageNumber(ff);
4603
4604 if (foundPage == -1) {
4605 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4606 return;
4607 }
4608
4609 std::shared_ptr<Event> event = Event::createFormValidateEvent(ff, d->m_pagesVector[foundPage]);
4610
4611 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4612
4613 d->executeScriptEvent(event, linkscript);
4614 if (!event->returnCode()) {
4617 ff->setValue(QVariant(ff->committedValue()));
4618 } else {
4619 ff->setValue(QVariant(event->value().toString()));
4621 }
4622 returnCode = event->returnCode();
4623}
4624
4626{
4627 if (ff->value().toString() == ff->committedValue()) {
4630 ff->setValue(QVariant(ff->committedValue()));
4631 return;
4632 }
4633
4634 bool returnCode = true;
4637 }
4638
4640 if (returnCode) {
4641 processValidateAction(action, ff, returnCode);
4642 }
4643 }
4644
4645 if (!returnCode) {
4646 return;
4647 } else {
4648 ff->commitValue(ff->value().toString());
4649 }
4650
4652
4654 processFormatAction(action, ff);
4655 } else {
4656 ff->commitFormattedValue(ff->value().toString());
4657 }
4658}
4659
4661{
4662 if (!action || action->actionType() != Action::Script) {
4663 return;
4664 }
4665
4666 Event::EventType eventType = Okular::Event::UnknownEvent;
4667
4668 switch (type) {
4669 case Document::CloseDocument:
4670 eventType = Okular::Event::DocWillClose;
4671 break;
4672 case Document::SaveDocumentStart:
4673 eventType = Okular::Event::DocWillSave;
4674 break;
4675 case Document::SaveDocumentFinish:
4676 eventType = Okular::Event::DocDidSave;
4677 break;
4678 case Document::PrintDocumentStart:
4679 eventType = Okular::Event::DocWillPrint;
4680 break;
4681 case Document::PrintDocumentFinish:
4682 eventType = Okular::Event::DocDidPrint;
4683 break;
4684 }
4685
4686 std::shared_ptr<Event> event = Event::createDocEvent(eventType);
4687
4688 const ScriptAction *linkScript = static_cast<const ScriptAction *>(action);
4689
4690 d->executeScriptEvent(event, linkScript);
4691}
4692
4694{
4695 if (!action || action->actionType() != Action::Script) {
4696 return;
4697 }
4698
4699 Okular::Event::EventType eventType = Okular::Event::UnknownEvent;
4700
4701 switch (fieldMouseEventType) {
4702 case Document::FieldMouseDown:
4703 eventType = Okular::Event::FieldMouseDown;
4704 break;
4706 eventType = Okular::Event::FieldMouseEnter;
4707 break;
4709 eventType = Okular::Event::FieldMouseExit;
4710 break;
4712 eventType = Okular::Event::FieldMouseUp;
4713 break;
4714 }
4715
4716 // Lookup the page of the FormFieldText
4717 int foundPage = d->findFieldPageNumber(ff);
4718
4719 if (foundPage == -1) {
4720 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4721 return;
4722 }
4723
4724 std::shared_ptr<Event> event = Event::createFieldMouseEvent(ff, d->m_pagesVector[foundPage], eventType);
4725
4726 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4727
4728 d->executeScriptEvent(event, linkscript);
4729}
4730
4735
4737{
4738 if (!ref) {
4739 return;
4740 }
4741
4742 const QUrl url = d->giveAbsoluteUrl(ref->fileName());
4743 if (!url.isLocalFile()) {
4744 qCDebug(OkularCoreDebug) << url.url() << "is not a local file.";
4745 return;
4746 }
4747
4748 const QString absFileName = url.toLocalFile();
4749 if (!QFile::exists(absFileName)) {
4750 qCDebug(OkularCoreDebug) << "No such file:" << absFileName;
4751 return;
4752 }
4753
4754 bool handled = false;
4755 Q_EMIT sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled);
4756 if (handled) {
4757 return;
4758 }
4759
4760 static QHash<int, QString> editors;
4761 // init the editors table if empty (on first run, usually)
4762 if (editors.isEmpty()) {
4763 editors = buildEditorsMap();
4764 }
4765
4766 // prefer the editor from the command line
4767 QString p = d->editorCommandOverride;
4768 if (p.isEmpty()) {
4769 QHash<int, QString>::const_iterator it = editors.constFind(SettingsCore::externalEditor());
4770 if (it != editors.constEnd()) {
4771 p = *it;
4772 } else {
4773 p = SettingsCore::externalEditorCommand();
4774 }
4775 }
4776 // custom editor not yet configured
4777 if (p.isEmpty()) {
4778 return;
4779 }
4780
4781 // manually append the %f placeholder if not specified
4782 if (p.indexOf(QLatin1String("%f")) == -1) {
4783 p.append(QLatin1String(" %f"));
4784 }
4785
4786 // replacing the placeholders
4788 map.insert(QLatin1Char('f'), absFileName);
4789 map.insert(QLatin1Char('c'), QString::number(ref->column()));
4790 map.insert(QLatin1Char('l'), QString::number(ref->row()));
4792 if (cmd.isEmpty()) {
4793 return;
4794 }
4795 QStringList args = KShell::splitArgs(cmd);
4796 if (args.isEmpty()) {
4797 return;
4798 }
4799
4800 const QString prog = args.takeFirst();
4801 // Make sure prog is in PATH and not just in the CWD
4802 const QString progFullPath = QStandardPaths::findExecutable(prog);
4803 if (progFullPath.isEmpty()) {
4804 return;
4805 }
4806
4807 KProcess::startDetached(progFullPath, args);
4808}
4809
4810const SourceReference *Document::dynamicSourceReference(int pageNr, double absX, double absY)
4811{
4812 if (!d->m_synctex_scanner) {
4813 return nullptr;
4814 }
4815
4816 const QSizeF dpi = d->m_generator->dpi();
4817
4818 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.width(), absY * 72. / dpi.height()) > 0) {
4819 synctex_node_p node;
4820 // TODO what should we do if there is really more than one node?
4821 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4822 int line = synctex_node_line(node);
4823 int col = synctex_node_column(node);
4824 // column extraction does not seem to be implemented in synctex so far. set the SourceReference default value.
4825 if (col == -1) {
4826 col = 0;
4827 }
4828 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4829
4830 return new Okular::SourceReference(QFile::decodeName(name), line, col);
4831 }
4832 }
4833 return nullptr;
4834}
4835
4837{
4838 if (d->m_generator) {
4839 if (d->m_generator->hasFeature(Generator::PrintNative)) {
4840 return NativePrinting;
4841 }
4842
4843#ifndef Q_OS_WIN
4844 if (d->m_generator->hasFeature(Generator::PrintPostscript)) {
4845 return PostscriptPrinting;
4846 }
4847#endif
4848 }
4849
4850 return NoPrinting;
4851}
4852
4854{
4855 return d->m_generator ? d->m_generator->hasFeature(Generator::PrintToFile) : false;
4856}
4857
4859{
4860 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentStart)) {
4861 processDocumentAction(action, PrintDocumentStart);
4862 }
4863 const Document::PrintError printError = d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4864 if (printError == Document::NoPrintError) {
4865 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentFinish)) {
4866 processDocumentAction(action, PrintDocumentFinish);
4867 }
4868 }
4869 return printError;
4870}
4871
4873{
4874 switch (error) {
4875 case TemporaryFileOpenPrintError:
4876 return i18n("Could not open a temporary file");
4877 case FileConversionPrintError:
4878 return i18n("Print conversion failed");
4879 case PrintingProcessCrashPrintError:
4880 return i18n("Printing process crashed");
4881 case PrintingProcessStartPrintError:
4882 return i18n("Printing process could not start");
4883 case PrintToFilePrintError:
4884 return i18n("Printing to file failed");
4885 case InvalidPrinterStatePrintError:
4886 return i18n("Printer was in invalid state");
4887 case UnableToFindFilePrintError:
4888 return i18n("Unable to find file to print");
4889 case NoFileToPrintError:
4890 return i18n("There was no file to print");
4891 case NoBinaryToPrintError:
4892 return i18n("Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4893 case InvalidPageSizePrintError:
4894 return i18n("The page print size is invalid");
4895 case NoPrintError:
4896 return QString();
4897 case UnknownPrintError:
4898 return QString();
4899 }
4900
4901 return QString();
4902}
4903
4905{
4906 if (d->m_generator) {
4908 return iface ? iface->printConfigurationWidget() : nullptr;
4909 } else {
4910 return nullptr;
4911 }
4912}
4913
4915{
4916 if (!dialog) {
4917 return;
4918 }
4919
4920 // We know it's a BackendConfigDialog, but check anyway
4921 BackendConfigDialog *bcd = dynamic_cast<BackendConfigDialog *>(dialog);
4922 if (!bcd) {
4923 return;
4924 }
4925
4926 // ensure that we have all the generators with settings loaded
4927 QVector<KPluginMetaData> offers = DocumentPrivate::configurableGenerators();
4928 d->loadServiceList(offers);
4929
4930 // We want the generators to be sorted by name so let's fill in a QMap
4931 // this sorts by internal id which is not awesome, but at least the sorting
4932 // is stable between runs that before it wasn't
4933 QMap<QString, GeneratorInfo> sortedGenerators;
4934 QHash<QString, GeneratorInfo>::iterator it = d->m_loadedGenerators.begin();
4935 QHash<QString, GeneratorInfo>::iterator itEnd = d->m_loadedGenerators.end();
4936 for (; it != itEnd; ++it) {
4937 sortedGenerators.insert(it.key(), it.value());
4938 }
4939
4940 bool pagesAdded = false;
4941 QMap<QString, GeneratorInfo>::iterator sit = sortedGenerators.begin();
4942 QMap<QString, GeneratorInfo>::iterator sitEnd = sortedGenerators.end();
4943 for (; sit != sitEnd; ++sit) {
4944 Okular::ConfigInterface *iface = d->generatorConfig(sit.value());
4945 if (iface) {
4946 iface->addPages(dialog);
4947 pagesAdded = true;
4948
4949 if (sit.value().generator == d->m_generator) {
4950 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4951 KPageView *view = bcd->thePageWidget();
4952 view->setCurrentPage(view->model()->index(rowCount - 1, 0));
4953 }
4954 }
4955 }
4956 if (pagesAdded) {
4957 connect(dialog, &KConfigDialog::settingsChanged, this, [this] { d->slotGeneratorConfigChanged(); });
4958 }
4959}
4960
4961QVector<KPluginMetaData> DocumentPrivate::configurableGenerators()
4962{
4963 const QVector<KPluginMetaData> available = availableGenerators();
4965 for (const KPluginMetaData &md : available) {
4966 if (md.rawData().value(QStringLiteral("X-KDE-okularHasInternalSettings")).toBool()) {
4967 result << md;
4968 }
4969 }
4970 return result;
4971}
4972
4974{
4975 if (!d->m_generator) {
4976 return KPluginMetaData();
4977 }
4978
4979 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4980 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4981 return genIt.value().metadata;
4982}
4983
4985{
4986 return DocumentPrivate::configurableGenerators().size();
4987}
4988
4990{
4991 // TODO: make it a static member of DocumentPrivate?
4992 QStringList result = d->m_supportedMimeTypes;
4993 if (result.isEmpty()) {
4994 const QVector<KPluginMetaData> available = DocumentPrivate::availableGenerators();
4995 for (const KPluginMetaData &md : available) {
4996 result << md.mimeTypes();
4997 }
4998
4999 // Remove duplicate mimetypes represented by different names
5000 QMimeDatabase mimeDatabase;
5001 QSet<QMimeType> uniqueMimetypes;
5002 for (const QString &mimeName : std::as_const(result)) {
5003 uniqueMimetypes.insert(mimeDatabase.mimeTypeForName(mimeName));
5004 }
5005 result.clear();
5006 for (const QMimeType &mimeType : uniqueMimetypes) {
5007 result.append(mimeType.name());
5008 }
5009
5010 // Add the Okular archive mimetype
5011 result << QStringLiteral("application/vnd.kde.okular-archive");
5012
5013 // Sorting by mimetype name doesn't make a ton of sense,
5014 // but ensures that the list is ordered the same way every time
5015 std::sort(result.begin(), result.end());
5016
5017 d->m_supportedMimeTypes = result;
5018 }
5019 return result;
5020}
5021
5023{
5024 if (!d->m_generator) {
5025 return false;
5026 }
5027
5028 return d->m_generator->hasFeature(Generator::SwapBackingFile);
5029}
5030
5031bool Document::swapBackingFile(const QString &newFileName, const QUrl &url)
5032{
5033 if (!d->m_generator) {
5034 return false;
5035 }
5036
5037 if (!d->m_generator->hasFeature(Generator::SwapBackingFile)) {
5038 return false;
5039 }
5040
5041 // Save metadata about the file we're about to close
5042 d->saveDocumentInfo();
5043
5044 d->clearAndWaitForRequests();
5045
5046 qCDebug(OkularCoreDebug) << "Swapping backing file to" << newFileName;
5047 QVector<Page *> newPagesVector;
5048 Generator::SwapBackingFileResult result = d->m_generator->swapBackingFile(newFileName, newPagesVector);
5049 if (result != Generator::SwapBackingFileError) {
5050 QList<ObjectRect *> rectsToDelete;
5051 QList<Annotation *> annotationsToDelete;
5052 QSet<PagePrivate *> pagePrivatesToDelete;
5053
5054 if (result == Generator::SwapBackingFileReloadInternalData) {
5055 // Here we need to replace everything that the old generator
5056 // had created with what the new one has without making it look like
5057 // we have actually closed and opened the file again
5058
5059 // Simple sanity check
5060 if (newPagesVector.count() != d->m_pagesVector.count()) {
5061 return false;
5062 }
5063
5064 // Update the undo stack contents
5065 for (int i = 0; i < d->m_undoStack->count(); ++i) {
5066 // Trust me on the const_cast ^_^
5067 QUndoCommand *uc = const_cast<QUndoCommand *>(d->m_undoStack->command(i));
5068 if (OkularUndoCommand *ouc = dynamic_cast<OkularUndoCommand *>(uc)) {
5069 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
5070 if (!success) {
5071 qWarning() << "Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
5072 return false;
5073 }
5074 } else {
5075 qWarning() << "Document::swapBackingFile: Unhandled undo command" << uc;
5076 return false;
5077 }
5078 }
5079
5080 for (int i = 0; i < d->m_pagesVector.count(); ++i) {
5081 // switch the PagePrivate* from newPage to oldPage
5082 // this way everyone still holding Page* doesn't get
5083 // disturbed by it
5084 Page *oldPage = d->m_pagesVector[i];
5085 Page *newPage = newPagesVector[i];
5086 newPage->d->adoptGeneratedContents(oldPage->d);
5087
5088 pagePrivatesToDelete << oldPage->d;
5089 oldPage->d = newPage->d;
5090 oldPage->d->m_page = oldPage;
5091 oldPage->d->m_doc = d;
5092 newPage->d = nullptr;
5093
5094 annotationsToDelete << oldPage->m_annotations;
5095 rectsToDelete << oldPage->m_rects;
5096 oldPage->m_annotations = newPage->m_annotations;
5097 oldPage->m_rects = newPage->m_rects;
5098 }
5099 qDeleteAll(newPagesVector);
5100 }
5101
5102 d->m_url = url;
5103 d->m_docFileName = newFileName;
5104 d->updateMetadataXmlNameAndDocSize();
5105 d->m_bookmarkManager->setUrl(d->m_url);
5106 d->m_documentInfo = DocumentInfo();
5107 d->m_documentInfoAskedKeys.clear();
5108
5109 if (d->m_synctex_scanner) {
5110 synctex_scanner_free(d->m_synctex_scanner);
5111 d->m_synctex_scanner = synctex_scanner_new_with_output_file(QFile::encodeName(newFileName).constData(), nullptr, 1);
5112 if (!d->m_synctex_scanner && QFile::exists(newFileName + QLatin1String("sync"))) {
5113 d->loadSyncFile(newFileName);
5114 }
5115 }
5116
5117 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::UrlChanged));
5118
5119 qDeleteAll(annotationsToDelete);
5120 qDeleteAll(rectsToDelete);
5121 qDeleteAll(pagePrivatesToDelete);
5122
5123 return true;
5124 } else {
5125 return false;
5126 }
5127}
5128
5129bool Document::swapBackingFileArchive(const QString &newFileName, const QUrl &url)
5130{
5131 qCDebug(OkularCoreDebug) << "Swapping backing archive to" << newFileName;
5132
5133 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
5134 if (!newArchive) {
5135 return false;
5136 }
5137
5138 const QString tempFileName = newArchive->document.fileName();
5139
5140 const bool success = swapBackingFile(tempFileName, url);
5141
5142 if (success) {
5143 delete d->m_archiveData;
5144 d->m_archiveData = newArchive;
5145 }
5146
5147 return success;
5148}
5149
5151{
5152 if (clean) {
5153 d->m_undoStack->setClean();
5154 } else {
5155 d->m_undoStack->resetClean();
5156 }
5157}
5158
5159bool Document::isHistoryClean() const
5160{
5161 return d->m_undoStack->isClean();
5162}
5163
5165{
5166 d->m_undoStack->clear();
5167}
5168
5170{
5171 if (!d->m_generator) {
5172 return false;
5173 }
5174 Q_ASSERT(!d->m_generatorName.isEmpty());
5175
5176 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5177 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5178 SaveInterface *saveIface = d->generatorSave(genIt.value());
5179 if (!saveIface) {
5180 return false;
5181 }
5182
5183 return saveIface->supportsOption(SaveInterface::SaveChanges);
5184}
5185
5187{
5188 switch (cap) {
5190 /* Assume that if the generator supports saving, forms can be saved.
5191 * We have no means to actually query the generator at the moment
5192 * TODO: Add some method to query the generator in SaveInterface */
5193 return canSaveChanges();
5194
5196 return d->canAddAnnotationsNatively();
5197 }
5198
5199 return false;
5200}
5201
5202bool Document::saveChanges(const QString &fileName)
5203{
5204 QString errorText;
5205 return saveChanges(fileName, &errorText);
5206}
5207
5208bool Document::saveChanges(const QString &fileName, QString *errorText)
5209{
5210 if (!d->m_generator || fileName.isEmpty()) {
5211 return false;
5212 }
5213 Q_ASSERT(!d->m_generatorName.isEmpty());
5214
5215 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5216 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5217 SaveInterface *saveIface = d->generatorSave(genIt.value());
5218 if (!saveIface || !saveIface->supportsOption(SaveInterface::SaveChanges)) {
5219 return false;
5220 }
5221
5222 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentStart)) {
5223 processDocumentAction(action, SaveDocumentStart);
5224 }
5225
5226 bool success = saveIface->save(fileName, SaveInterface::SaveChanges, errorText);
5227 if (success) {
5228 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentFinish)) {
5229 processDocumentAction(action, SaveDocumentFinish);
5230 }
5231 }
5232 return success;
5233}
5234
5236{
5237 if (!view) {
5238 return;
5239 }
5240
5241 Document *viewDoc = view->viewDocument();
5242 if (viewDoc) {
5243 // check if already registered for this document
5244 if (viewDoc == this) {
5245 return;
5246 }
5247
5248 viewDoc->unregisterView(view);
5249 }
5250
5251 d->m_views.insert(view);
5252 view->d_func()->document = d;
5253}
5254
5256{
5257 if (!view) {
5258 return;
5259 }
5260
5261 const Document *viewDoc = view->viewDocument();
5262 if (!viewDoc || viewDoc != this) {
5263 return;
5264 }
5265
5266 view->d_func()->document = nullptr;
5267 d->m_views.remove(view);
5268}
5269
5271{
5272 if (d->m_generator) {
5273 return d->m_generator->requestFontData(font);
5274 }
5275
5276 return {};
5277}
5278
5279ArchiveData *DocumentPrivate::unpackDocumentArchive(const QString &archivePath)
5280{
5281 QMimeDatabase db;
5282 const QMimeType mime = db.mimeTypeForFile(archivePath, QMimeDatabase::MatchExtension);
5283 if (!mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) {
5284 return nullptr;
5285 }
5286
5287 KZip okularArchive(archivePath);
5288 if (!okularArchive.open(QIODevice::ReadOnly)) {
5289 return nullptr;
5290 }
5291
5292 const KArchiveDirectory *mainDir = okularArchive.directory();
5293
5294 // Check the archive doesn't have folders, we don't create them when saving the archive
5295 // and folders mean paths and paths mean path traversal issues
5296 const QStringList mainDirEntries = mainDir->entries();
5297 for (const QString &entry : mainDirEntries) {
5298 if (mainDir->entry(entry)->isDirectory()) {
5299 qWarning() << "Warning: Found a directory inside" << archivePath << " - Okular does not create files like that so it is most probably forged.";
5300 return nullptr;
5301 }
5302 }
5303
5304 const KArchiveEntry *mainEntry = mainDir->entry(QStringLiteral("content.xml"));
5305 if (!mainEntry || !mainEntry->isFile()) {
5306 return nullptr;
5307 }
5308
5309 std::unique_ptr<QIODevice> mainEntryDevice(static_cast<const KZipFileEntry *>(mainEntry)->createDevice());
5310 QDomDocument doc;
5311 if (!doc.setContent(mainEntryDevice.get())) {
5312 return nullptr;
5313 }
5314 mainEntryDevice.reset();
5315
5316 QDomElement root = doc.documentElement();
5317 if (root.tagName() != QLatin1String("OkularArchive")) {
5318 return nullptr;
5319 }
5320
5321 QString documentFileName;
5322 QString metadataFileName;
5323 QDomElement el = root.firstChild().toElement();
5324 for (; !el.isNull(); el = el.nextSibling().toElement()) {
5325 if (el.tagName() == QLatin1String("Files")) {
5326 QDomElement fileEl = el.firstChild().toElement();
5327 for (; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement()) {
5328 if (fileEl.tagName() == QLatin1String("DocumentFileName")) {
5329 documentFileName = fileEl.text();
5330 } else if (fileEl.tagName() == QLatin1String("MetadataFileName")) {
5331 metadataFileName = fileEl.text();
5332 }
5333 }
5334 }
5335 }
5336 if (documentFileName.isEmpty()) {
5337 return nullptr;
5338 }
5339
5340 const KArchiveEntry *docEntry = mainDir->entry(documentFileName);
5341 if (!docEntry || !docEntry->isFile()) {
5342 return nullptr;
5343 }
5344
5345 std::unique_ptr<ArchiveData> archiveData(new ArchiveData());
5346 const int dotPos = documentFileName.indexOf(QLatin1Char('.'));
5347 if (dotPos != -1) {
5348 archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos));
5349 }
5350 if (!archiveData->document.open()) {
5351 return nullptr;
5352 }
5353
5354 archiveData->originalFileName = documentFileName;
5355
5356 {
5357 std::unique_ptr<QIODevice> docEntryDevice(static_cast<const KZipFileEntry *>(docEntry)->createDevice());
5358 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5359 archiveData->document.close();
5360 }
5361
5362 const KArchiveEntry *metadataEntry = mainDir->entry(metadataFileName);
5363 if (metadataEntry && metadataEntry->isFile()) {
5364 std::unique_ptr<QIODevice> metadataEntryDevice(static_cast<const KZipFileEntry *>(metadataEntry)->createDevice());
5365 archiveData->metadataFile.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.xml"));
5366 if (archiveData->metadataFile.open()) {
5367 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5368 archiveData->metadataFile.close();
5369 }
5370 }
5371
5372 return archiveData.release();
5373}
5374
5375Document::OpenResult Document::openDocumentArchive(const QString &docFile, const QUrl &url, const QString &password)
5376{
5377 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5378 if (!d->m_archiveData) {
5379 return OpenError;
5380 }
5381
5382 const QString tempFileName = d->m_archiveData->document.fileName();
5383 QMimeDatabase db;
5384 const QMimeType docMime = db.mimeTypeForFile(tempFileName, QMimeDatabase::MatchExtension);
5385 const OpenResult ret = openDocument(tempFileName, url, docMime, password);
5386
5387 if (ret != OpenSuccess) {
5388 delete d->m_archiveData;
5389 d->m_archiveData = nullptr;
5390 }
5391
5392 return ret;
5393}
5394
5396{
5397 if (!d->m_generator) {
5398 return false;
5399 }
5400
5401 /* If we opened an archive, use the name of original file (eg foo.pdf)
5402 * instead of the archive's one (eg foo.okular) */
5403 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5404 if (docFileName == QLatin1String("-")) {
5405 return false;
5406 }
5407
5408 QString docPath = d->m_docFileName;
5409 const QFileInfo fi(docPath);
5410 if (fi.isSymLink()) {
5411 docPath = fi.symLinkTarget();
5412 }
5413
5414 KZip okularArchive(fileName);
5415 if (!okularArchive.open(QIODevice::WriteOnly)) {
5416 return false;
5417 }
5418
5419 const KUser user;
5420#ifndef Q_OS_WIN
5421 const KUserGroup userGroup(user.groupId());
5422#else
5423 const KUserGroup userGroup(QStringLiteral(""));
5424#endif
5425
5426 QDomDocument contentDoc(QStringLiteral("OkularArchive"));
5427 QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
5428 contentDoc.appendChild(xmlPi);
5429 QDomElement root = contentDoc.createElement(QStringLiteral("OkularArchive"));
5430 contentDoc.appendChild(root);
5431
5432 QDomElement filesNode = contentDoc.createElement(QStringLiteral("Files"));
5433 root.appendChild(filesNode);
5434
5435 QDomElement fileNameNode = contentDoc.createElement(QStringLiteral("DocumentFileName"));
5436 filesNode.appendChild(fileNameNode);
5437 fileNameNode.appendChild(contentDoc.createTextNode(docFileName));
5438
5439 QDomElement metadataFileNameNode = contentDoc.createElement(QStringLiteral("MetadataFileName"));
5440 filesNode.appendChild(metadataFileNameNode);
5441 metadataFileNameNode.appendChild(contentDoc.createTextNode(QStringLiteral("metadata.xml")));
5442
5443 // If the generator can save annotations natively, do it
5444 QTemporaryFile modifiedFile;
5445 bool annotationsSavedNatively = false;
5446 bool formsSavedNatively = false;
5447 if (d->canAddAnnotationsNatively() || canSaveChanges(SaveFormsCapability)) {
5448 if (!modifiedFile.open()) {
5449 return false;
5450 }
5451
5452 const QString modifiedFileName = modifiedFile.fileName();
5453
5454 modifiedFile.close(); // We're only interested in the file name
5455
5456 QString errorText;
5457 if (saveChanges(modifiedFileName, &errorText)) {
5458 docPath = modifiedFileName; // Save this instead of the original file
5459 annotationsSavedNatively = d->canAddAnnotationsNatively();
5460 formsSavedNatively = canSaveChanges(SaveFormsCapability);
5461 } else {
5462 qCWarning(OkularCoreDebug) << "saveChanges failed: " << errorText;
5463 qCDebug(OkularCoreDebug) << "Falling back to saving a copy of the original file";
5464 }
5465 }
5466
5467 PageItems saveWhat = None;
5468 if (!annotationsSavedNatively) {
5469 saveWhat |= AnnotationPageItems;
5470 }
5471 if (!formsSavedNatively) {
5472 saveWhat |= FormFieldPageItems;
5473 }
5474
5475 QTemporaryFile metadataFile;
5476 if (!d->savePageDocumentInfo(&metadataFile, saveWhat)) {
5477 return false;
5478 }
5479
5480 const QByteArray contentDocXml = contentDoc.toByteArray();
5481 const mode_t perm = 0100644;
5482 okularArchive.writeFile(QStringLiteral("content.xml"), contentDocXml, perm, user.loginName(), userGroup.name());
5483
5484 okularArchive.addLocalFile(docPath, docFileName);
5485 okularArchive.addLocalFile(metadataFile.fileName(), QStringLiteral("metadata.xml"));
5486
5487 if (!okularArchive.close()) {
5488 return false;
5489 }
5490
5491 return true;
5492}
5493
5495{
5496 if (!d->m_archiveData) {
5497 return false;
5498 }
5499
5500 // Remove existing file, if present (QFile::copy doesn't overwrite by itself)
5501 QFile::remove(destFileName);
5502
5503 return d->m_archiveData->document.copy(destFileName);
5504}
5505
5507{
5508 int landscape, portrait;
5509
5510 // if some pages are landscape and others are not, the most common wins, as
5511 // QPrinter does not accept a per-page setting
5512 landscape = 0;
5513 portrait = 0;
5514 for (uint i = 0; i < pages(); i++) {
5515 const Okular::Page *currentPage = page(i);
5516 double width = currentPage->width();
5517 double height = currentPage->height();
5518 if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) {
5519 std::swap(width, height);
5520 }
5521 if (width > height) {
5522 landscape++;
5523 } else {
5524 portrait++;
5525 }
5526 }
5527 return (landscape > portrait) ? QPageLayout::Landscape : QPageLayout::Portrait;
5528}
5529
5531{
5532 d->m_annotationEditingEnabled = enable;
5533 foreachObserver(notifySetup(d->m_pagesVector, 0));
5534}
5535
5536void Document::walletDataForFile(const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey) const
5537{
5538 if (d->m_generator) {
5539 d->m_generator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5540 } else if (d->m_walletGenerator) {
5541 d->m_walletGenerator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5542 }
5543}
5544
5546{
5547 return d->m_docdataMigrationNeeded;
5548}
5549
5551{
5552 if (d->m_docdataMigrationNeeded) {
5553 d->m_docdataMigrationNeeded = false;
5554 foreachObserver(notifySetup(d->m_pagesVector, 0));
5555 }
5556}
5557
5559{
5560 return d->m_generator ? d->m_generator->layersModel() : nullptr;
5561}
5562
5564{
5565 return d->m_openError;
5566}
5567
5568QByteArray Document::requestSignedRevisionData(const Okular::SignatureInfo &info)
5569{
5570 QFile f(d->m_docFileName);
5571 if (!f.open(QIODevice::ReadOnly)) {
5572 Q_EMIT error(i18n("Could not open '%1'. File does not exist", d->m_docFileName), -1);
5573 return {};
5574 }
5575
5576 const QList<qint64> byteRange = info.signedRangeBounds();
5577 f.seek(byteRange.first());
5578 QByteArray data = f.read(byteRange.last() - byteRange.first());
5579 f.close();
5580
5581 return data;
5582}
5583
5584void Document::refreshPixmaps(int pageNumber)
5585{
5586 d->refreshPixmaps(pageNumber);
5587}
5588
5589void DocumentPrivate::executeScript(const QString &function)
5590{
5591 if (!m_scripter) {
5592 m_scripter = new Scripter(this);
5593 }
5594 m_scripter->execute(JavaScript, function);
5595}
5596
5597void DocumentPrivate::requestDone(PixmapRequest *req)
5598{
5599 if (!req) {
5600 return;
5601 }
5602
5603 if (!m_generator || m_closingLoop) {
5604 m_pixmapRequestsMutex.lock();
5605 m_executingPixmapRequests.remove(req);
5606 m_pixmapRequestsMutex.unlock();
5607 delete req;
5608 if (m_closingLoop) {
5609 m_closingLoop->exit();
5610 }
5611 return;
5612 }
5613
5614#ifndef NDEBUG
5615 if (!m_generator->canGeneratePixmap()) {
5616 qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state.";
5617 }
5618#endif
5619
5620 if (!req->shouldAbortRender()) {
5621 // [MEM] 1.1 find and remove a previous entry for the same page and id
5622 std::list<AllocatedPixmap *>::iterator aIt = m_allocatedPixmaps.begin();
5623 std::list<AllocatedPixmap *>::iterator aEnd = m_allocatedPixmaps.end();
5624 for (; aIt != aEnd; ++aIt) {
5625 if ((*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer()) {
5626 AllocatedPixmap *p = *aIt;
5627 m_allocatedPixmaps.erase(aIt);
5628 m_allocatedPixmapsTotalMemory -= p->memory;
5629 delete p;
5630 break;
5631 }
5632 }
5633
5634 DocumentObserver *observer = req->observer();
5635 if (m_observers.contains(observer)) {
5636 // [MEM] 1.2 append memory allocation descriptor to the FIFO
5637 qulonglong memoryBytes = 0;
5638 const TilesManager *tm = req->d->tilesManager();
5639 if (tm) {
5640 memoryBytes = tm->totalMemory();
5641 } else {
5642 memoryBytes = 4 * req->width() * req->height();
5643 }
5644
5645 AllocatedPixmap *memoryPage = new AllocatedPixmap(req->observer(), req->pageNumber(), memoryBytes);
5646 m_allocatedPixmaps.push_back(memoryPage);
5647 m_allocatedPixmapsTotalMemory += memoryBytes;
5648
5649 // 2. notify an observer that its pixmap changed
5651 }
5652#ifndef NDEBUG
5653 else {
5654 qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer;
5655 }
5656#endif
5657 }
5658
5659 // 3. delete request
5660 m_pixmapRequestsMutex.lock();
5661 m_executingPixmapRequests.remove(req);
5662 m_pixmapRequestsMutex.unlock();
5663 delete req;
5664
5665 // 4. start a new generation if some is pending
5666 m_pixmapRequestsMutex.lock();
5667 bool hasPixmaps = !m_pixmapRequestsStack.empty();
5668 m_pixmapRequestsMutex.unlock();
5669 if (hasPixmaps) {
5670 sendGeneratorPixmapRequest();
5671 }
5672}
5673
5674void DocumentPrivate::setPageBoundingBox(int page, const NormalizedRect &boundingBox)
5675{
5676 Page *kp = m_pagesVector[page];
5677 if (!m_generator || !kp) {
5678 return;
5679 }
5680
5681 if (kp->boundingBox() == boundingBox) {
5682 return;
5683 }
5684 kp->setBoundingBox(boundingBox);
5685
5686 // notify observers about the change
5687 foreachObserverD(notifyPageChanged(page, DocumentObserver::BoundingBox));
5688
5689 // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate.
5690 // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away.
5691 // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker.
5692 // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off).
5693}
5694
5695void DocumentPrivate::calculateMaxTextPages()
5696{
5697 int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB
5698 switch (SettingsCore::memoryLevel()) {
5699 case SettingsCore::EnumMemoryLevel::Low:
5700 m_maxAllocatedTextPages = multipliers * 2;
5701 break;
5702
5703 case SettingsCore::EnumMemoryLevel::Normal:
5704 m_maxAllocatedTextPages = multipliers * 50;
5705 break;
5706
5707 case SettingsCore::EnumMemoryLevel::Aggressive:
5708 m_maxAllocatedTextPages = multipliers * 250;
5709 break;
5710
5711 case SettingsCore::EnumMemoryLevel::Greedy:
5712 m_maxAllocatedTextPages = multipliers * 1250;
5713 break;
5714 }
5715}
5716
5717void DocumentPrivate::textGenerationDone(Page *page)
5718{
5719 if (!m_pageController) {
5720 return;
5721 }
5722
5723 // 1. If we reached the cache limit, delete the first text page from the fifo
5724 if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) {
5725 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
5726 if (pageToKick != page->number()) // this should never happen but better be safe than sorry
5727 {
5728 m_pagesVector.at(pageToKick)->setTextPage(nullptr); // deletes the textpage
5729 }
5730 }
5731
5732 // 2. Add the page to the fifo of generated text pages
5733 m_allocatedTextPagesFifo.append(page->number());
5734}
5735
5737{
5738 d->setRotationInternal(r, true);
5739}
5740
5741void DocumentPrivate::setRotationInternal(int r, bool notify)
5742{
5743 Rotation rotation = (Rotation)r;
5744 if (!m_generator || (m_rotation == rotation)) {
5745 return;
5746 }
5747
5748 // tell the pages to rotate
5749 QVector<Okular::Page *>::const_iterator pIt = m_pagesVector.constBegin();
5750 QVector<Okular::Page *>::const_iterator pEnd = m_pagesVector.constEnd();
5751 for (; pIt != pEnd; ++pIt) {
5752 (*pIt)->d->rotateAt(rotation);
5753 }
5754 if (notify) {
5755 // notify the generator that the current rotation has changed
5756 m_generator->rotationChanged(rotation, m_rotation);
5757 }
5758 // set the new rotation
5759 m_rotation = rotation;
5760
5761 if (notify) {
5762 foreachObserverD(notifySetup(m_pagesVector, DocumentObserver::NewLayoutForPages));
5763 foreachObserverD(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations));
5764 }
5765 qCDebug(OkularCoreDebug) << "Rotated:" << r;
5766}
5767
5769{
5770 if (!d->m_generator || !d->m_generator->hasFeature(Generator::PageSizes)) {
5771 return;
5772 }
5773
5774 if (d->m_pageSizes.isEmpty()) {
5775 d->m_pageSizes = d->m_generator->pageSizes();
5776 }
5777 int sizeid = d->m_pageSizes.indexOf(size);
5778 if (sizeid == -1) {
5779 return;
5780 }
5781
5782 // tell the pages to change size
5783 QVector<Okular::Page *>::const_iterator pIt = d->m_pagesVector.constBegin();
5784 QVector<Okular::Page *>::const_iterator pEnd = d->m_pagesVector.constEnd();
5785 for (; pIt != pEnd; ++pIt) {
5786 (*pIt)->d->changeSize(size);
5787 }
5788 // clear 'memory allocation' descriptors
5789 qDeleteAll(d->m_allocatedPixmaps);
5790 d->m_allocatedPixmaps.clear();
5791 d->m_allocatedPixmapsTotalMemory = 0;
5792 // notify the generator that the current page size has changed
5793 d->m_generator->pageSizeChanged(size, d->m_pageSize);
5794 // set the new page size
5795 d->m_pageSize = size;
5796
5797 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::NewLayoutForPages));
5798 foreachObserver(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights));
5799 qCDebug(OkularCoreDebug) << "New PageSize id:" << sizeid;
5800}
5801
5802/** DocumentViewport **/
5803
5805 : pageNumber(n)
5806{
5807 // default settings
5808 rePos.enabled = false;
5809 rePos.normalizedX = 0.5;
5810 rePos.normalizedY = 0.0;
5811 rePos.pos = Center;
5812 autoFit.enabled = false;
5813 autoFit.width = false;
5814 autoFit.height = false;
5815}
5816
5818 : pageNumber(-1)
5819{
5820 // default settings (maybe overridden below)
5821 rePos.enabled = false;
5822 rePos.normalizedX = 0.5;
5823 rePos.normalizedY = 0.0;
5824 rePos.pos = Center;
5825 autoFit.enabled = false;
5826 autoFit.width = false;
5827 autoFit.height = false;
5828
5829 // check for string presence
5830 if (xmlDesc.isEmpty()) {
5831 return;
5832 }
5833
5834 // decode the string
5835 bool ok;
5836 int field = 0;
5837 QString token = xmlDesc.section(QLatin1Char(';'), field, field);
5838 while (!token.isEmpty()) {
5839 // decode the current token
5840 if (field == 0) {
5841 pageNumber = token.toInt(&ok);
5842 if (!ok) {
5843 return;
5844 }
5845 } else if (token.startsWith(QLatin1String("C1"))) {
5846 rePos.enabled = true;
5847 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5848 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5849 rePos.pos = Center;
5850 } else if (token.startsWith(QLatin1String("C2"))) {
5851 rePos.enabled = true;
5852 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5853 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5854 if (token.section(QLatin1Char(':'), 3, 3).toInt() == 1) {
5855 rePos.pos = Center;
5856 } else {
5857 rePos.pos = TopLeft;
5858 }
5859 } else if (token.startsWith(QLatin1String("AF1"))) {
5860 autoFit.enabled = true;
5861 autoFit.width = token.section(QLatin1Char(':'), 1, 1) == QLatin1String("T");
5862 autoFit.height = token.section(QLatin1Char(':'), 2, 2) == QLatin1String("T");
5863 }
5864 // proceed tokenizing string
5865 field++;
5866 token = xmlDesc.section(QLatin1Char(';'), field, field);
5867 }
5868}
5869
5871{
5872 // start string with page number
5874 // if has center coordinates, save them on string
5875 if (rePos.enabled) {
5876 s += QStringLiteral(";C2:") + QString::number(rePos.normalizedX) + QLatin1Char(':') + QString::number(rePos.normalizedY) + QLatin1Char(':') + QString::number(rePos.pos);
5877 }
5878 // if has autofit enabled, save its state on string
5879 if (autoFit.enabled) {
5880 s += QStringLiteral(";AF1:") + (autoFit.width ? QLatin1Char('T') : QLatin1Char('F')) + QLatin1Char(':') + (autoFit.height ? QLatin1Char('T') : QLatin1Char('F'));
5881 }
5882 return s;
5883}
5884
5886{
5887 return pageNumber >= 0;
5888}
5889
5891{
5892 bool equal = (pageNumber == other.pageNumber) && (rePos.enabled == other.rePos.enabled) && (autoFit.enabled == other.autoFit.enabled);
5893 if (!equal) {
5894 return false;
5895 }
5896 if (rePos.enabled && ((rePos.normalizedX != other.rePos.normalizedX) || (rePos.normalizedY != other.rePos.normalizedY) || rePos.pos != other.rePos.pos)) {
5897 return false;
5898 }
5899 if (autoFit.enabled && ((autoFit.width != other.autoFit.width) || (autoFit.height != other.autoFit.height))) {
5900 return false;
5901 }
5902 return true;
5903}
5904
5905bool DocumentViewport::operator<(const DocumentViewport &other) const
5906{
5907 // TODO: Check autoFit and Position
5908
5909 if (pageNumber != other.pageNumber) {
5910 return pageNumber < other.pageNumber;
5911 }
5912
5913 if (!rePos.enabled && other.rePos.enabled) {
5914 return true;
5915 }
5916
5917 if (!other.rePos.enabled) {
5918 return false;
5919 }
5920
5921 if (rePos.normalizedY != other.rePos.normalizedY) {
5922 return rePos.normalizedY < other.rePos.normalizedY;
5923 }
5924
5925 return rePos.normalizedX < other.rePos.normalizedX;
5926}
5927
5928/** DocumentInfo **/
5929
5931 : d(new DocumentInfoPrivate())
5932{
5933}
5934
5936 : d(new DocumentInfoPrivate())
5937{
5938 *this = info;
5939}
5940
5941DocumentInfo &DocumentInfo::operator=(const DocumentInfo &info)
5942{
5943 if (this != &info) {
5944 d->values = info.d->values;
5945 d->titles = info.d->titles;
5946 }
5947 return *this;
5948}
5949
5950DocumentInfo::~DocumentInfo()
5951{
5952 delete d;
5953}
5954
5955void DocumentInfo::set(const QString &key, const QString &value, const QString &title)
5956{
5957 d->values[key] = value;
5958 d->titles[key] = title;
5959}
5960
5961void DocumentInfo::set(Key key, const QString &value)
5962{
5963 d->values[getKeyString(key)] = value;
5964}
5965
5967{
5968 return d->values.keys();
5969}
5970
5972{
5973 return get(getKeyString(key));
5974}
5975
5977{
5978 return d->values[key];
5979}
5980
5982{
5983 switch (key) {
5984 case Title:
5985 return QStringLiteral("title");
5986 break;
5987 case Subject:
5988 return QStringLiteral("subject");
5989 break;
5990 case Description:
5991 return QStringLiteral("description");
5992 break;
5993 case Author:
5994 return QStringLiteral("author");
5995 break;
5996 case Creator:
5997 return QStringLiteral("creator");
5998 break;
5999 case Producer:
6000 return QStringLiteral("producer");
6001 break;
6002 case Copyright:
6003 return QStringLiteral("copyright");
6004 break;
6005 case Pages:
6006 return QStringLiteral("pages");
6007 break;
6008 case CreationDate:
6009 return QStringLiteral("creationDate");
6010 break;
6011 case ModificationDate:
6012 return QStringLiteral("modificationDate");
6013 break;
6014 case MimeType:
6015 return QStringLiteral("mimeType");
6016 break;
6017 case Category:
6018 return QStringLiteral("category");
6019 break;
6020 case Keywords:
6021 return QStringLiteral("keywords");
6022 break;
6023 case FilePath:
6024 return QStringLiteral("filePath");
6025 break;
6026 case DocumentSize:
6027 return QStringLiteral("documentSize");
6028 break;
6029 case PagesSize:
6030 return QStringLiteral("pageSize");
6031 break;
6032 default:
6033 qCWarning(OkularCoreDebug) << "Unknown" << key;
6034 return QString();
6035 break;
6036 }
6037}
6038
6040{
6041 if (key == QLatin1String("title")) {
6042 return Title;
6043 } else if (key == QLatin1String("subject")) {
6044 return Subject;
6045 } else if (key == QLatin1String("description")) {
6046 return Description;
6047 } else if (key == QLatin1String("author")) {
6048 return Author;
6049 } else if (key == QLatin1String("creator")) {
6050 return Creator;
6051 } else if (key == QLatin1String("producer")) {
6052 return Producer;
6053 } else if (key == QLatin1String("copyright")) {
6054 return Copyright;
6055 } else if (key == QLatin1String("pages")) {
6056 return Pages;
6057 } else if (key == QLatin1String("creationDate")) {
6058 return CreationDate;
6059 } else if (key == QLatin1String("modificationDate")) {
6060 return ModificationDate;
6061 } else if (key == QLatin1String("mimeType")) {
6062 return MimeType;
6063 } else if (key == QLatin1String("category")) {
6064 return Category;
6065 } else if (key == QLatin1String("keywords")) {
6066 return Keywords;
6067 } else if (key == QLatin1String("filePath")) {
6068 return FilePath;
6069 } else if (key == QLatin1String("documentSize")) {
6070 return DocumentSize;
6071 } else if (key == QLatin1String("pageSize")) {
6072 return PagesSize;
6073 } else {
6074 return Invalid;
6075 }
6076}
6077
6079{
6080 switch (key) {
6081 case Title:
6082 return i18n("Title");
6083 break;
6084 case Subject:
6085 return i18n("Subject");
6086 break;
6087 case Description:
6088 return i18n("Description");
6089 break;
6090 case Author:
6091 return i18n("Author");
6092 break;
6093 case Creator:
6094 return i18n("Creator");
6095 break;
6096 case Producer:
6097 return i18n("Producer");
6098 break;
6099 case Copyright:
6100 return i18n("Copyright");
6101 break;
6102 case Pages:
6103 return i18n("Pages");
6104 break;
6105 case CreationDate:
6106 return i18n("Created");
6107 break;
6108 case ModificationDate:
6109 return i18n("Modified");
6110 break;
6111 case MimeType:
6112 return i18n("MIME Type");
6113 break;
6114 case Category:
6115 return i18n("Category");
6116 break;
6117 case Keywords:
6118 return i18n("Keywords");
6119 break;
6120 case FilePath:
6121 return i18n("File Path");
6122 break;
6123 case DocumentSize:
6124 return i18n("File Size");
6125 break;
6126 case PagesSize:
6127 return i18n("Page Size");
6128 break;
6129 default:
6130 return QString();
6131 break;
6132 }
6133}
6134
6136{
6137 QString title = getKeyTitle(getKeyFromString(key));
6138 if (title.isEmpty()) {
6139 title = d->titles[key];
6140 }
6141 return title;
6142}
6143
6144/** DocumentSynopsis **/
6145
6147 : QDomDocument(QStringLiteral("DocumentSynopsis"))
6148{
6149 // void implementation, only subclassed for naming
6150}
6151
6153 : QDomDocument(document)
6154{
6155}
6156
6157/** EmbeddedFile **/
6158
6162
6166
6168 : pageNumber(page)
6169 , rect(rectangle)
6170{
6171}
6172
6173/** NewSignatureData **/
6174
6175struct Okular::NewSignatureDataPrivate {
6176 NewSignatureDataPrivate() = default;
6177
6178 QString certNickname;
6179 QString certSubjectCommonName;
6180 QString password;
6181 QString documentPassword;
6182 QString location;
6183 QString reason;
6184 QString backgroundImagePath;
6185 double fontSize = 10;
6186 double leftFontSize = 20;
6187 int page = -1;
6188 NormalizedRect boundingRectangle;
6189};
6190
6191NewSignatureData::NewSignatureData()
6192 : d(new NewSignatureDataPrivate())
6193{
6194}
6195
6196NewSignatureData::~NewSignatureData()
6197{
6198 delete d;
6199}
6200
6201QString NewSignatureData::certNickname() const
6202{
6203 return d->certNickname;
6204}
6205
6206void NewSignatureData::setCertNickname(const QString &certNickname)
6207{
6208 d->certNickname = certNickname;
6209}
6210
6211QString NewSignatureData::certSubjectCommonName() const
6212{
6213 return d->certSubjectCommonName;
6214}
6215
6216void NewSignatureData::setCertSubjectCommonName(const QString &certSubjectCommonName)
6217{
6218 d->certSubjectCommonName = certSubjectCommonName;
6219}
6220
6221QString NewSignatureData::password() const
6222{
6223 return d->password;
6224}
6225
6226void NewSignatureData::setPassword(const QString &password)
6227{
6228 d->password = password;
6229}
6230
6231int NewSignatureData::page() const
6232{
6233 return d->page;
6234}
6235
6236void NewSignatureData::setPage(int page)
6237{
6238 d->page = page;
6239}
6240
6241NormalizedRect NewSignatureData::boundingRectangle() const
6242{
6243 return d->boundingRectangle;
6244}
6245
6246void NewSignatureData::setBoundingRectangle(const NormalizedRect &rect)
6247{
6248 d->boundingRectangle = rect;
6249}
6250
6252{
6253 return d->documentPassword;
6254}
6255
6257{
6258 d->documentPassword = password;
6259}
6260
6262{
6263 return d->location;
6264}
6265
6267{
6268 d->location = location;
6269}
6270
6272{
6273 return d->reason;
6274}
6275
6277{
6278 d->reason = reason;
6279}
6280
6282{
6283 return d->backgroundImagePath;
6284}
6285
6287{
6288 d->backgroundImagePath = path;
6289}
6290
6292{
6293 return d->fontSize;
6294}
6295
6297{
6298 d->fontSize = fontSize;
6299}
6300
6302{
6303 return d->leftFontSize;
6304}
6305
6307{
6308 d->leftFontSize = fontSize;
6309}
6310
6311#undef foreachObserver
6312#undef foreachObserverD
6313
6314#include "document.moc"
6315
6316/* kate: replace-tabs on; indent-width 4; */
QStringList entries() const
const KArchiveEntry * entry(const QString &name) const
virtual bool isDirectory() const
virtual bool isFile() const
virtual bool close()
bool addLocalFile(const QString &fileName, const QString &destName)
virtual bool open(QIODevice::OpenMode mode)
bool writeFile(const QString &name, QByteArrayView data, mode_t perm=0100644, const QString &user=QString(), const QString &group=QString(), const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
static Q_INVOKABLE bool authorize(const QString &action)
void settingsChanged(const QString &dialogName)
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
void start() override
static bool isExecutableFile(const QUrl &url, const QString &mimetypeName)
int error() const
void result(KJob *job)
void setUiDelegate(KJobUiDelegate *delegate)
void setCurrentPage(const QModelIndex &index)
QAbstractItemModel * model() const
QString pluginId() const
QJsonObject rawData() const
QString fileName() const
static QList< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter={}, KPluginMetaDataOptions options={})
bool isValid() const
int startDetached()
QString name() const
KGroupId groupId() const
QString loginName() const
Encapsulates data that describes an action.
Definition action.h:41
QVector< Action * > nextActions() const
Returns the next actions to be executed after.
Definition action.cpp:67
@ Execute
Execute a command or external application.
Definition action.h:48
@ Goto
Goto a given page or external document.
Definition action.h:47
@ DocAction
Start a custom action.
Definition action.h:50
@ BackendOpaque
Calls back to the backend with the action.
Definition action.h:55
@ Movie
Play a movie.
Definition action.h:52
@ Browse
Browse a given website.
Definition action.h:49
@ Script
Executes a Script code.
Definition action.h:53
@ Rendition
Play a movie and/or execute a Script code.
Definition action.h:54
@ Sound
Play a sound.
Definition action.h:51
virtual ActionType actionType() const =0
Returns the type of the action.
Native annotation interface.
virtual bool supports(Capability capability) const =0
Query for the supported capabilities.
virtual void notifyRemoval(Annotation *annotation, int page)=0
Called when an existing annotation at a given page is removed.
virtual void notifyAddition(Annotation *annotation, int page)=0
Called when a new annotation is added to a page.
virtual void notifyModification(const Annotation *annotation, int page, bool appearanceChanged)=0
Called after an existing annotation at a given page is modified.
@ Modification
Generator can edit native annotations.
@ Removal
Generator can remove native annotations.
@ Addition
Generator can create native annotations.
Annotation struct holds properties shared by all annotations.
Definition annotations.h:99
QString contents() const
Returns the contents of the annotation.
int flags() const
Returns the flags of the annotation.
void setContents(const QString &contents)
Sets the contents of the annotation.
@ DenyDelete
Cannot be deleted.
@ ExternallyDrawn
Is drawn externally (by the generator which provided it)
@ BeingMoved
Is being moved (mouse drag and drop). If ExternallyDrawn, the generator must not draw it.
@ External
Is stored external.
@ DenyWrite
Cannot be changed.
@ BeingResized
Is being resized (mouse drag and drop). If ExternallyDrawn, the generator must not draw it.
QDomNode getAnnotationPropertiesDomNode() const
Retrieve the QDomNode representing this annotation's properties.
@ AHighlight
A highlight annotation.
@ AGeom
A geometrical annotation.
@ AText
A textual annotation.
@ AInk
An ink annotation.
@ ALine
A line annotation.
@ ACaret
A caret annotation.
@ AWidget
A widget annotation.
@ AStamp
A stamp annotation.
virtual SubType subType() const =0
Returns the sub type of the annotation.
void playSound(const Sound *sound, const SoundAction *linksound=nullptr)
Enqueue the specified sound for playing, optionally taking more information about the playing from th...
void stopPlaybacks()
Tell the AudioPlayer to stop all the playbacks.
static AudioPlayer * instance()
Gets the instance of the audio player.
Bookmarks manager utility.
The Browse action browses an url by opening a web browser or email client, depending on the url proto...
Definition action.h:262
QUrl url() const
Returns the url to browse.
Definition action.cpp:251
A helper class to store information about x509 certificate.
Abstract interface for configuration control.
virtual bool reparseConfig()=0
This method is called to tell the generator to re-parse its configuration.
virtual void addPages(KConfigDialog *dialog)=0
This method allows the generator to add custom configuration pages to the config dialog of okular.
The DocumentAction action contains an action that is performed on the current document.
Definition action.h:301
DocumentActionType documentActionType() const
Returns the type of action.
Definition action.cpp:280
@ SaveAs
SaveAs the document.
Definition action.h:320
@ Quit
Quit application.
Definition action.h:313
@ Print
Print the document.
Definition action.h:319
@ PageNext
Jump to next page.
Definition action.h:309
@ Find
Open find dialog.
Definition action.h:316
@ Presentation
Start presentation.
Definition action.h:314
@ PageLast
Jump to last page.
Definition action.h:310
@ PageFirst
Jump to first page.
Definition action.h:307
@ GoToPage
Goto page.
Definition action.h:317
@ Close
Close document.
Definition action.h:318
@ HistoryForward
Go forward in page history.
Definition action.h:312
@ EndPresentation
End presentation.
Definition action.h:315
@ PagePrev
Jump to previous page.
Definition action.h:308
@ HistoryBack
Go back in page history.
Definition action.h:311
The DocumentInfo structure can be filled in by generators to display metadata about the currently ope...
Definition document.h:76
QString get(Key key) const
Returns the value for a given key or an null string when the key doesn't exist.
static Key getKeyFromString(const QString &key)
Returns the Key from a string key.
Key
The list of predefined keys.
Definition document.h:83
@ ModificationDate
The date of last modification of the document.
Definition document.h:93
@ Author
The author of the document.
Definition document.h:87
@ Subject
The subject of the document.
Definition document.h:85
@ Category
The category of the document.
Definition document.h:95
@ Producer
The producer of the document (e.g. some software)
Definition document.h:89
@ Copyright
The copyright of the document.
Definition document.h:90
@ CreationDate
The date of creation of the document.
Definition document.h:92
@ MimeType
The mime type of the document.
Definition document.h:94
@ FilePath
The path of the file.
Definition document.h:97
@ PagesSize
The size of the pages (if all pages have the same size)
Definition document.h:99
@ Keywords
The keywords which describe the content of the document.
Definition document.h:96
@ Creator
The creator of the document (this can be different from the author)
Definition document.h:88
@ Pages
The number of pages of the document.
Definition document.h:91
@ DocumentSize
The size of the document.
Definition document.h:98
@ Description
The description of the document.
Definition document.h:86
@ Invalid
An invalid key.
Definition document.h:101
@ Title
The title of the document.
Definition document.h:84
static QString getKeyString(Key key)
Returns the internal string for the given key.
void set(const QString &key, const QString &value, const QString &title=QString())
Sets a value for a custom key.
QString getKeyTitle(const QString &key) const
Returns the user visible string for the given key Takes into account keys added by the set() that tak...
DocumentInfo()
Creates a new document info.
QStringList keys() const
Returns all the keys present in this DocumentInfo.
Base class for objects being notified when something changes.
Definition observer.h:29
virtual void notifySetup(const QVector< Okular::Page * > &pages, int setupFlags)
This method is called whenever the document is initialized or reconstructed.
Definition observer.cpp:21
@ NewLayoutForPages
All the pages have.
Definition observer.h:58
@ DocumentChanged
The document is a new document.
Definition observer.h:57
@ UrlChanged
The URL has changed.
Definition observer.h:59
virtual void notifyPageChanged(int page, int flags)
This method is called whenever the content on page described by the passed flags has been changed.
Definition observer.cpp:29
virtual void notifyViewportChanged(bool smoothMove)
This method is called whenever the viewport has been changed.
Definition observer.cpp:25
@ Annotations
Annotations have been changed.
Definition observer.h:49
@ TextSelection
Text selection has been changed.
Definition observer.h:48
@ Pixmap
Pixmaps has been changed.
Definition observer.h:45
@ BoundingBox
Bounding boxes have been changed.
Definition observer.h:50
@ Highlights
Highlighting information has been changed.
Definition observer.h:47
virtual bool canUnloadPixmap(int page) const
Returns whether the observer agrees that all pixmaps for the given page can be unloaded to improve me...
Definition observer.cpp:45
A DOM tree that describes the Table of Contents.
Definition document.h:1531
DocumentSynopsis()
Creates a new document synopsis object.
A view on the document.
Definition document.h:1450
int pageNumber
The number of the page nearest the center of the viewport.
Definition document.h:1481
bool operator==(const DocumentViewport &other) const
bool isValid() const
Returns whether the viewport is valid.
DocumentViewport(int number=-1)
Creates a new viewport for the given page number.
@ Center
Relative to the center of the page.
Definition document.h:1487
@ TopLeft
Relative to the top left corner of the page.
Definition document.h:1488
QString toString() const
Returns the viewport as xml description.
struct Okular::DocumentViewport::@141216310007016116031316306157323050222007024333 rePos
If 'rePos.enabled == true' then this structure contains the viewport center or top left depending on ...
struct Okular::DocumentViewport::@325034170353016146316202254342334130170042130305 autoFit
If 'autoFit.enabled == true' then the page must be autofit in the viewport.
The Document.
Definition document.h:192
QStringList supportedMimeTypes() const
Returns the list with the supported MIME types.
void setAnnotationEditingEnabled(bool enable)
Control annotation editing (creation, modification and removal), which is enabled by default.
const QVector< VisiblePageRect * > & visiblePageRects() const
Returns the list of visible page rectangles.
void docdataMigrationDone()
Delete annotations and form data from the docdata folder.
void fontReadingEnded()
Reports that the reading of the fonts in the document is finished.
bool isDocdataMigrationNeeded() const
Since version 0.21, okular does not allow editing annotations and form data if they are stored in the...
const Page * page(int number) const
Returns the page object for the given page number or 0 if the number is out of range.
QByteArray requestSignedRevisionData(const Okular::SignatureInfo &info)
Returns the part of document covered by the given signature info.
BookmarkManager * bookmarkManager() const
Returns the bookmark manager of the document.
int configurableGenerators() const
Returns the number of generators that have a configuration widget.
void error(const QString &text, int duration)
This signal is emitted whenever an error occurred.
void unregisterView(View *view)
Unregister the specified view from the current document.
void linkEndPresentation()
This signal is emitted whenever an action requests an end presentation operation.
void cancelSearch()
Cancels the current search.
bool exportToText(const QString &fileName) const
Exports the document as ASCII text and saves it under fileName.
void processSourceReference(const SourceReference *reference)
Processes/Executes the given source reference.
bool canSaveChanges() const
Returns whether the changes to the document (modified annotations, values in form fields,...
PageSize::List pageSizes() const
Returns the list of supported page sizes or an empty list if this feature is not available.
void stopFontReading()
Force the termination of the reading of the information about the fonts in the document,...
void canUndoChanged(bool undoAvailable)
This signal is emitted whenever the availability of the undo function changes.
bool canSign() const
Whether the current document can perform digital signing.
void undo()
Undo last edit command.
Rotation rotation() const
Returns the current rotation of the document.
void removeObserver(DocumentObserver *observer)
Unregisters the given observer for the document.
OKULARCORE_DEPRECATED void editFormText(int pageNumber, Okular::FormFieldText *form, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
Edit the text contents of the specified form on page page to be newContents.
void setRotation(int rotation)
This slot is called whenever the user changes the rotation of the document.
void reloadDocument() const
Reloads the pixmaps for whole document.
PrintingType printingSupport() const
Returns what sort of printing the document supports: Native, Postscript, None.
bool supportsSearching() const
Returns whether the document supports searching.
static QString printErrorString(PrintError error)
QPageLayout::Orientation orientation() const
Returns the orientation of the document (for printing purposes).
@ RemoveAllPrevious
Remove all the previous requests, even for non requested page pixmaps.
Definition document.h:475
@ NoOption
No options.
Definition document.h:474
void requestTextPage(uint pageNumber)
Sends a request for text page generation for the given page pageNumber.
void continueSearch(int searchID)
Continues the search for the given searchID.
void addObserver(DocumentObserver *observer)
Registers a new observer for the document.
void fillConfigDialog(KConfigDialog *dialog)
Fill the KConfigDialog dialog with the setting pages of the generators.
bool swapBackingFile(const QString &newFileName, const QUrl &url)
Reload the document from a new location, without any visible effect to the user.
QList< ExportFormat > exportFormats() const
Returns the list of supported export formats.
bool isAllowed(Permission action) const
Returns whether the given action is allowed in the document.
OKULARCORE_DEPRECATED void processFormMouseUpScripAction(const Action *action, Okular::FormField *ff)
Processes the mouse up action on ff.
void linkGoToPage()
This signal is emitted whenever an action requests a goto operation.
bool historyAtEnd() const
Returns whether the document history is at the end.
KXMLGUIClient * guiClient()
Returns the gui client of the generator, if it provides one.
void searchText(int searchID, const QString &text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor &color)
Searches the given text in the document.
void editFormList(int pageNumber, Okular::FormFieldChoice *form, const QList< int > &newChoices)
Edit the selected list entries in form on page page to be newChoices.
void setNextViewport()
Sets the current document viewport to the previous viewport in the viewport history.
void setPageTextSelection(int page, std::unique_ptr< RegularAreaRect > &&rect, const QColor &color)
Clears the text selection highlights for the given page, creates new ones if rect is not nullptr,...
void canRedoChanged(bool redoAvailable)
This signal is emitted whenever the availability of the redo function changes.
bool canRedo() const
Returns true if there is a redo command available; otherwise returns false.
void linkPresentation()
This signal is emitted whenever an action requests a start presentation operation.
DocumentInfo documentInfo() const
Returns the meta data of the document.
void redo()
Redo last undone edit command.
QString openError() const
Returns the reason why the file opening failed, if any.
void removePageAnnotations(int page, const QList< Annotation * > &annotations)
Removes the given annotations from the given page.
QWidget * printConfigurationWidget() const
Returns a custom printer configuration page or 0 if no custom printer configuration page is available...
void setVisiblePageRects(const QVector< VisiblePageRect * > &visiblePageRects, DocumentObserver *excludeObserver=nullptr)
Sets the list of visible page rectangles.
void setPageSize(const Okular::PageSize &size)
This slot is called whenever the user changes the page size of the document.
void processKVCFActions(Okular::FormField *ff)
A method that executes the relevant keystroke, validate, calculate and format actions on a FormField ...
DocumentAdditionalActionType
Describes the additional actions available in the Document.
Definition document.h:775
void linkFind()
This signal is emitted whenever an action requests a find operation.
bool extractArchivedFile(const QString &destFileName)
Extract the document file from the current archive.
void modifyPageAnnotationProperties(int page, Annotation *annotation)
Modifies the given annotation on the given page.
OKULARCORE_DEPRECATED void processFormatAction(const Action *action, Okular::FormFieldText *fft)
Processes the given format action on fft.
void aboutToClose()
This signal is emitted whenever the document is about to close.
CertificateStore * certificateStore() const
Returns the generator's certificate store (if any)
void gotFont(const Okular::FontInfo &font)
Emitted when a new font is found during the reading of the fonts of the document.
const DocumentViewport & viewport() const
Returns the current viewport of the document.
QList< int > bookmarkedPageList() const
Returns a list of the bookmarked.pages.
bool canSaveChanges(SaveCapability cap) const
Returns whether it's possible to save a given category of changes to another document.
void processFocusAction(const Action *action, Okular::FormField *field)
Processes the given focus action on the field.
void requestPrint()
This signal is emitted whenever an action requests a document print operation.
void close()
This signal is emitted whenever an action requests a document close operation.
void walletDataForFile(const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey) const
Returns which wallet data to use to read/write the password for the given fileName.
QUrl currentDocument() const
Returns the url of the currently opened document.
void sourceReferenceActivated(const QString &absFileName, int line, int col, bool *handled)
This signal is emitted whenever a source reference with the given parameters has been activated.
SaveCapability
Saving capabilities.
Definition document.h:948
@ SaveAnnotationsCapability
Can save annotation changes.
Definition document.h:950
@ SaveFormsCapability
Can save form changes.
Definition document.h:949
void setViewportPage(int page, DocumentObserver *excludeObserver=nullptr, bool smoothMove=false)
Sets the current document viewport to the given page.
void recalculateForms()
Recalculates all the form fields in the document.
const QList< EmbeddedFile * > * embeddedFiles() const
Returns the list of embedded files or 0 if no embedded files are available.
void addPageAnnotation(int page, Annotation *annotation)
Adds a new annotation to the given page.
OpenResult openDocumentArchive(const QString &docFile, const QUrl &url, const QString &password=QString())
Opens a document archive.
void notice(const QString &text, int duration)
This signal is emitted to signal a notice.
KPluginMetaData generatorInfo() const
Returns the metadata associated with the generator.
QString bookmarkedPageRange() const
Returns the range of the bookmarked.pages.
void requestPixmaps(const QList< PixmapRequest * > &requests)
Sends requests for pixmap generation.
void registerView(View *view)
Register the specified view for the current document.
bool historyAtBegin() const
Returns whether the document history is at the begin.
QByteArray fontData(const FontInfo &font) const
Gets the font data for the given font.
void setNextDocumentDestination(const QString &namedDestination)
Sets the next namedDestination in the viewport history.
void resetSearch(int searchID)
Resets the search for the given searchID.
bool canSwapBackingFile() const
Returns whether the generator supports hot-swapping the current file with another identical file.
void setZoom(int factor, DocumentObserver *excludeObserver=nullptr)
Sets the zoom for the current document.
void requestSaveAs()
This signal is emitted whenever an action requests a document save as operation.
void quit()
This signal is emitted whenever an action requests an application quit operation.
void fontReadingProgress(int page)
Reports the progress when reading the fonts in the document.
OpenResult openDocument(const QString &docFile, const QUrl &url, const QMimeType &mime, const QString &password=QString())
Opens the document.
void editFormButtons(int pageNumber, const QList< Okular::FormFieldButton * > &formButtons, const QList< bool > &newButtonStates)
Set the states of the group of form buttons formButtons on page page to newButtonStates.
bool saveDocumentArchive(const QString &fileName)
Saves a document archive.
void reparseConfig()
Reparses and applies the configuration.
OKULARCORE_DEPRECATED void processValidateAction(const Action *action, Okular::FormFieldText *fft, bool &returnCode)
Processes the given keystroke action on fft.
bool exportTo(const QString &fileName, const ExportFormat &format) const
Exports the document in the given format and saves it under fileName.
@ NoMatchFound
No match was found.
Definition document.h:625
@ SearchCancelled
The search was cancelled.
Definition document.h:626
@ MatchFound
Any match was found.
Definition document.h:624
void setViewport(const DocumentViewport &viewport, DocumentObserver *excludeObserver=nullptr, bool smoothMove=false, bool updateHistory=true)
Sets the current document viewport to the given viewport.
void processAction(const Action *action)
Processes the given action.
~Document() override
Destroys the document.
Document::PrintError print(QPrinter &printer)
Prints the document to the given printer.
void translatePageAnnotation(int page, Annotation *annotation, const Okular::NormalizedPoint &delta)
Translates the position of the given annotation on the given page by a distance delta in normalized c...
bool supportsPageSizes() const
Returns whether the document supports the listing of page sizes.
Document(QWidget *widget)
Creates a new document with the given widget as widget to relay GUI things (messageboxes,...
void warning(const QString &text, int duration)
This signal is emitted to signal a warning.
bool sign(const NewSignatureData &data, const QString &newPath)
Digitally sign document.
QVariant metaData(const QString &key, const QVariant &option=QVariant()) const
Returns the meta data for the given key and option or an empty variant if the key doesn't exists.
OKULARCORE_DEPRECATED void processKeystrokeAction(const Action *action, Okular::FormFieldText *fft, const QVariant &newValue)
Processes the given keystroke action on fft.
const SourceReference * dynamicSourceReference(int pageNr, double absX, double absY)
Asks the generator to dynamically generate a SourceReference for a given page number and absolute X a...
void setEditorCommandOverride(const QString &editCmd)
sets the editor command to the command editCmd, as given at the commandline.
void setHistoryClean(bool clean)
Sets the history to be clean.
void searchFinished(int searchID, Okular::Document::SearchStatus endStatus)
Reports that the current search finished.
@ NoPrintError
Printing succeeded.
Definition document.h:839
const DocumentSynopsis * documentSynopsis() const
Returns the table of content of the document or 0 if no table of content is available.
void refreshFormWidget(Okular::FormField *field)
This signal is emitted whenever a FormField was changed programmatically and the according widget sho...
bool canModifyPageAnnotation(const Annotation *annotation) const
Tests if the annotation can be modified.
QSizeF allPagesSize() const
If all pages have the same size this method returns it, if the page sizes differ an empty size object...
void closeDocument()
Closes the document.
bool canUndo() const
Returns true if there is an undo command available; otherwise returns false.
bool saveChanges(const QString &fileName)
Save the document and the optional changes to it to the specified fileName.
void processFormMouseScriptAction(const Action *action, Okular::FormField *ff, MouseEventType fieldMouseEventType)
Processes the mouse action of type fieldMouseEventType on ff.
QString pageSizeString(int page) const
Returns the size string for the given page or an empty string if the page is out of range.
bool supportsTiles() const
Returns whether the current document supports tiles.
OKULARCORE_DEPRECATED void processKeystrokeCommitAction(const Action *action, Okular::FormFieldText *fft)
Processes the given keystroke action on fft.
bool isOpened() const
Returns whether the document is currently opened.
void setPrevViewport()
Sets the current document viewport to the next viewport in the viewport history.
bool canConfigurePrinter() const
Returns whether the document can configure the printer itself.
void undoHistoryCleanChanged(bool clean)
This signal is emitted whenever the undo history is clean (i.e.
QString editorCommandOverride() const
returns the overriding editor command.
PrintingType
What type of printing a document supports.
Definition document.h:820
@ PostscriptPrinting
Postscript file printing.
Definition document.h:823
@ NativePrinting
Native Cross-Platform Printing.
Definition document.h:822
@ NoPrinting
Printing Not Supported.
Definition document.h:821
void setNextDocumentViewport(const DocumentViewport &viewport)
Sets the next viewport in the viewport history.
bool canExportToText() const
Returns whether the document supports the export to ASCII text.
void processMovieAction(const Okular::MovieAction *action)
This signal is emitted whenever an movie action is triggered and the UI should process it.
uint currentPage() const
Returns the number of the current page.
void processRenditionAction(const Okular::RenditionAction *action)
This signal is emitted whenever an rendition action is triggered and the UI should process it.
OpenResult
Describes the result of an open document operation.
Definition document.h:210
void editPageAnnotationContents(int page, Annotation *annotation, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
Edits the plain text contents of the given annotation on the given page.
void refreshPixmaps(int pageNumber)
Refresh the pixmaps for the given pageNumber.
bool swapBackingFileArchive(const QString &newFileName, const QUrl &url)
Same as swapBackingFile, but newFileName must be a .okular file.
QAbstractItemModel * layersModel() const
Returns the model for rendering layers (NULL if the document has no layers)
bool canRemovePageAnnotation(const Annotation *annotation) const
Tests if the annotation can be removed.
uint pages() const
Returns the number of pages of the document.
void startFontReading()
Starts the reading of the information about the fonts in the document, if available.
bool canProvideFontInformation() const
Whether the current document can provide information about the fonts used in it.
SearchType
Describes the possible search types.
Definition document.h:612
@ PreviousMatch
Search previous match.
Definition document.h:614
@ GoogleAny
Search complete document (any words in google style)
Definition document.h:617
@ AllDocument
Search complete document.
Definition document.h:615
@ GoogleAll
Search complete document (all words in google style)
Definition document.h:616
@ NextMatch
Search next match.
Definition document.h:613
void editFormCombo(int pageNumber, Okular::FormFieldChoice *form, const QString &newText, int newCursorPos, int prevCursorPos, int prevAnchorPos)
Set the active choice in the combo box form on page page to newText The new cursor position (newCurso...
bool supportsPrintToFile() const
Returns whether the document supports printing to both PDF and PS files.
void adjustPageAnnotation(int page, Annotation *annotation, const Okular::NormalizedPoint &delta1, const Okular::NormalizedPoint &delta2)
Adjusts the position of the top-left and bottom-right corners of given annotation on the given page.
void removePageAnnotation(int page, Annotation *annotation)
Removes the given annotation from the given page.
void processDocumentAction(const Action *action, DocumentAdditionalActionType type)
Processes the given document additional action of specified type.
void prepareToModifyAnnotationProperties(Annotation *annotation)
Prepares to modify the properties of the given annotation.
@ FieldMouseUp
< This event is the result of mouse exiting from a field.
Definition document.h:760
@ FieldMouseEnter
< This event is the result of a mouse down on a field.
Definition document.h:758
@ FieldMouseExit
< This event is the result of mouse exiting from a field.
Definition document.h:759
EmbeddedFile()
Creates a new embedded file.
virtual ~EmbeddedFile()
Destroys the embedded file.
The Execute action executes an external application.
Definition action.h:217
QString fileName() const
Returns the file name of the application to execute.
Definition action.cpp:200
QString parameters() const
Returns the parameters of the application to execute.
Definition action.cpp:206
Defines an entry for the export menu.
Definition generator.h:80
A small class that represents the information of a font.
Definition fontinfo.h:25
Interface of a choice form field.
Definition form.h:421
virtual QString editChoice() const
The text entered into an editable combo box choice field.
Definition form.cpp:350
virtual QList< int > currentChoices() const =0
The currently selected choices.
virtual QStringList choices() const =0
The possible choices of the choice field.
Interface of a text form field.
Definition form.h:310
virtual QString text() const =0
The text of text field.
The base interface of a form field.
Definition form.h:40
virtual bool isReadOnly() const
Whether the field is read-only.
Definition form.cpp:53
Action * additionalAction(AdditionalActionType type) const
Returns the additional action of the given type or nullptr if no action has been defined.
Definition form.cpp:106
@ FieldModified
An action to be performed when the user modifies the field.
Definition form.h:161
@ CalculateField
An action to be performed when the field needs to be recalculated.
Definition form.h:164
@ ValidateField
An action to be performed when the field value changes.
Definition form.h:163
@ FormatField
An action to be performed before the field is formatted to display its value.
Definition form.h:162
void commitFormattedValue(const QString &value)
Updates the value that was last committed in this form field.
Definition form.cpp:163
virtual void setValue(const QVariant &value)
Sets the value associated with the form field.
Definition form.cpp:80
virtual void setAppearanceValue(const QVariant &value)
Sets the appearance value associated with the form field.
Definition form.cpp:84
QString committedValue() const
Returns the value that was last committed in this form field.
Definition form.cpp:145
void commitValue(const QString &value)
Updates the value that was last committed in this form field.
Definition form.cpp:151
QString committedFormattedValue() const
Returns the formatted value that was last committed in this form field.
Definition form.cpp:157
virtual QVariant value() const
Returns the value associated wit the form field.
Definition form.cpp:88
[Abstract Class] The information generator.
Definition generator.h:189
void error(const QString &message, int duration)
This signal should be emitted whenever an error occurred in the generator.
SwapBackingFileResult
Describes the result of an swap file operation.
Definition generator.h:279
DocumentMetaDataKey
Internal document setting.
Definition generator.h:576
@ TextHintingMetaData
Returns (bool)text hinting from Settings (option is not used)
Definition generator.h:580
@ GraphicsAntialiasMetaData
Returns (bool)graphic antialias from Settings (option is not used)
Definition generator.h:579
@ TextAntialiasMetaData
Returns (bool) text antialias from Settings (option is not used)
Definition generator.h:578
@ PaperColorMetaData
Returns (QColor) the paper color if set in Settings or the default color (white) if option is true (o...
Definition generator.h:577
void notice(const QString &message, int duration)
This signal should be emitted whenever the user should be noticed.
@ Threaded
Whether the Generator supports asynchronous generation of pictures or text pages.
Definition generator.h:203
@ PrintToFile
Whether the Generator supports export to PDF & PS through the Print Dialog.
Definition generator.h:210
@ ReadRawData
Whether the Generator can read a document directly from its raw data.
Definition generator.h:205
@ TextExtraction
Whether the Generator can extract text from the document in the form of TextPage's.
Definition generator.h:204
@ TiledRendering
Whether the Generator can render tiles.
Definition generator.h:211
@ SwapBackingFile
Whether the Generator can hot-swap the file it's reading from.
Definition generator.h:212
@ PrintNative
Whether the Generator supports native cross-platform printing (QPainter-based).
Definition generator.h:208
@ FontInfo
Whether the Generator can provide information about the fonts used in the document.
Definition generator.h:206
@ PageSizes
Whether the Generator can change the size of the document pages.
Definition generator.h:207
@ SupportsCancelling
Whether the Generator can cancel requests.
Definition generator.h:213
@ PrintPostscript
Whether the Generator supports postscript-based file printing.
Definition generator.h:209
@ Points
The page size is given in 1/72 inches.
Definition generator.h:396
@ Pixels
The page size is given in screen pixels.
Definition generator.h:397
@ None
The page size is not defined in a physical metric.
Definition generator.h:395
PageLayout
This enum identifies default page layouts.
Definition generator.h:370
@ NoLayout
Layout not specified.
Definition generator.h:371
void warning(const QString &message, int duration)
This signal should be emitted whenever the user should be warned.
The Goto action changes the viewport to another page or loads an external document.
Definition action.h:151
bool isExternal() const
Returns whether the goto action points to an external document.
Definition action.cpp:140
QString destinationName() const
Returns the document named destination the goto action points to.
Definition action.cpp:158
QString fileName() const
Returns the filename of the external document.
Definition action.cpp:146
DocumentViewport destViewport() const
Returns the document viewport the goto action points to.
Definition action.cpp:152
Abstract interface for user interface control.
KXMLGUIClient * guiClient()
This method requests the XML GUI Client provided by the interface.
The Movie action executes an operation on a video on activation.
Definition action.h:469
Data needed to create a new signature.
Definition document.h:1638
QString documentPassword() const
void setReason(const QString &reason)
void setDocumentPassword(const QString &password)
void setLocation(const QString &location)
QString reason() const
void setLeftFontSize(double fontSize)
QString backgroundImagePath() const
double leftFontSize() const
double fontSize() const
void setBackgroundImagePath(const QString &path)
void setFontSize(double fontSize)
QString location() const
NormalizedPoint is a helper class which stores the coordinates of a normalized point.
Definition area.h:117
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition area.h:189
double bottom
The normalized bottom coordinate.
Definition area.h:435
double right
The normalized right coordinate.
Definition area.h:430
double height() const
Definition area.h:412
double left
The normalized left coordinate.
Definition area.h:420
double width() const
Definition area.h:406
QRect geometry(int xScale, int yScale) const
Returns the rectangle mapped to a reference area of xScale x yScale.
Definition area.cpp:232
double top
The normalized top coordinate.
Definition area.h:425
bool isNull() const
Returns whether this normalized rectangle is a null normalized rect.
Definition area.cpp:155
An area with normalized coordinates that contains a reference to an object.
Definition area.h:458
@ Action
An action.
Definition area.h:464
A small class that represents the size of a page.
Definition pagesize.h:24
Collector for all the data belonging to a page.
Definition page.h:48
void deletePixmap(DocumentObserver *observer)
Deletes the pixmap for the given observer.
Definition page.cpp:753
double height() const
Returns the height of the page.
Definition page.cpp:185
bool hasPixmap(DocumentObserver *observer, int width=-1, int height=-1, const NormalizedRect &rect=NormalizedRect()) const
Returns whether the page of size width x height has a pixmap in the region given by rect for the give...
Definition page.cpp:219
double width() const
Returns the width of the page.
Definition page.cpp:180
void setPageSize(DocumentObserver *observer, int width, int height)
Sets the size of the page (in screen pixels) if there is a TilesManager.
Definition page.cpp:248
Rotation rotation() const
Returns the rotation of the page as defined by the user.
Definition page.cpp:170
Describes a pixmap type request.
Definition generator.h:645
bool preload() const
Returns whether the generation request is for a page that is not important i.e.
void setTile(bool tile)
Sets whether the generator should render only the given normalized rect or the entire page.
bool asynchronous() const
Returns whether the generation should be done synchronous or asynchronous.
bool shouldAbortRender() const
Should the request be aborted if possible?
void setPartialUpdatesWanted(bool partialUpdatesWanted)
Sets whether the request should report back updates if possible.
int width() const
Returns the page width of the requested pixmap.
int height() const
Returns the page height of the requested pixmap.
int priority() const
Returns the priority (less it better, 0 is maximum) of the request.
const NormalizedRect & normalizedRect() const
Returns the normalized region of the page to request.
bool partialUpdatesWanted() const
Should the request report back updates if possible?
int pageNumber() const
Returns the page number of the request.
void setNormalizedRect(const NormalizedRect &rect)
Sets the region of the page to request.
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
bool isTile() const
Returns whether the generator should render just the region given by normalizedRect() or the entire p...
DocumentObserver * observer() const
Returns the observer of the request.
Abstract interface for advanced printing control.
virtual QWidget * printConfigurationWidget() const =0
Builds and returns a new printing configuration widget.
This is a list of NormalizedRect, to describe an area consisting of multiple rectangles using normali...
Definition area.h:927
The Rendition action executes an operation on a video or executes some JavaScript code on activation.
Definition action.h:523
ScriptType scriptType() const
Returns the type of script.
Definition action.cpp:578
QString script() const
Returns the script code.
Definition action.cpp:584
Abstract interface for saving.
virtual AnnotationProxy * annotationProxy() const =0
Returns the annotation proxy.
virtual bool save(const QString &fileName, SaveOptions options, QString *errorText)=0
Save to the specified fileName with the specified options.
virtual bool supportsOption(SaveOption option) const =0
Query for the supported saving options.
@ SaveChanges
The possibility to save with the current changes to the document.
The Script action executes a Script code.
Definition action.h:423
ScriptType scriptType() const
Returns the type of action.
Definition action.cpp:441
QString script() const
Returns the code.
Definition action.cpp:447
The Sound action plays a sound on activation.
Definition action.h:359
Okular::Sound * sound() const
Returns the sound object which contains the sound data.
Definition action.cpp:394
This class describes the object rectangle for a source reference.
Definition area.h:599
Defines a source reference.
int column() const
Returns the column of the position in the source file.
int row() const
Returns the row of the position in the source file.
QString fileName() const
Returns the filename of the source.
This class represents a rectangular portion of a page.
Definition tile.h:23
NormalizedRect rect() const
Location of the tile.
bool isValid() const
True if the pixmap is available and updated.
static QSizeF realDpi(const QWindow *windowOnScreen)
Return the real DPI of the display containing given window.
Definition utils.cpp:47
View on the document.
Definition view.h:30
@ CapabilityRead
Possibility to read a capability.
Definition view.h:53
@ CapabilitySerializable
The capability is suitable for being serialized/deserialized.
Definition view.h:55
@ ZoomModality
Possibility to get/set the zoom mode of the view.
Definition view.h:42
@ Zoom
Possibility to get/set the zoom of the view.
Definition view.h:41
@ TrimMargins
Possibility to toggle trim-margins mode.
Definition view.h:45
@ ViewModeModality
Possibility to get/set the view mode.
Definition view.h:44
@ Continuous
Possibility to toggle continuous mode.
Definition view.h:43
Document * viewDocument() const
Return the document which this view is associated to, or null if it is not associated with any docume...
Definition view.cpp:39
VisiblePageRect(int pageNumber=-1, const NormalizedRect &rectangle=NormalizedRect())
Creates a new visible page rectangle.
NormalizedRect rect
The rectangle in normalized coordinates.
Definition document.h:1629
int pageNumber
The page number where the rectangle is located.
Definition document.h:1624
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Type type(const QSqlDatabase &db)
char * toString(const EngineQuery &query)
KCALUTILS_EXPORT QString mimeType()
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT QUrl upUrl(const QUrl &url)
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
QString name(const QVariant &location)
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
QWidget * window(QObject *job)
QString expandMacrosShellQuote(const QString &str, const QHash< QChar, QString > &map, QChar c=QLatin1Char('%'))
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
KGuiItem forward(BidiMode useBidi=IgnoreRTL)
global.h
Definition action.h:17
@ JavaScript
JavaScript code.
Definition global.h:76
Permission
Describes the DRM capabilities.
Definition global.h:24
@ AllowFillForms
Allows to fill the forms in the document.
Definition global.h:29
@ AllowNotes
Allows to add annotations to the document.
Definition global.h:28
Rotation
A rotation.
Definition global.h:46
@ Rotation270
Rotated 2700 degrees clockwise.
Definition global.h:50
@ Rotation90
Rotated 90 degrees clockwise.
Definition global.h:48
@ Rotation0
Not rotated.
Definition global.h:47
@ FromTop
Searching from top of the page, next result is to be found, there was no earlier search result.
Definition global.h:37
@ FromBottom
Searching from bottom of the page, next result is to be found, there was no earlier search result.
Definition global.h:38
@ PreviousResult
Searching for the previous result on the page, earlier result should be located so we search from the...
Definition global.h:40
@ NextResult
Searching for the next result on the page, earlier result should be located so we search from the las...
Definition global.h:39
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
bool isEmpty() const const
QByteArray & remove(qsizetype pos, qsizetype len)
qsizetype size() const const
QChar fromLatin1(char c)
bool isHighSurrogate(char32_t ucs4)
QColor fromHsv(int h, int s, int v, int a)
bool openUrl(const QUrl &url)
bool isRelativePath(const QString &path)
bool mkpath(const QString &dirPath) const const
QString tempPath()
QDomElement createElement(const QString &tagName)
QDomProcessingInstruction createProcessingInstruction(const QString &target, const QString &data)
QDomText createTextNode(const QString &value)
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QByteArray toByteArray(int indent) const const
QString attribute(const QString &name, const QString &defValue) const const
bool hasAttribute(const QString &name) const const
void setAttribute(const QString &name, const QString &value)
QString tagName() const const
QString text() const const
QDomNode appendChild(const QDomNode &newChild)
QDomNode firstChild() const const
bool isElement() const const
bool isNull() const const
QDomNode nextSibling() const const
QDomDocument ownerDocument() const const
QDomElement toElement() const const
int exec(ProcessEventsFlags flags)
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists(const QString &fileName)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
bool remove()
virtual void close() override
virtual bool seek(qint64 pos) override
bool exists(const QString &path)
QString symLinkTarget() const const
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
const_iterator constBegin() const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator end()
iterator find(const Key &key)
bool isEmpty() const const
T value(const Key &key) const const
QByteArray read(qint64 maxSize)
QByteArray readAll()
QJsonValue value(QLatin1StringView key) const const
int toInt(int defaultValue) const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const AT &value) const const
qsizetype count() const const
iterator end()
T & first()
bool isEmpty() const const
T & last()
void push_back(parameter_type value)
qsizetype size() const const
value_type takeFirst()
QLocale system()
typedef ConstIterator
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
size_type count() const const
iterator end()
iterator insert(const Key &key, const T &value)
Key key(const T &value, const Key &defaultKey) const const
size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
QMimeType mimeTypeForData(QIODevice *device) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
QMimeType mimeTypeForUrl(const QUrl &url) const const
bool inherits(const QString &mimeTypeName) const const
bool isValid() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
T qobject_cast(QObject *object)
PageSizeId id() const const
QString name() const const
int height() const const
int width() const const
int height() const const
int width() const const
void clear()
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
bool isEmpty() const const
T * get() const const
int height() const const
int width() const const
qreal height() const const
bool isValid() const const
qreal width() const const
void push(const T &t)
T & top()
QString findExecutable(const QString &executableName, const QStringList &paths)
QString writableLocation(StandardLocation type)
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromUcs4(const char32_t *unicode, qsizetype size)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
bool isNull() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
std::u32string toStdU32String() const const
qulonglong toULongLong(bool *ok, int base) const const
QString trimmed() const const
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
CaseSensitivity
WaitCursor
SkipEmptyParts
virtual QString fileName() const const override
void timeout()
QTransform inverted(bool *invertible) const const
void canRedoChanged(bool canRedo)
void canUndoChanged(bool canUndo)
void cleanChanged(bool clean)
PreferLocalFile
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QString host(ComponentFormattingOptions options) const const
bool isEmpty() const const
bool isLocalFile() const const
bool isRelative() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QUrl resolved(const QUrl &relative) const const
QString scheme() const const
QString toLocalFile() const const
QString url(FormattingOptions options) const const
bool toBool() const const
QString toString() const const
T value() const const
QScreen * screen() const const
QWidget * window() const const
QWindow * windowHandle() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 7 2025 11:54:37 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.