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 (SettingsCore::useFileInfoForViewContinuous()) {
766 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
767 view->setCapability(View::Continuous, (int)m_generator->defaultPageContinuous());
768 }
769 }
770}
771
772void DocumentPrivate::saveViewsInfo(View *view, QDomElement &e) const
773{
774 if (view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable)) && view->supportsCapability(View::ZoomModality) &&
776 QDomElement zoomEl = e.ownerDocument().createElement(QStringLiteral("zoom"));
777 e.appendChild(zoomEl);
778 bool ok = true;
779 const double zoom = view->capability(View::Zoom).toDouble(&ok);
780 if (ok && zoom != 0) {
781 zoomEl.setAttribute(QStringLiteral("value"), QString::number(zoom));
782 }
783 const int mode = view->capability(View::ZoomModality).toInt(&ok);
784 if (ok) {
785 zoomEl.setAttribute(QStringLiteral("mode"), mode);
786 }
787 }
788 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
789 QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("continuous"));
790 e.appendChild(contEl);
791 const bool mode = view->capability(View::Continuous).toBool();
792 contEl.setAttribute(QStringLiteral("mode"), mode);
793 }
794 if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
795 QDomElement viewEl = e.ownerDocument().createElement(QStringLiteral("viewMode"));
796 e.appendChild(viewEl);
797 bool ok = true;
798 const int mode = view->capability(View::ViewModeModality).toInt(&ok);
799 if (ok) {
800 viewEl.setAttribute(QStringLiteral("mode"), mode);
801 }
802 }
803 if (view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
804 QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("trimMargins"));
805 e.appendChild(contEl);
806 const bool value = view->capability(View::TrimMargins).toBool();
807 contEl.setAttribute(QStringLiteral("value"), value);
808 }
809}
810
811QUrl DocumentPrivate::giveAbsoluteUrl(const QString &fileName) const
812{
813 if (!QDir::isRelativePath(fileName)) {
814 return QUrl::fromLocalFile(fileName);
815 }
816
817 if (!m_url.isValid()) {
818 return QUrl();
819 }
820
821 return QUrl(KIO::upUrl(m_url).toString() + fileName);
822}
823
824bool DocumentPrivate::openRelativeFile(const QString &fileName)
825{
826 const QUrl newUrl = giveAbsoluteUrl(fileName);
827 if (newUrl.isEmpty()) {
828 return false;
829 }
830
831 qCDebug(OkularCoreDebug).nospace() << "openRelativeFile: '" << newUrl << "'";
832
833 Q_EMIT m_parent->openUrl(newUrl);
834 return m_url == newUrl;
835}
836
837Generator *DocumentPrivate::loadGeneratorLibrary(const KPluginMetaData &service)
838{
839 const auto result = KPluginFactory::instantiatePlugin<Okular::Generator>(service);
840
841 if (!result) {
842 qCWarning(OkularCoreDebug).nospace() << "Failed to load plugin " << service.fileName() << ": " << result.errorText;
843 return nullptr;
844 }
845
846 GeneratorInfo info(result.plugin, service);
847 m_loadedGenerators.insert(service.pluginId(), info);
848 return result.plugin;
849}
850
851void DocumentPrivate::loadAllGeneratorLibraries()
852{
853 if (m_generatorsLoaded) {
854 return;
855 }
856
857 loadServiceList(availableGenerators());
858
859 m_generatorsLoaded = true;
860}
861
862void DocumentPrivate::loadServiceList(const QVector<KPluginMetaData> &offers)
863{
864 int count = offers.count();
865 if (count <= 0) {
866 return;
867 }
868
869 for (int i = 0; i < count; ++i) {
870 QString id = offers.at(i).pluginId();
871 // don't load already loaded generators
872 QHash<QString, GeneratorInfo>::const_iterator genIt = m_loadedGenerators.constFind(id);
873 if (!m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd()) {
874 continue;
875 }
876
877 Generator *g = loadGeneratorLibrary(offers.at(i));
878 (void)g;
879 }
880}
881
882void DocumentPrivate::unloadGenerator(const GeneratorInfo &info)
883{
884 delete info.generator;
885}
886
887void DocumentPrivate::cacheExportFormats()
888{
889 if (m_exportCached) {
890 return;
891 }
892
893 const ExportFormat::List formats = m_generator->exportFormats();
894 for (int i = 0; i < formats.count(); ++i) {
895 if (formats.at(i).mimeType().name() == QLatin1String("text/plain")) {
896 m_exportToText = formats.at(i);
897 } else {
898 m_exportFormats.append(formats.at(i));
899 }
900 }
901
902 m_exportCached = true;
903}
904
905ConfigInterface *DocumentPrivate::generatorConfig(GeneratorInfo &info)
906{
907 if (info.configChecked) {
908 return info.config;
909 }
910
911 info.config = qobject_cast<Okular::ConfigInterface *>(info.generator);
912 info.configChecked = true;
913 return info.config;
914}
915
916SaveInterface *DocumentPrivate::generatorSave(GeneratorInfo &info)
917{
918 if (info.saveChecked) {
919 return info.save;
920 }
921
922 info.save = qobject_cast<Okular::SaveInterface *>(info.generator);
923 info.saveChecked = true;
924 return info.save;
925}
926
927Document::OpenResult DocumentPrivate::openDocumentInternal(const KPluginMetaData &offer, bool isstdin, const QString &docFile, const QByteArray &filedata, const QString &password)
928{
929 QString propName = offer.pluginId();
930 QHash<QString, GeneratorInfo>::const_iterator genIt = m_loadedGenerators.constFind(propName);
931 m_walletGenerator = nullptr;
932 if (genIt != m_loadedGenerators.constEnd()) {
933 m_generator = genIt.value().generator;
934 } else {
935 m_generator = loadGeneratorLibrary(offer);
936 if (!m_generator) {
937 return Document::OpenError;
938 }
939 genIt = m_loadedGenerators.constFind(propName);
940 Q_ASSERT(genIt != m_loadedGenerators.constEnd());
941 }
942 Q_ASSERT_X(m_generator, "Document::load()", "null generator?!");
943
944 m_generator->d_func()->m_document = this;
945
946 // connect error reporting signals
947 m_openError.clear();
948 QMetaObject::Connection errorToOpenErrorConnection = QObject::connect(m_generator, &Generator::error, m_parent, [this](const QString &message) { m_openError = message; });
949 QObject::connect(m_generator, &Generator::warning, m_parent, &Document::warning);
950 QObject::connect(m_generator, &Generator::notice, m_parent, &Document::notice);
951
953
954 const QWindow *window = m_widget && m_widget->window() ? m_widget->window()->windowHandle() : nullptr;
955 const QSizeF dpi = Utils::realDpi(window);
956 qCDebug(OkularCoreDebug) << "Output DPI:" << dpi;
957 m_generator->setDPI(dpi);
958
959 Document::OpenResult openResult = Document::OpenError;
960 if (!isstdin) {
961 openResult = m_generator->loadDocumentWithPassword(docFile, m_pagesVector, password);
962 } else if (!filedata.isEmpty()) {
963 if (m_generator->hasFeature(Generator::ReadRawData)) {
964 openResult = m_generator->loadDocumentFromDataWithPassword(filedata, m_pagesVector, password);
965 } else {
966 m_tempFile = new QTemporaryFile();
967 if (!m_tempFile->open()) {
968 delete m_tempFile;
969 m_tempFile = nullptr;
970 } else {
971 m_tempFile->write(filedata);
972 QString tmpFileName = m_tempFile->fileName();
973 m_tempFile->close();
974 openResult = m_generator->loadDocumentWithPassword(tmpFileName, m_pagesVector, password);
975 }
976 }
977 }
978
980 if (openResult != Document::OpenSuccess || m_pagesVector.size() <= 0) {
981 m_generator->d_func()->m_document = nullptr;
982 QObject::disconnect(m_generator, nullptr, m_parent, nullptr);
983
984 // TODO this is a bit of a hack, since basically means that
985 // you can only call walletDataForFile after calling openDocument
986 // but since in reality it's what happens I've decided not to refactor/break API
987 // One solution is just kill walletDataForFile and make OpenResult be an object
988 // where the wallet data is also returned when OpenNeedsPassword
989 m_walletGenerator = m_generator;
990 m_generator = nullptr;
991
992 qDeleteAll(m_pagesVector);
993 m_pagesVector.clear();
994 delete m_tempFile;
995 m_tempFile = nullptr;
996
997 // TODO: Q_EMIT a message telling the document is empty
998 if (openResult == Document::OpenSuccess) {
999 openResult = Document::OpenError;
1000 }
1001 } else {
1002 /*
1003 * Now that the documen is opened, the tab (if using tabs) is visible, which mean that
1004 * we can now connect the error reporting signal directly to the parent
1005 */
1006
1007 QObject::disconnect(errorToOpenErrorConnection);
1008 QObject::connect(m_generator, &Generator::error, m_parent, &Document::error);
1009 }
1010
1011 return openResult;
1012}
1013
1014bool DocumentPrivate::savePageDocumentInfo(QTemporaryFile *infoFile, int what) const
1015{
1016 if (infoFile->open()) {
1017 // 1. Create DOM
1018 QDomDocument doc(QStringLiteral("documentInfo"));
1019 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
1020 doc.appendChild(xmlPi);
1021 QDomElement root = doc.createElement(QStringLiteral("documentInfo"));
1022 doc.appendChild(root);
1023
1024 // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
1025 QDomElement pageList = doc.createElement(QStringLiteral("pageList"));
1026 root.appendChild(pageList);
1027 // <page list><page number='x'>.... </page> save pages that hold data
1028 QVector<Page *>::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd();
1029 for (; pIt != pEnd; ++pIt) {
1030 (*pIt)->d->saveLocalContents(pageList, doc, PageItems(what));
1031 }
1032
1033 // 3. Save DOM to XML file
1034 QString xml = doc.toString();
1035
1036 QTextStream os(infoFile);
1037 os.setEncoding(QStringConverter::Utf8);
1038 os << xml;
1039 return true;
1040 }
1041 return false;
1042}
1043
1044DocumentViewport DocumentPrivate::nextDocumentViewport() const
1045{
1046 DocumentViewport ret = m_nextDocumentViewport;
1047 if (!m_nextDocumentDestination.isEmpty() && m_generator) {
1048 DocumentViewport vp(m_parent->metaData(QStringLiteral("NamedViewport"), m_nextDocumentDestination).toString());
1049 if (vp.isValid()) {
1050 ret = vp;
1051 }
1052 }
1053 return ret;
1054}
1055
1056void DocumentPrivate::performAddPageAnnotation(int page, Annotation *annotation)
1057{
1058 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1059 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1060
1061 // find out the page to attach annotation
1062 Page *kp = m_pagesVector[page];
1063 if (!m_generator || !kp) {
1064 return;
1065 }
1066
1067 // the annotation belongs already to a page
1068 if (annotation->d_ptr->m_page) {
1069 return;
1070 }
1071
1072 // add annotation to the page
1073 kp->addAnnotation(annotation);
1074
1075 // tell the annotation proxy
1076 if (proxy && proxy->supports(AnnotationProxy::Addition)) {
1077 proxy->notifyAddition(annotation, page);
1078 }
1079
1080 // notify observers about the change
1081 notifyAnnotationChanges(page);
1082
1083 if (annotation->flags() & Annotation::ExternallyDrawn) {
1084 // Redraw everything, including ExternallyDrawn annotations
1085 refreshPixmaps(page);
1086 }
1087}
1088
1089void DocumentPrivate::performRemovePageAnnotation(int page, Annotation *annotation)
1090{
1091 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1092 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1093 bool isExternallyDrawn;
1094
1095 // find out the page
1096 Page *kp = m_pagesVector[page];
1097 if (!m_generator || !kp) {
1098 return;
1099 }
1100
1101 if (annotation->flags() & Annotation::ExternallyDrawn) {
1102 isExternallyDrawn = true;
1103 } else {
1104 isExternallyDrawn = false;
1105 }
1106
1107 // try to remove the annotation
1108 if (m_parent->canRemovePageAnnotation(annotation)) {
1109 // tell the annotation proxy
1110 if (proxy && proxy->supports(AnnotationProxy::Removal)) {
1111 proxy->notifyRemoval(annotation, page);
1112 }
1113
1114 kp->removeAnnotation(annotation); // Also destroys the object
1115
1116 // in case of success, notify observers about the change
1117 notifyAnnotationChanges(page);
1118
1119 if (isExternallyDrawn) {
1120 // Redraw everything, including ExternallyDrawn annotations
1121 refreshPixmaps(page);
1122 }
1123 }
1124}
1125
1126void DocumentPrivate::performModifyPageAnnotation(int page, Annotation *annotation, bool appearanceChanged)
1127{
1128 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
1129 AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr;
1130
1131 // find out the page
1132 const Page *kp = m_pagesVector[page];
1133 if (!m_generator || !kp) {
1134 return;
1135 }
1136
1137 // tell the annotation proxy
1138 if (proxy && proxy->supports(AnnotationProxy::Modification)) {
1139 proxy->notifyModification(annotation, page, appearanceChanged);
1140 }
1141
1142 // notify observers about the change
1143 notifyAnnotationChanges(page);
1144 if (appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn)) {
1145 /* When an annotation is being moved, the generator will not render it.
1146 * Therefore there's no need to refresh pixmaps after the first time */
1147 if (annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized)) {
1148 if (m_annotationBeingModified) {
1149 return;
1150 } else { // First time: take note
1151 m_annotationBeingModified = true;
1152 }
1153 } else {
1154 m_annotationBeingModified = false;
1155 }
1156
1157 // Redraw everything, including ExternallyDrawn annotations
1158 qCDebug(OkularCoreDebug) << "Refreshing Pixmaps";
1159 refreshPixmaps(page);
1160 }
1161}
1162
1163void DocumentPrivate::performSetAnnotationContents(const QString &newContents, Annotation *annot, int pageNumber)
1164{
1165 bool appearanceChanged = false;
1166
1167 // Check if appearanceChanged should be true
1168 switch (annot->subType()) {
1169 // If it's an in-place TextAnnotation, set the inplace text
1171 const Okular::TextAnnotation *txtann = static_cast<Okular::TextAnnotation *>(annot);
1172 if (txtann->textType() == Okular::TextAnnotation::InPlace) {
1173 appearanceChanged = true;
1174 }
1175 break;
1176 }
1177 // If it's a LineAnnotation, check if caption text is visible
1179 const Okular::LineAnnotation *lineann = static_cast<Okular::LineAnnotation *>(annot);
1180 if (lineann->showCaption()) {
1181 appearanceChanged = true;
1182 }
1183 break;
1184 }
1185 default:
1186 break;
1187 }
1188
1189 // Set contents
1190 annot->setContents(newContents);
1191
1192 // Tell the document the annotation has been modified
1193 performModifyPageAnnotation(pageNumber, annot, appearanceChanged);
1194}
1195
1196void DocumentPrivate::recalculateForms()
1197{
1198 const QVariant fco = m_parent->metaData(QStringLiteral("FormCalculateOrder"));
1199 const QVector<int> formCalculateOrder = fco.value<QVector<int>>();
1200 for (int formId : formCalculateOrder) {
1201 for (uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++) {
1202 const Page *p = m_parent->page(pageIdx);
1203 if (p) {
1204 bool pageNeedsRefresh = false;
1205 const QList<Okular::FormField *> forms = p->formFields();
1206 for (FormField *form : forms) {
1207 if (form->id() == formId) {
1208 const Action *action = form->additionalAction(FormField::CalculateField);
1209 if (action) {
1210 std::shared_ptr<Event> event;
1211 QString oldVal;
1212 if (dynamic_cast<FormFieldText *>(form) || dynamic_cast<FormFieldChoice *>(form)) {
1213 // Prepare text calculate event
1214 event = Event::createFormCalculateEvent(form, m_pagesVector[pageIdx]);
1215 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
1216 executeScriptEvent(event, linkscript);
1217 // The value maybe changed in javascript so save it first.
1218 oldVal = form->value().toString();
1219
1220 if (event) {
1221 // Update text field from calculate
1222 const QString newVal = event->value().toString();
1223 if (newVal != oldVal) {
1224 form->setValue(QVariant(newVal));
1225 form->setAppearanceValue(QVariant(newVal));
1226 bool returnCode = true;
1227 if (form->additionalAction(Okular::FormField::FieldModified) && !form->isReadOnly()) {
1228 m_parent->processKeystrokeCommitAction(form->additionalAction(Okular::FormField::FieldModified), form, returnCode);
1229 }
1230 if (const Okular::Action *action = form->additionalAction(Okular::FormField::ValidateField)) {
1231 if (returnCode) {
1232 m_parent->processValidateAction(action, form, returnCode);
1233 }
1234 }
1235 if (!returnCode) {
1236 continue;
1237 } else {
1238 form->commitValue(form->value().toString());
1239 }
1240 if (const Okular::Action *action = form->additionalAction(Okular::FormField::FormatField)) {
1241 // The format action handles the refresh.
1242 m_parent->processFormatAction(action, form);
1243 } else {
1244 form->commitFormattedValue(form->value().toString());
1245 Q_EMIT m_parent->refreshFormWidget(form);
1246 pageNeedsRefresh = true;
1247 }
1248 }
1249 }
1250 }
1251 } else {
1252 qWarning() << "Form that is part of calculate order doesn't have a calculate action";
1253 }
1254 }
1255 }
1256 if (pageNeedsRefresh) {
1257 refreshPixmaps(p->number());
1258 }
1259 }
1260 }
1261 }
1262}
1263
1264void DocumentPrivate::saveDocumentInfo() const
1265{
1266 if (m_xmlFileName.isEmpty()) {
1267 return;
1268 }
1269
1270 QFile infoFile(m_xmlFileName);
1271 qCDebug(OkularCoreDebug) << "About to save document info to" << m_xmlFileName;
1273 qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName;
1274 return;
1275 }
1276 // 1. Create DOM
1277 QDomDocument doc(QStringLiteral("documentInfo"));
1278 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
1279 doc.appendChild(xmlPi);
1280 QDomElement root = doc.createElement(QStringLiteral("documentInfo"));
1281 root.setAttribute(QStringLiteral("url"), m_url.toDisplayString(QUrl::PreferLocalFile));
1282 doc.appendChild(root);
1283
1284 // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
1285 // -> do this if there are not-yet-migrated annots or forms in docdata/
1286 if (m_docdataMigrationNeeded) {
1287 QDomElement pageList = doc.createElement(QStringLiteral("pageList"));
1288 root.appendChild(pageList);
1289 // OriginalAnnotationPageItems and OriginalFormFieldPageItems tell to
1290 // store the same unmodified annotation list and form contents that we
1291 // read when we opened the file and ignore any change made by the user.
1292 // Since we don't store annotations and forms in docdata/ any more, this is
1293 // necessary to preserve annotations/forms that previous Okular version
1294 // had stored there.
1295 const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems;
1296 // <page list><page number='x'>.... </page> save pages that hold data
1297 QVector<Page *>::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd();
1298 for (; pIt != pEnd; ++pIt) {
1299 (*pIt)->d->saveLocalContents(pageList, doc, saveWhat);
1300 }
1301 }
1302
1303 // 2.2. Save document info (current viewport, history, ... ) to DOM
1304 QDomElement generalInfo = doc.createElement(QStringLiteral("generalInfo"));
1305 root.appendChild(generalInfo);
1306 // create rotation node
1307 if (m_rotation != Rotation0) {
1308 QDomElement rotationNode = doc.createElement(QStringLiteral("rotation"));
1309 generalInfo.appendChild(rotationNode);
1310 rotationNode.appendChild(doc.createTextNode(QString::number((int)m_rotation)));
1311 }
1312 // <general info><history> ... </history> save history up to OKULAR_HISTORY_SAVEDSTEPS viewports
1313 const auto currentViewportIterator = std::list<DocumentViewport>::const_iterator(m_viewportIterator);
1314 std::list<DocumentViewport>::const_iterator backIterator = currentViewportIterator;
1315 if (backIterator != m_viewportHistory.end()) {
1316 // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator
1317 int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
1318 while (backSteps-- && backIterator != m_viewportHistory.begin()) {
1319 --backIterator;
1320 }
1321
1322 // create history root node
1323 QDomElement historyNode = doc.createElement(QStringLiteral("history"));
1324 generalInfo.appendChild(historyNode);
1325
1326 // add old[backIterator] and present[viewportIterator] items
1327 std::list<DocumentViewport>::const_iterator endIt = currentViewportIterator;
1328 ++endIt;
1329 while (backIterator != endIt) {
1330 QString name = (backIterator == currentViewportIterator) ? QStringLiteral("current") : QStringLiteral("oldPage");
1331 QDomElement historyEntry = doc.createElement(name);
1332 historyEntry.setAttribute(QStringLiteral("viewport"), (*backIterator).toString());
1333 historyNode.appendChild(historyEntry);
1334 ++backIterator;
1335 }
1336 }
1337 // create views root node
1338 QDomElement viewsNode = doc.createElement(QStringLiteral("views"));
1339 generalInfo.appendChild(viewsNode);
1340 for (View *view : std::as_const(m_views)) {
1341 QDomElement viewEntry = doc.createElement(QStringLiteral("view"));
1342 viewEntry.setAttribute(QStringLiteral("name"), view->name());
1343 viewsNode.appendChild(viewEntry);
1344 saveViewsInfo(view, viewEntry);
1345 }
1346
1347 // 3. Save DOM to XML file
1348 QString xml = doc.toString();
1349 QTextStream os(&infoFile);
1350 os.setEncoding(QStringConverter::Utf8);
1351 os << xml;
1352 infoFile.close();
1353}
1354
1355void DocumentPrivate::slotTimedMemoryCheck()
1356{
1357 // [MEM] clean memory (for 'free mem dependent' profiles only)
1358 if (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024 * 1024) {
1359 cleanupPixmapMemory();
1360 }
1361}
1362
1363void DocumentPrivate::sendGeneratorPixmapRequest()
1364{
1365 /* If the pixmap cache will have to be cleaned in order to make room for the
1366 * next request, get the distance from the current viewport of the page
1367 * whose pixmap will be removed. We will ignore preload requests for pages
1368 * that are at the same distance or farther */
1369 const qulonglong memoryToFree = calculateMemoryToFree();
1370 const int currentViewportPage = (*m_viewportIterator).pageNumber;
1371 int maxDistance = INT_MAX; // Default: No maximum
1372 if (memoryToFree) {
1373 const AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap(true);
1374 if (pixmapToReplace) {
1375 maxDistance = qAbs(pixmapToReplace->page - currentViewportPage);
1376 }
1377 }
1378
1379 // find a request
1380 PixmapRequest *request = nullptr;
1381 m_pixmapRequestsMutex.lock();
1382 while (!m_pixmapRequestsStack.empty() && !request) {
1383 PixmapRequest *r = m_pixmapRequestsStack.back();
1384 if (!r) {
1385 m_pixmapRequestsStack.pop_back();
1386 continue;
1387 }
1388
1389 QRect requestRect = r->isTile() ? r->normalizedRect().geometry(r->width(), r->height()) : QRect(0, 0, r->width(), r->height());
1390 TilesManager *tilesManager = r->d->tilesManager();
1391 const double normalizedArea = r->normalizedRect().width() * r->normalizedRect().height();
1392 const QScreen *screen = nullptr;
1393 if (m_widget) {
1394 const QWindow *window = m_widget->window()->windowHandle();
1395 if (window) {
1396 screen = window->screen();
1397 }
1398 }
1399 if (!screen) {
1401 }
1402 const long screenSize = screen->devicePixelRatio() * screen->size().width() * screen->devicePixelRatio() * screen->size().height();
1403
1404 // Make sure the page is the right size to receive the pixmap
1405 r->page()->setPageSize(r->observer(), r->width(), r->height());
1406
1407 // If it's a preload but the generator is not threaded no point in trying to preload
1408 if (r->preload() && !m_generator->hasFeature(Generator::Threaded)) {
1409 m_pixmapRequestsStack.pop_back();
1410 delete r;
1411 }
1412 // request only if page isn't already present and request has valid id
1413 else if ((!r->d->mForce && r->page()->hasPixmap(r->observer(), r->width(), r->height(), r->normalizedRect())) || !m_observers.contains(r->observer())) {
1414 m_pixmapRequestsStack.pop_back();
1415 delete r;
1416 } else if (!r->d->mForce && r->preload() && qAbs(r->pageNumber() - currentViewportPage) >= maxDistance) {
1417 m_pixmapRequestsStack.pop_back();
1418 // qCDebug(OkularCoreDebug) << "Ignoring request that doesn't fit in cache";
1419 delete r;
1420 }
1421 // Ignore requests for pixmaps that are already being generated
1422 else if (tilesManager && tilesManager->isRequesting(r->normalizedRect(), r->width(), r->height())) {
1423 m_pixmapRequestsStack.pop_back();
1424 delete r;
1425 }
1426 // If the requested area is above 4*screenSize pixels, and we're not rendering most of the page, switch on the tile manager
1427 else if (!tilesManager && m_generator->hasFeature(Generator::TiledRendering) && (long)r->width() * (long)r->height() > 4L * screenSize && normalizedArea < 0.75) {
1428 // if the image is too big. start using tiles
1429 qCDebug(OkularCoreDebug).nospace() << "Start using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1430
1431 // fill the tiles manager with the last rendered pixmap
1432 const QPixmap *pixmap = r->page()->_o_nearestPixmap(r->observer(), r->width(), r->height());
1433 if (pixmap) {
1434 tilesManager = new TilesManager(r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation());
1435 tilesManager->setPixmap(pixmap, NormalizedRect(0, 0, 1, 1), true /*isPartialPixmap*/);
1436 tilesManager->setSize(r->width(), r->height());
1437 } else {
1438 // create new tiles manager
1439 tilesManager = new TilesManager(r->pageNumber(), r->width(), r->height(), r->page()->rotation());
1440 }
1441 tilesManager->setRequest(r->normalizedRect(), r->width(), r->height());
1442 r->page()->deletePixmap(r->observer());
1443 r->page()->d->setTilesManager(r->observer(), tilesManager);
1444 r->setTile(true);
1445
1446 // Change normalizedRect to the smallest rect that contains all
1447 // visible tiles.
1448 if (!r->normalizedRect().isNull()) {
1449 NormalizedRect tilesRect;
1450 const QList<Tile> tiles = tilesManager->tilesAt(r->normalizedRect(), TilesManager::TerminalTile);
1451 QList<Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
1452 while (tIt != tEnd) {
1453 Tile tile = *tIt;
1454 if (tilesRect.isNull()) {
1455 tilesRect = tile.rect();
1456 } else {
1457 tilesRect |= tile.rect();
1458 }
1459
1460 ++tIt;
1461 }
1462
1463 r->setNormalizedRect(tilesRect);
1464 request = r;
1465 } else {
1466 // Discard request if normalizedRect is null. This happens in
1467 // preload requests issued by PageView if the requested page is
1468 // not visible and the user has just switched from a non-tiled
1469 // zoom level to a tiled one
1470 m_pixmapRequestsStack.pop_back();
1471 delete r;
1472 }
1473 }
1474 // If the requested area is below 3*screenSize pixels, switch off the tile manager
1475 else if (tilesManager && (long)r->width() * (long)r->height() < 3L * screenSize) {
1476 qCDebug(OkularCoreDebug).nospace() << "Stop using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1477
1478 // page is too small. stop using tiles.
1479 r->page()->deletePixmap(r->observer());
1480 r->setTile(false);
1481
1482 request = r;
1483 } else if ((long)requestRect.width() * (long)requestRect.height() > 100L * screenSize && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy)) {
1484 m_pixmapRequestsStack.pop_back();
1485 if (!m_warnedOutOfMemory) {
1486 qCWarning(OkularCoreDebug).nospace() << "Running out of memory on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);";
1487 qCWarning(OkularCoreDebug) << "this message will be reported only once.";
1488 m_warnedOutOfMemory = true;
1489 }
1490 delete r;
1491 } else {
1492 request = r;
1493 }
1494 }
1495
1496 // if no request found (or already generated), return
1497 if (!request) {
1498 m_pixmapRequestsMutex.unlock();
1499 return;
1500 }
1501
1502 // [MEM] preventive memory freeing
1503 qulonglong pixmapBytes = 0;
1504 TilesManager *tm = request->d->tilesManager();
1505 if (tm) {
1506 pixmapBytes = tm->totalMemory();
1507 } else {
1508 pixmapBytes = 4 * request->width() * request->height();
1509 }
1510
1511 if (pixmapBytes > (1024 * 1024)) {
1512 cleanupPixmapMemory(memoryToFree /* previously calculated value */);
1513 }
1514
1515 // submit the request to the generator
1516 if (m_generator->canGeneratePixmap()) {
1517 QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height()) : request->normalizedRect().geometry(request->width(), request->height());
1518 qCDebug(OkularCoreDebug).nospace() << "sending request observer=" << request->observer() << " " << requestRect.width() << "x" << requestRect.height() << "@" << request->pageNumber() << " async == " << request->asynchronous()
1519 << " isTile == " << request->isTile();
1520 m_pixmapRequestsStack.remove(request);
1521
1522 if (tm) {
1523 tm->setRequest(request->normalizedRect(), request->width(), request->height());
1524 }
1525
1526 if ((int)m_rotation % 2) {
1527 request->d->swap();
1528 }
1529
1530 if (m_rotation != Rotation0 && !request->normalizedRect().isNull()) {
1531 request->setNormalizedRect(TilesManager::fromRotatedRect(request->normalizedRect(), m_rotation));
1532 }
1533
1534 // If set elsewhere we already know we want it to be partial
1535 if (!request->partialUpdatesWanted()) {
1536 request->setPartialUpdatesWanted(request->asynchronous() && !request->page()->hasPixmap(request->observer()));
1537 }
1538
1539 // we always have to unlock _before_ the generatePixmap() because
1540 // a sync generation would end with requestDone() -> deadlock, and
1541 // we can not really know if the generator can do async requests
1542 m_executingPixmapRequests.push_back(request);
1543 m_pixmapRequestsMutex.unlock();
1544 m_generator->generatePixmap(request);
1545 } else {
1546 m_pixmapRequestsMutex.unlock();
1547 // pino (7/4/2006): set the polling interval from 10 to 30
1548 QTimer::singleShot(30, m_parent, [this] { sendGeneratorPixmapRequest(); });
1549 }
1550}
1551
1552void DocumentPrivate::rotationFinished(int page, Okular::Page *okularPage)
1553{
1554 const Okular::Page *wantedPage = m_pagesVector.value(page, nullptr);
1555 if (!wantedPage || wantedPage != okularPage) {
1556 return;
1557 }
1558
1559 for (DocumentObserver *o : std::as_const(m_observers)) {
1560 o->notifyPageChanged(page, DocumentObserver::Pixmap | DocumentObserver::Annotations);
1561 }
1562}
1563
1564void DocumentPrivate::slotFontReadingProgress(int page)
1565{
1566 Q_EMIT m_parent->fontReadingProgress(page);
1567
1568 if (page >= (int)m_parent->pages() - 1) {
1569 Q_EMIT m_parent->fontReadingEnded();
1570 m_fontThread = nullptr;
1571 m_fontsCached = true;
1572 }
1573}
1574
1575void DocumentPrivate::fontReadingGotFont(const Okular::FontInfo &font)
1576{
1577 // Try to avoid duplicate fonts
1578 if (m_fontsCache.indexOf(font) == -1) {
1579 m_fontsCache.append(font);
1580
1581 Q_EMIT m_parent->gotFont(font);
1582 }
1583}
1584
1585void DocumentPrivate::slotGeneratorConfigChanged()
1586{
1587 if (!m_generator) {
1588 return;
1589 }
1590
1591 // reparse generator config and if something changed clear Pages
1592 bool configchanged = false;
1593 QHash<QString, GeneratorInfo>::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end();
1594 for (; it != itEnd; ++it) {
1595 Okular::ConfigInterface *iface = generatorConfig(it.value());
1596 if (iface) {
1597 bool it_changed = iface->reparseConfig();
1598 if (it_changed && (m_generator == it.value().generator)) {
1599 configchanged = true;
1600 }
1601 }
1602 }
1603 if (configchanged) {
1604 // invalidate pixmaps
1605 QVector<Page *>::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd();
1606 for (; it != end; ++it) {
1607 (*it)->deletePixmaps();
1608 }
1609
1610 // [MEM] remove allocation descriptors
1611 qDeleteAll(m_allocatedPixmaps);
1612 m_allocatedPixmaps.clear();
1613 m_allocatedPixmapsTotalMemory = 0;
1614
1615 // send reload signals to observers
1616 foreachObserverD(notifyContentsCleared(DocumentObserver::Pixmap));
1617 }
1618
1619 // free memory if in 'low' profile
1620 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.empty() && !m_pagesVector.isEmpty()) {
1621 cleanupPixmapMemory();
1622 }
1623}
1624
1625void DocumentPrivate::refreshPixmaps(int pageNumber)
1626{
1627 Page *page = m_pagesVector.value(pageNumber, nullptr);
1628 if (!page) {
1629 return;
1630 }
1631
1632 QMap<DocumentObserver *, PagePrivate::PixmapObject>::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd();
1633 QVector<Okular::PixmapRequest *> pixmapsToRequest;
1634 for (; it != itEnd; ++it) {
1635 const QSize size = (*it).m_pixmap->size();
1636 PixmapRequest *p = new PixmapRequest(it.key(), pageNumber, size.width(), size.height(), 1 /* dpr */, 1, PixmapRequest::Asynchronous);
1637 p->d->mForce = true;
1638 pixmapsToRequest << p;
1639 }
1640
1641 // Need to do this ↑↓ in two steps since requestPixmaps can end up calling cancelRenderingBecauseOf
1642 // which changes m_pixmaps and thus breaks the loop above
1643 for (PixmapRequest *pr : std::as_const(pixmapsToRequest)) {
1644 QList<Okular::PixmapRequest *> requestedPixmaps;
1645 requestedPixmaps.push_back(pr);
1646 m_parent->requestPixmaps(requestedPixmaps, Okular::Document::NoOption);
1647 }
1648
1649 for (DocumentObserver *observer : std::as_const(m_observers)) {
1650 QList<Okular::PixmapRequest *> requestedPixmaps;
1651
1652 TilesManager *tilesManager = page->d->tilesManager(observer);
1653 if (tilesManager) {
1654 tilesManager->markDirty();
1655
1656 PixmapRequest *p = new PixmapRequest(observer, pageNumber, tilesManager->width(), tilesManager->height(), 1 /* dpr */, 1, PixmapRequest::Asynchronous);
1657
1658 // Get the visible page rect
1659 NormalizedRect visibleRect;
1660 QVector<Okular::VisiblePageRect *>::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
1661 for (; vIt != vEnd; ++vIt) {
1662 if ((*vIt)->pageNumber == pageNumber) {
1663 visibleRect = (*vIt)->rect;
1664 break;
1665 }
1666 }
1667
1668 if (!visibleRect.isNull()) {
1669 p->setNormalizedRect(visibleRect);
1670 p->setTile(true);
1671 p->d->mForce = true;
1672 requestedPixmaps.push_back(p);
1673 } else {
1674 delete p;
1675 }
1676 }
1677
1678 m_parent->requestPixmaps(requestedPixmaps, Okular::Document::NoOption);
1679 }
1680}
1681
1682void DocumentPrivate::_o_configChanged()
1683{
1684 // free text pages if needed
1685 calculateMaxTextPages();
1686 while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) {
1687 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
1688 m_pagesVector.at(pageToKick)->setTextPage(nullptr); // deletes the textpage
1689 }
1690}
1691
1692void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct)
1693{
1694 DoContinueDirectionMatchSearchStruct *searchStruct = static_cast<DoContinueDirectionMatchSearchStruct *>(doContinueDirectionMatchSearchStruct);
1695 RunningSearch *search = m_searches.value(searchStruct->searchID);
1696
1697 if ((m_searchCancelled && !searchStruct->match) || !search) {
1698 // if the user cancelled but he just got a match, give him the match!
1700
1701 if (search) {
1702 search->isCurrentlySearching = false;
1703 }
1704
1705 Q_EMIT m_parent->searchFinished(searchStruct->searchID, Document::SearchCancelled);
1706 delete searchStruct->pagesToNotify;
1707 delete searchStruct;
1708 return;
1709 }
1710
1711 const bool forward = search->cachedType == Document::NextMatch;
1712 bool doContinue = false;
1713 // if no match found, loop through the whole doc, starting from currentPage
1714 if (!searchStruct->match) {
1715 const int pageCount = m_pagesVector.count();
1716 if (search->pagesDone < pageCount) {
1717 doContinue = true;
1718 if (searchStruct->currentPage >= pageCount) {
1719 searchStruct->currentPage = 0;
1720 Q_EMIT m_parent->notice(i18n("Continuing search from beginning"), 3000);
1721 } else if (searchStruct->currentPage < 0) {
1722 searchStruct->currentPage = pageCount - 1;
1723 Q_EMIT m_parent->notice(i18n("Continuing search from bottom"), 3000);
1724 }
1725 }
1726 }
1727
1728 if (doContinue) {
1729 // get page
1730 const Page *page = m_pagesVector[searchStruct->currentPage];
1731 // request search page if needed
1732 if (!page->hasTextPage()) {
1733 m_parent->requestTextPage(page->number());
1734 }
1735
1736 // if found a match on the current page, end the loop
1737 searchStruct->match = page->findText(searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity);
1738 if (!searchStruct->match) {
1739 if (forward) {
1740 searchStruct->currentPage++;
1741 } else {
1742 searchStruct->currentPage--;
1743 }
1744 search->pagesDone++;
1745 } else {
1746 search->pagesDone = 1;
1747 }
1748
1749 // Both of the previous if branches need to call doContinueDirectionMatchSearch
1750 QTimer::singleShot(0, m_parent, [this, searchStruct] { doContinueDirectionMatchSearch(searchStruct); });
1751 } else {
1752 doProcessSearchMatch(searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor);
1753 delete searchStruct;
1754 }
1755}
1756
1757void DocumentPrivate::doProcessSearchMatch(RegularAreaRect *match, RunningSearch *search, QSet<int> *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor &color)
1758{
1759 // reset cursor to previous shape
1761
1762 bool foundAMatch = false;
1763
1764 search->isCurrentlySearching = false;
1765
1766 // if a match has been found..
1767 if (match) {
1768 // update the RunningSearch structure adding this match..
1769 foundAMatch = true;
1770 search->continueOnPage = currentPage;
1771 search->continueOnMatch = *match;
1772 search->highlightedPages.insert(currentPage);
1773 // ..add highlight to the page..
1774 m_pagesVector[currentPage]->d->setHighlight(searchID, match, color);
1775
1776 // ..queue page for notifying changes..
1777 pagesToNotify->insert(currentPage);
1778
1779 // Create a normalized rectangle around the search match that includes a 5% buffer on all sides.
1780 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);
1781
1782 const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible(matchRectWithBuffer, currentPage);
1783
1784 // ..move the viewport to show the first of the searched word sequence centered
1785 if (moveViewport && !matchRectFullyVisible) {
1786 DocumentViewport searchViewport(currentPage);
1787 searchViewport.rePos.enabled = true;
1788 searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0;
1789 searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0;
1790 m_parent->setViewport(searchViewport, nullptr, true);
1791 }
1792 delete match;
1793 }
1794
1795 // notify observers about highlights changes
1796 for (int pageNumber : std::as_const(*pagesToNotify)) {
1797 for (DocumentObserver *observer : std::as_const(m_observers)) {
1798 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
1799 }
1800 }
1801
1802 if (foundAMatch) {
1803 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1804 } else {
1805 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1806 }
1807
1808 delete pagesToNotify;
1809}
1810
1811void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID)
1812{
1813 QMap<Page *, QVector<RegularAreaRect *>> *pageMatches = static_cast<QMap<Page *, QVector<RegularAreaRect *>> *>(pageMatchesMap);
1814 QSet<int> *pagesToNotify = static_cast<QSet<int> *>(pagesToNotifySet);
1815 RunningSearch *search = m_searches.value(searchID);
1816
1817 if (m_searchCancelled || !search) {
1818 typedef QVector<RegularAreaRect *> MatchesVector;
1819
1821
1822 if (search) {
1823 search->isCurrentlySearching = false;
1824 }
1825
1826 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1827 for (const MatchesVector &mv : std::as_const(*pageMatches)) {
1828 qDeleteAll(mv);
1829 }
1830 delete pageMatches;
1831 delete pagesToNotify;
1832 return;
1833 }
1834
1835 if (currentPage < m_pagesVector.count()) {
1836 // get page (from the first to the last)
1837 Page *page = m_pagesVector.at(currentPage);
1838 int pageNumber = page->number(); // redundant? is it == currentPage ?
1839
1840 // request search page if needed
1841 if (!page->hasTextPage()) {
1842 m_parent->requestTextPage(pageNumber);
1843 }
1844
1845 // loop on a page adding highlights for all found items
1846 RegularAreaRect *lastMatch = nullptr;
1847 while (true) {
1848 if (lastMatch) {
1849 lastMatch = page->findText(searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch);
1850 } else {
1851 lastMatch = page->findText(searchID, search->cachedString, FromTop, search->cachedCaseSensitivity);
1852 }
1853
1854 if (!lastMatch) {
1855 break;
1856 }
1857
1858 // add highlight rect to the matches map
1859 (*pageMatches)[page].append(lastMatch);
1860 }
1861 delete lastMatch;
1862
1863 QTimer::singleShot(0, m_parent, [this, pagesToNotifySet, pageMatches, currentPage, searchID] { doContinueAllDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID); });
1864 } else {
1865 // reset cursor to previous shape
1867
1868 search->isCurrentlySearching = false;
1869 bool foundAMatch = pageMatches->count() != 0;
1870 QMap<Page *, QVector<RegularAreaRect *>>::const_iterator it, itEnd;
1871 it = pageMatches->constBegin();
1872 itEnd = pageMatches->constEnd();
1873 for (; it != itEnd; ++it) {
1874 for (RegularAreaRect *match : it.value()) {
1875 it.key()->d->setHighlight(searchID, match, search->cachedColor);
1876 delete match;
1877 }
1878 search->highlightedPages.insert(it.key()->number());
1879 pagesToNotify->insert(it.key()->number());
1880 }
1881
1882 for (DocumentObserver *observer : std::as_const(m_observers)) {
1883 observer->notifySetup(m_pagesVector, 0);
1884 }
1885
1886 // notify observers about highlights changes
1887 for (int pageNumber : std::as_const(*pagesToNotify)) {
1888 for (DocumentObserver *observer : std::as_const(m_observers)) {
1889 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
1890 }
1891 }
1892
1893 if (foundAMatch) {
1894 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1895 } else {
1896 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1897 }
1898
1899 delete pageMatches;
1900 delete pagesToNotify;
1901 }
1902}
1903
1904void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList &words)
1905{
1906 typedef QPair<RegularAreaRect *, QColor> MatchColor;
1907 QMap<Page *, QVector<MatchColor>> *pageMatches = static_cast<QMap<Page *, QVector<MatchColor>> *>(pageMatchesMap);
1908 QSet<int> *pagesToNotify = static_cast<QSet<int> *>(pagesToNotifySet);
1909 RunningSearch *search = m_searches.value(searchID);
1910
1911 if (m_searchCancelled || !search) {
1912 typedef QVector<MatchColor> MatchesVector;
1913
1915
1916 if (search) {
1917 search->isCurrentlySearching = false;
1918 }
1919
1920 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1921
1922 for (const MatchesVector &mv : std::as_const(*pageMatches)) {
1923 for (const MatchColor &mc : mv) {
1924 delete mc.first;
1925 }
1926 }
1927 delete pageMatches;
1928 delete pagesToNotify;
1929 return;
1930 }
1931
1932 const int wordCount = words.count();
1933 const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
1934 int baseHue, baseSat, baseVal;
1935 search->cachedColor.getHsv(&baseHue, &baseSat, &baseVal);
1936
1937 if (currentPage < m_pagesVector.count()) {
1938 // get page (from the first to the last)
1939 Page *page = m_pagesVector.at(currentPage);
1940 int pageNumber = page->number(); // redundant? is it == currentPage ?
1941
1942 // request search page if needed
1943 if (!page->hasTextPage()) {
1944 m_parent->requestTextPage(pageNumber);
1945 }
1946
1947 // loop on a page adding highlights for all found items
1948 bool allMatched = wordCount > 0, anyMatched = false;
1949 for (int w = 0; w < wordCount; w++) {
1950 const QString &word = words[w];
1951 int newHue = baseHue - w * hueStep;
1952 if (newHue < 0) {
1953 newHue += 360;
1954 }
1955 QColor wordColor = QColor::fromHsv(newHue, baseSat, baseVal);
1956 RegularAreaRect *lastMatch = nullptr;
1957 // add all highlights for current word
1958 bool wordMatched = false;
1959 while (true) {
1960 if (lastMatch) {
1961 lastMatch = page->findText(searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch);
1962 } else {
1963 lastMatch = page->findText(searchID, word, FromTop, search->cachedCaseSensitivity);
1964 }
1965
1966 if (!lastMatch) {
1967 break;
1968 }
1969
1970 // add highligh rect to the matches map
1971 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
1972 wordMatched = true;
1973 }
1974 allMatched = allMatched && wordMatched;
1975 anyMatched = anyMatched || wordMatched;
1976 }
1977
1978 // if not all words are present in page, remove partial highlights
1979 const bool matchAll = search->cachedType == Document::GoogleAll;
1980 if (!allMatched && matchAll) {
1981 const QVector<MatchColor> &matches = (*pageMatches)[page];
1982 for (const MatchColor &mc : matches) {
1983 delete mc.first;
1984 }
1985 pageMatches->remove(page);
1986 }
1987
1988 QTimer::singleShot(0, m_parent, [this, pagesToNotifySet, pageMatches, currentPage, searchID, words] { doContinueGooglesDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID, words); });
1989 } else {
1990 // reset cursor to previous shape
1992
1993 search->isCurrentlySearching = false;
1994 bool foundAMatch = pageMatches->count() != 0;
1995 QMap<Page *, QVector<MatchColor>>::const_iterator it, itEnd;
1996 it = pageMatches->constBegin();
1997 itEnd = pageMatches->constEnd();
1998 for (; it != itEnd; ++it) {
1999 for (const MatchColor &mc : it.value()) {
2000 it.key()->d->setHighlight(searchID, mc.first, mc.second);
2001 delete mc.first;
2002 }
2003 search->highlightedPages.insert(it.key()->number());
2004 pagesToNotify->insert(it.key()->number());
2005 }
2006
2007 // send page lists to update observers (since some filter on bookmarks)
2008 for (DocumentObserver *observer : std::as_const(m_observers)) {
2009 observer->notifySetup(m_pagesVector, 0);
2010 }
2011
2012 // notify observers about highlights changes
2013 for (int pageNumber : std::as_const(*pagesToNotify)) {
2014 for (DocumentObserver *observer : std::as_const(m_observers)) {
2015 observer->notifyPageChanged(pageNumber, DocumentObserver::Highlights);
2016 }
2017 }
2018
2019 if (foundAMatch) {
2020 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
2021 } else {
2022 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
2023 }
2024
2025 delete pageMatches;
2026 delete pagesToNotify;
2027 }
2028}
2029
2030QVariant DocumentPrivate::documentMetaData(const Generator::DocumentMetaDataKey key, const QVariant &option) const
2031{
2032 switch (key) {
2034 bool giveDefault = option.toBool();
2035 QColor color;
2036 if ((SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper) && SettingsCore::changeColors()) {
2037 color = SettingsCore::paperColor();
2038 } else if (giveDefault) {
2039 color = Qt::white;
2040 }
2041 return color;
2042 } break;
2043
2045 switch (SettingsCore::textAntialias()) {
2046 case SettingsCore::EnumTextAntialias::Enabled:
2047 return true;
2048 break;
2049 case SettingsCore::EnumTextAntialias::Disabled:
2050 return false;
2051 break;
2052 }
2053 break;
2054
2056 switch (SettingsCore::graphicsAntialias()) {
2057 case SettingsCore::EnumGraphicsAntialias::Enabled:
2058 return true;
2059 break;
2060 case SettingsCore::EnumGraphicsAntialias::Disabled:
2061 return false;
2062 break;
2063 }
2064 break;
2065
2067 switch (SettingsCore::textHinting()) {
2068 case SettingsCore::EnumTextHinting::Enabled:
2069 return true;
2070 break;
2071 case SettingsCore::EnumTextHinting::Disabled:
2072 return false;
2073 break;
2074 }
2075 break;
2076 }
2077 return QVariant();
2078}
2079
2080bool DocumentPrivate::isNormalizedRectangleFullyVisible(const Okular::NormalizedRect &rectOfInterest, int rectPage)
2081{
2082 bool rectFullyVisible = false;
2083 const QVector<Okular::VisiblePageRect *> &visibleRects = m_parent->visiblePageRects();
2086
2087 for (; (vIt != vEnd) && !rectFullyVisible; ++vIt) {
2088 if ((*vIt)->pageNumber == rectPage && (*vIt)->rect.contains(rectOfInterest.left, rectOfInterest.top) && (*vIt)->rect.contains(rectOfInterest.right, rectOfInterest.bottom)) {
2089 rectFullyVisible = true;
2090 }
2091 }
2092 return rectFullyVisible;
2093}
2094
2095struct pdfsyncpoint {
2096 QString file;
2097 qlonglong x;
2098 qlonglong y;
2099 int row;
2100 int column;
2101 int page;
2102};
2103
2104void DocumentPrivate::loadSyncFile(const QString &filePath)
2105{
2106 QFile f(filePath + QLatin1String("sync"));
2107 if (!f.open(QIODevice::ReadOnly)) {
2108 return;
2109 }
2110
2111 QTextStream ts(&f);
2112 // first row: core name of the pdf output
2113 const QString coreName = ts.readLine();
2114 // second row: version string, in the form 'Version %u'
2115 const QString versionstr = ts.readLine();
2116 // anchor the pattern with \A and \z to match the entire subject string
2117 // TODO: with Qt 5.12 QRegularExpression::anchoredPattern() can be used instead
2118 static QRegularExpression versionre(QStringLiteral("\\AVersion \\d+\\z"), QRegularExpression::CaseInsensitiveOption);
2119 QRegularExpressionMatch match = versionre.match(versionstr);
2120 if (!match.hasMatch()) {
2121 return;
2122 }
2123
2125 QStack<QString> fileStack;
2126 int currentpage = -1;
2127 const QLatin1String texStr(".tex");
2128 const QChar spaceChar = QChar::fromLatin1(' ');
2129
2130 fileStack.push(coreName + texStr);
2131
2132 const QSizeF dpi = m_generator->dpi();
2133
2134 QString line;
2135 while (!ts.atEnd()) {
2136 line = ts.readLine();
2137 const QStringList tokens = line.split(spaceChar, Qt::SkipEmptyParts);
2138 const int tokenSize = tokens.count();
2139 if (tokenSize < 1) {
2140 continue;
2141 }
2142 if (tokens.first() == QLatin1String("l") && tokenSize >= 3) {
2143 int id = tokens.at(1).toInt();
2145 if (it == points.constEnd()) {
2146 pdfsyncpoint pt;
2147 pt.x = 0;
2148 pt.y = 0;
2149 pt.row = tokens.at(2).toInt();
2150 pt.column = 0; // TODO
2151 pt.page = -1;
2152 pt.file = fileStack.top();
2153 points[id] = pt;
2154 }
2155 } else if (tokens.first() == QLatin1String("s") && tokenSize >= 2) {
2156 currentpage = tokens.at(1).toInt() - 1;
2157 } else if (tokens.first() == QLatin1String("p*") && tokenSize >= 4) {
2158 // TODO
2159 qCDebug(OkularCoreDebug) << "PdfSync: 'p*' line ignored";
2160 } else if (tokens.first() == QLatin1String("p") && tokenSize >= 4) {
2161 int id = tokens.at(1).toInt();
2163 if (it != points.end()) {
2164 it->x = tokens.at(2).toInt();
2165 it->y = tokens.at(3).toInt();
2166 it->page = currentpage;
2167 }
2168 } else if (line.startsWith(QLatin1Char('(')) && tokenSize == 1) {
2169 QString newfile = line;
2170 // chop the leading '('
2171 newfile.remove(0, 1);
2172 if (!newfile.endsWith(texStr)) {
2173 newfile += texStr;
2174 }
2175 fileStack.push(newfile);
2176 } else if (line == QLatin1String(")")) {
2177 if (!fileStack.isEmpty()) {
2178 fileStack.pop();
2179 } else {
2180 qCDebug(OkularCoreDebug) << "PdfSync: going one level down too much";
2181 }
2182 } else {
2183 qCDebug(OkularCoreDebug).nospace() << "PdfSync: unknown line format: '" << line << "'";
2184 }
2185 }
2186
2187 QVector<QList<Okular::SourceRefObjectRect *>> refRects(m_pagesVector.size());
2188 for (const pdfsyncpoint &pt : std::as_const(points)) {
2189 // drop pdfsync points not completely valid
2190 if (pt.page < 0 || pt.page >= m_pagesVector.size()) {
2191 continue;
2192 }
2193
2194 // magic numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels
2195 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()));
2196 QString file = pt.file;
2197 Okular::SourceReference *sourceRef = new Okular::SourceReference(file, pt.row, pt.column);
2198 refRects[pt.page].append(new Okular::SourceRefObjectRect(p, sourceRef));
2199 }
2200 for (int i = 0; i < refRects.size(); ++i) {
2201 if (!refRects.at(i).isEmpty()) {
2202 m_pagesVector[i]->setSourceReferences(refRects.at(i));
2203 }
2204 }
2205}
2206
2207void DocumentPrivate::clearAndWaitForRequests()
2208{
2209 m_pixmapRequestsMutex.lock();
2210 std::list<PixmapRequest *>::const_iterator sIt = m_pixmapRequestsStack.begin();
2211 std::list<PixmapRequest *>::const_iterator sEnd = m_pixmapRequestsStack.end();
2212 for (; sIt != sEnd; ++sIt) {
2213 delete *sIt;
2214 }
2215 m_pixmapRequestsStack.clear();
2216 m_pixmapRequestsMutex.unlock();
2217
2218 QEventLoop loop;
2219 bool startEventLoop = false;
2220 do {
2221 m_pixmapRequestsMutex.lock();
2222 startEventLoop = !m_executingPixmapRequests.empty();
2223
2224 if (m_generator->hasFeature(Generator::SupportsCancelling)) {
2225 for (PixmapRequest *executingRequest : std::as_const(m_executingPixmapRequests)) {
2226 executingRequest->d->mShouldAbortRender = 1;
2227 }
2228
2229 if (m_generator->d_ptr->mTextPageGenerationThread) {
2230 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
2231 }
2232 }
2233
2234 m_pixmapRequestsMutex.unlock();
2235 if (startEventLoop) {
2236 m_closingLoop = &loop;
2237 loop.exec();
2238 m_closingLoop = nullptr;
2239 }
2240 } while (startEventLoop);
2241}
2242
2243int DocumentPrivate::findFieldPageNumber(Okular::FormField *field)
2244{
2245 // Lookup the page of the FormField
2246 int foundPage = -1;
2247 for (uint pageIdx = 0, nPages = m_parent->pages(); pageIdx < nPages; pageIdx++) {
2248 const Page *p = m_parent->page(pageIdx);
2249 if (p && p->formFields().contains(field)) {
2250 foundPage = static_cast<int>(pageIdx);
2251 break;
2252 }
2253 }
2254 return foundPage;
2255}
2256
2257void DocumentPrivate::executeScriptEvent(const std::shared_ptr<Event> &event, const Okular::ScriptAction *linkscript)
2258{
2259 if (!m_scripter) {
2260 m_scripter = new Scripter(this);
2261 }
2262 m_scripter->setEvent(event.get());
2263 m_scripter->execute(linkscript->scriptType(), linkscript->script());
2264
2265 // Clear out the event after execution
2266 m_scripter->setEvent(nullptr);
2267}
2268
2270 : QObject(nullptr)
2271 , d(new DocumentPrivate(this))
2272{
2273 d->m_widget = widget;
2274 d->m_bookmarkManager = new BookmarkManager(d);
2275 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(), DocumentViewport());
2276 d->m_undoStack = new QUndoStack(this);
2277
2278 connect(SettingsCore::self(), &SettingsCore::configChanged, this, [this] { d->_o_configChanged(); });
2282
2283 qRegisterMetaType<Okular::FontInfo>();
2284}
2285
2287{
2288 // delete generator, pages, and related stuff
2289 closeDocument();
2290
2291 QSet<View *>::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd();
2292 for (; viewIt != viewEnd; ++viewIt) {
2293 View *v = *viewIt;
2294 v->d_func()->document = nullptr;
2295 }
2296
2297 // delete the bookmark manager
2298 delete d->m_bookmarkManager;
2299
2300 // delete the loaded generators
2301 QHash<QString, GeneratorInfo>::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd();
2302 for (; it != itEnd; ++it) {
2303 d->unloadGenerator(it.value());
2304 }
2305 d->m_loadedGenerators.clear();
2306
2307 // delete the private structure
2308 delete d;
2309}
2310
2311QString DocumentPrivate::docDataFileName(const QUrl &url, qint64 document_size)
2312{
2313 QString fn = url.fileName();
2314 fn = QString::number(document_size) + QLatin1Char('.') + fn + QStringLiteral(".xml");
2315 QString docdataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/okular/docdata");
2316 // make sure that the okular/docdata/ directory exists (probably this used to be handled by KStandardDirs)
2317 if (!QFileInfo::exists(docdataDir)) {
2318 qCDebug(OkularCoreDebug) << "creating docdata folder" << docdataDir;
2319 QDir().mkpath(docdataDir);
2320 }
2321 QString newokularfile = docdataDir + QLatin1Char('/') + fn;
2322
2323 return newokularfile;
2324}
2325
2326QVector<KPluginMetaData> DocumentPrivate::availableGenerators()
2327{
2328 static QVector<KPluginMetaData> result;
2329 if (result.isEmpty()) {
2330 result = KPluginMetaData::findPlugins(QStringLiteral("okular_generators"));
2331 }
2332 return result;
2333}
2334
2335KPluginMetaData DocumentPrivate::generatorForMimeType(const QMimeType &type, QWidget *widget, const QVector<KPluginMetaData> &triedOffers)
2336{
2337 // First try to find an exact match, and then look for more general ones (e. g. the plain text one)
2338 // Ideally we would rank these by "closeness", but that might be overdoing it
2339
2340 const QVector<KPluginMetaData> available = availableGenerators();
2341 QVector<KPluginMetaData> offers;
2342 QVector<KPluginMetaData> exactMatches;
2343
2344 QMimeDatabase mimeDatabase;
2345
2346 for (const KPluginMetaData &md : available) {
2347 if (triedOffers.contains(md)) {
2348 continue;
2349 }
2350
2351 const QStringList mimetypes = md.mimeTypes();
2352 for (const QString &supported : mimetypes) {
2353 QMimeType mimeType = mimeDatabase.mimeTypeForName(supported);
2354 if (mimeType == type && !exactMatches.contains(md)) {
2355 exactMatches << md;
2356 }
2357
2358 if (type.inherits(supported) && !offers.contains(md)) {
2359 offers << md;
2360 }
2361 }
2362 }
2363
2364 if (!exactMatches.isEmpty()) {
2365 offers = exactMatches;
2366 }
2367
2368 if (offers.isEmpty()) {
2369 return KPluginMetaData();
2370 }
2371 int hRank = 0;
2372 // best ranked offer search
2373 int offercount = offers.size();
2374 if (offercount > 1) {
2375 // sort the offers: the offers with an higher priority come before
2376 auto cmp = [](const KPluginMetaData &s1, const KPluginMetaData &s2) {
2377 const QString property = QStringLiteral("X-KDE-Priority");
2378 return s1.rawData().value(property).toInt() > s2.rawData().value(property).toInt();
2379 };
2380 std::stable_sort(offers.begin(), offers.end(), cmp);
2381
2382 if (SettingsCore::chooseGenerators()) {
2383 QStringList list;
2384 for (int i = 0; i < offercount; ++i) {
2385 list << offers.at(i).pluginId();
2386 }
2387 ChooseEngineDialog choose(list, type, widget);
2388
2389 if (choose.exec() == QDialog::Rejected) {
2390 return KPluginMetaData();
2391 }
2392
2393 hRank = choose.selectedGenerator();
2394 }
2395 }
2396 Q_ASSERT(hRank < offers.size());
2397 return offers.at(hRank);
2398}
2399
2400Document::OpenResult Document::openDocument(const QString &docFile, const QUrl &url, const QMimeType &_mime, const QString &password)
2401{
2402 QMimeDatabase db;
2403 QMimeType mime = _mime;
2404 QByteArray filedata;
2405 int fd = -1;
2406 if (url.scheme() == QLatin1String("fd")) {
2407 bool ok;
2408 fd = QStringView {url.path()}.mid(1).toInt(&ok);
2409 if (!ok) {
2410 return OpenError;
2411 }
2412 } else if (url.fileName() == QLatin1String("-")) {
2413 fd = 0;
2414 }
2415 bool triedMimeFromFileContent = false;
2416 if (fd < 0) {
2417 if (!mime.isValid()) {
2418 return OpenError;
2419 }
2420
2421 d->m_url = url;
2422 d->m_docFileName = docFile;
2423
2424 if (!d->updateMetadataXmlNameAndDocSize()) {
2425 return OpenError;
2426 }
2427 } else {
2428 QFile qstdin;
2429 const bool ret = qstdin.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle);
2430 if (!ret) {
2431 qWarning() << "failed to read" << url << filedata;
2432 return OpenError;
2433 }
2434
2435 filedata = qstdin.readAll();
2436 mime = db.mimeTypeForData(filedata);
2437 if (!mime.isValid() || mime.isDefault()) {
2438 return OpenError;
2439 }
2440 d->m_docSize = filedata.size();
2441 triedMimeFromFileContent = true;
2442 }
2443
2444 const bool fromFileDescriptor = fd >= 0;
2445
2446 // 0. load Generator
2447 // request only valid non-disabled plugins suitable for the mimetype
2448 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2449 if (!offer.isValid() && !triedMimeFromFileContent) {
2451 triedMimeFromFileContent = true;
2452 if (newmime != mime) {
2453 mime = newmime;
2454 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2455 }
2456 if (!offer.isValid()) {
2457 // There's still no offers, do a final mime search based on the filename
2458 // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we
2459 // use is the one fed by the server, that may be wrong
2460 newmime = db.mimeTypeForUrl(url);
2461
2462 if (!newmime.isDefault() && newmime != mime) {
2463 mime = newmime;
2464 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2465 }
2466 }
2467 }
2468 if (!offer.isValid()) {
2469 d->m_openError = i18n("Can not find a plugin which is able to handle the document being passed.");
2470 Q_EMIT error(d->m_openError, -1);
2471 qCWarning(OkularCoreDebug).nospace() << "No plugin for mimetype '" << mime.name() << "'.";
2472 return OpenError;
2473 }
2474
2475 // 1. load Document
2476 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2477 if (openResult == OpenError) {
2478 QVector<KPluginMetaData> triedOffers;
2479 triedOffers << offer;
2480 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2481
2482 while (offer.isValid()) {
2483 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2484
2485 if (openResult == OpenError) {
2486 triedOffers << offer;
2487 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2488 } else {
2489 break;
2490 }
2491 }
2492
2493 if (openResult == OpenError && !triedMimeFromFileContent) {
2495 triedMimeFromFileContent = true;
2496 if (newmime != mime) {
2497 mime = newmime;
2498 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2499 while (offer.isValid()) {
2500 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2501
2502 if (openResult == OpenError) {
2503 triedOffers << offer;
2504 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2505 } else {
2506 break;
2507 }
2508 }
2509 }
2510 }
2511
2512 if (openResult == OpenSuccess) {
2513 // Clear errors, since we're trying various generators, maybe one of them errored out
2514 // but we finally succeeded
2515 // TODO one can still see the error message animating out but since this is a very rare
2516 // condition we can leave this for future work
2517 Q_EMIT error(QString(), -1);
2518 }
2519 }
2520 if (openResult != OpenSuccess) {
2521 return openResult;
2522 }
2523
2524 // no need to check for the existence of a synctex file, no parser will be
2525 // created if none exists
2526 d->m_synctex_scanner = synctex_scanner_new_with_output_file(QFile::encodeName(docFile).constData(), nullptr, 1);
2527 if (!d->m_synctex_scanner && QFile::exists(docFile + QLatin1String("sync"))) {
2528 d->loadSyncFile(docFile);
2529 }
2530
2531 d->m_generatorName = offer.pluginId();
2532 d->m_pageController = new PageController();
2533 connect(d->m_pageController, &PageController::rotationFinished, this, [this](int p, Okular::Page *op) { d->rotationFinished(p, op); });
2534
2535 for (Page *p : std::as_const(d->m_pagesVector)) {
2536 p->d->m_doc = d;
2537 }
2538
2539 d->m_docdataMigrationNeeded = false;
2540
2541 // 2. load Additional Data (bookmarks, local annotations and metadata) about the document
2542 if (d->m_archiveData) {
2543 // QTemporaryFile is weird and will return false in exists if fileName wasn't called before
2544 d->m_archiveData->metadataFile.fileName();
2545 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2546 d->loadDocumentInfo(LoadGeneralInfo);
2547 } else {
2548 if (d->loadDocumentInfo(LoadPageInfo)) {
2549 d->m_docdataMigrationNeeded = true;
2550 }
2551 d->loadDocumentInfo(LoadGeneralInfo);
2552 }
2553
2554 d->m_bookmarkManager->setUrl(d->m_url);
2555
2556 // 3. setup observers internal lists and data
2557 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged));
2558
2559 // 4. set initial page (restoring the page saved in xml if loaded)
2560 DocumentViewport loadedViewport = (*d->m_viewportIterator);
2561 if (loadedViewport.isValid()) {
2562 (*d->m_viewportIterator) = DocumentViewport();
2563 if (loadedViewport.pageNumber >= (int)d->m_pagesVector.size()) {
2564 loadedViewport.pageNumber = d->m_pagesVector.size() - 1;
2565 }
2566 } else {
2567 loadedViewport.pageNumber = 0;
2568 }
2569 setViewport(loadedViewport);
2570
2571 // start bookmark saver timer
2572 if (!d->m_saveBookmarksTimer) {
2573 d->m_saveBookmarksTimer = new QTimer(this);
2574 connect(d->m_saveBookmarksTimer, &QTimer::timeout, this, [this] { d->saveDocumentInfo(); });
2575 }
2576 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2577
2578 // start memory check timer
2579 if (!d->m_memCheckTimer) {
2580 d->m_memCheckTimer = new QTimer(this);
2581 connect(d->m_memCheckTimer, &QTimer::timeout, this, [this] { d->slotTimedMemoryCheck(); });
2582 }
2583 d->m_memCheckTimer->start(kMemCheckTime);
2584
2585 const DocumentViewport nextViewport = d->nextDocumentViewport();
2586 if (nextViewport.isValid()) {
2587 setViewport(nextViewport);
2588 d->m_nextDocumentViewport = DocumentViewport();
2589 d->m_nextDocumentDestination = QString();
2590 }
2591
2592 AudioPlayer::instance()->setDocument(fromFileDescriptor ? QUrl() : d->m_url, this);
2593
2594 const QStringList docScripts = d->m_generator->metaData(QStringLiteral("DocumentScripts"), QStringLiteral("JavaScript")).toStringList();
2595 if (!docScripts.isEmpty()) {
2596 d->m_scripter = new Scripter(d);
2597 for (const QString &docscript : docScripts) {
2598 const Okular::ScriptAction *linkScript = new Okular::ScriptAction(Okular::JavaScript, docscript);
2599 std::shared_ptr<Event> event = Event::createDocEvent(Event::DocOpen);
2600 d->executeScriptEvent(event, linkScript);
2601 }
2602 }
2603
2604 return OpenSuccess;
2605}
2606
2607bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2608{
2609 // m_docFileName is always local so we can use QFileInfo on it
2610 QFileInfo fileReadTest(m_docFileName);
2611 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2612 return false;
2613 }
2614
2615 m_docSize = fileReadTest.size();
2616
2617 // determine the related "xml document-info" filename
2618 if (m_url.isLocalFile()) {
2619 const QString filePath = docDataFileName(m_url, m_docSize);
2620 qCDebug(OkularCoreDebug) << "Metadata file is now:" << filePath;
2621 m_xmlFileName = filePath;
2622 } else {
2623 qCDebug(OkularCoreDebug) << "Metadata file: disabled";
2624 m_xmlFileName = QString();
2625 }
2626
2627 return true;
2628}
2629
2631{
2632 if (d->m_generator) {
2634 if (iface) {
2635 return iface->guiClient();
2636 }
2637 }
2638 return nullptr;
2639}
2640
2642{
2643 // check if there's anything to close...
2644 if (!d->m_generator) {
2645 return;
2646 }
2647
2648 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(CloseDocument)) {
2649 processDocumentAction(action, CloseDocument);
2650 }
2651
2653
2654 delete d->m_pageController;
2655 d->m_pageController = nullptr;
2656
2657 delete d->m_scripter;
2658 d->m_scripter = nullptr;
2659
2660 // remove requests left in queue
2661 d->clearAndWaitForRequests();
2662
2663 if (d->m_fontThread) {
2664 disconnect(d->m_fontThread, nullptr, this, nullptr);
2665 d->m_fontThread->stopExtraction();
2666 d->m_fontThread->wait();
2667 d->m_fontThread = nullptr;
2668 }
2669
2670 // stop any audio playback
2672
2673 // close the current document and save document info if a document is still opened
2674 if (d->m_generator && d->m_pagesVector.size() > 0) {
2675 d->saveDocumentInfo();
2676
2677 // free the content of the opaque backend actions (if any)
2678 // this is a bit awkward since backends can store "random stuff" in the
2679 // BackendOpaqueAction nativeId qvariant so we need to tell them to free it
2680 // ideally we would just do that in the BackendOpaqueAction destructor
2681 // but that's too late in the cleanup process, i.e. the generator has already closed its document
2682 // and the document generator is nullptr
2683 for (const Page *p : std::as_const(d->m_pagesVector)) {
2684 const QList<ObjectRect *> &oRects = p->objectRects();
2685 for (const ObjectRect *oRect : oRects) {
2686 if (oRect->objectType() == ObjectRect::Action) {
2687 const Action *a = static_cast<const Action *>(oRect->object());
2688 const BackendOpaqueAction *backendAction = dynamic_cast<const BackendOpaqueAction *>(a);
2689 if (backendAction) {
2690 d->m_generator->freeOpaqueActionContents(*backendAction);
2691 }
2692 }
2693 }
2694
2695 const QList<FormField *> forms = p->formFields();
2696 for (const FormField *form : forms) {
2697 const QList<Action *> additionalActions = form->additionalActions();
2698 for (const Action *a : additionalActions) {
2699 const BackendOpaqueAction *backendAction = dynamic_cast<const BackendOpaqueAction *>(a);
2700 if (backendAction) {
2701 d->m_generator->freeOpaqueActionContents(*backendAction);
2702 }
2703 }
2704 }
2705 }
2706
2707 d->m_generator->closeDocument();
2708 }
2709
2710 if (d->m_synctex_scanner) {
2711 synctex_scanner_free(d->m_synctex_scanner);
2712 d->m_synctex_scanner = nullptr;
2713 }
2714
2715 // stop timers
2716 if (d->m_memCheckTimer) {
2717 d->m_memCheckTimer->stop();
2718 }
2719 if (d->m_saveBookmarksTimer) {
2720 d->m_saveBookmarksTimer->stop();
2721 }
2722
2723 if (d->m_generator) {
2724 // disconnect the generator from this document ...
2725 d->m_generator->d_func()->m_document = nullptr;
2726 // .. and this document from the generator signals
2727 disconnect(d->m_generator, nullptr, this, nullptr);
2728
2729 QHash<QString, GeneratorInfo>::const_iterator genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
2730 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2731 }
2732 d->m_generator = nullptr;
2733 d->m_generatorName = QString();
2734 d->m_url = QUrl();
2735 d->m_walletGenerator = nullptr;
2736 d->m_docFileName = QString();
2737 d->m_xmlFileName = QString();
2738 delete d->m_tempFile;
2739 d->m_tempFile = nullptr;
2740 delete d->m_archiveData;
2741 d->m_archiveData = nullptr;
2742 d->m_docSize = -1;
2743 d->m_exportCached = false;
2744 d->m_exportFormats.clear();
2745 d->m_exportToText = ExportFormat();
2746 d->m_fontsCached = false;
2747 d->m_fontsCache.clear();
2748 d->m_rotation = Rotation0;
2749
2750 // send an empty list to observers (to free their data)
2752
2753 // delete pages and clear 'd->m_pagesVector' container
2754 QVector<Page *>::const_iterator pIt = d->m_pagesVector.constBegin();
2755 QVector<Page *>::const_iterator pEnd = d->m_pagesVector.constEnd();
2756 for (; pIt != pEnd; ++pIt) {
2757 delete *pIt;
2758 }
2759 d->m_pagesVector.clear();
2760
2761 // clear 'memory allocation' descriptors
2762 qDeleteAll(d->m_allocatedPixmaps);
2763 d->m_allocatedPixmaps.clear();
2764
2765 // clear 'running searches' descriptors
2766 QMap<int, RunningSearch *>::const_iterator rIt = d->m_searches.constBegin();
2767 QMap<int, RunningSearch *>::const_iterator rEnd = d->m_searches.constEnd();
2768 for (; rIt != rEnd; ++rIt) {
2769 delete *rIt;
2770 }
2771 d->m_searches.clear();
2772
2773 // clear the visible areas and notify the observers
2774 QVector<VisiblePageRect *>::const_iterator vIt = d->m_pageRects.constBegin();
2775 QVector<VisiblePageRect *>::const_iterator vEnd = d->m_pageRects.constEnd();
2776 for (; vIt != vEnd; ++vIt) {
2777 delete *vIt;
2778 }
2779 d->m_pageRects.clear();
2780 foreachObserver(notifyVisibleRectsChanged());
2781
2782 // reset internal variables
2783
2784 d->m_viewportHistory.clear();
2785 d->m_viewportHistory.emplace_back();
2786 d->m_viewportIterator = d->m_viewportHistory.begin();
2787 d->m_allocatedPixmapsTotalMemory = 0;
2788 d->m_allocatedTextPagesFifo.clear();
2789 d->m_pageSize = PageSize();
2790 d->m_pageSizes.clear();
2791
2792 d->m_documentInfo = DocumentInfo();
2793 d->m_documentInfoAskedKeys.clear();
2794
2795 AudioPlayer::instance()->resetDocument();
2796
2797 d->m_undoStack->clear();
2798 d->m_docdataMigrationNeeded = false;
2799
2800#if HAVE_MALLOC_TRIM
2801 // trim unused memory, glibc should do this but it seems it does not
2802 // this can greatly decrease the [perceived] memory consumption of okular
2803 // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827
2804 malloc_trim(0);
2805#endif
2806}
2807
2809{
2810 Q_ASSERT(!d->m_observers.contains(pObserver));
2811 d->m_observers << pObserver;
2812
2813 // if the observer is added while a document is already opened, tell it
2814 if (!d->m_pagesVector.isEmpty()) {
2816 pObserver->notifyViewportChanged(false /*disables smoothMove*/);
2817 }
2818}
2819
2821{
2822 // remove observer from the set. it won't receive notifications anymore
2823 if (d->m_observers.contains(pObserver)) {
2824 // free observer's pixmap data
2825 QVector<Page *>::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd();
2826 for (; it != end; ++it) {
2827 (*it)->deletePixmap(pObserver);
2828 }
2829
2830 // [MEM] free observer's allocation descriptors
2831 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2832 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2833 while (aIt != aEnd) {
2834 AllocatedPixmap *p = *aIt;
2835 if (p->observer == pObserver) {
2836 aIt = d->m_allocatedPixmaps.erase(aIt);
2837 delete p;
2838 } else {
2839 ++aIt;
2840 }
2841 }
2842
2843 for (PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
2844 if (executingRequest->observer() == pObserver) {
2845 d->cancelRenderingBecauseOf(executingRequest, nullptr);
2846 }
2847 }
2848
2849 // remove observer entry from the set
2850 d->m_observers.remove(pObserver);
2851 }
2852}
2853
2855{
2856 // reparse generator config and if something changed clear Pages
2857 bool configchanged = false;
2858 if (d->m_generator) {
2860 if (iface) {
2861 configchanged = iface->reparseConfig();
2862 }
2863 }
2864 if (configchanged) {
2865 // invalidate pixmaps
2866 QVector<Page *>::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd();
2867 for (; it != end; ++it) {
2868 (*it)->deletePixmaps();
2869 }
2870
2871 // [MEM] remove allocation descriptors
2872 qDeleteAll(d->m_allocatedPixmaps);
2873 d->m_allocatedPixmaps.clear();
2874 d->m_allocatedPixmapsTotalMemory = 0;
2875
2876 // send reload signals to observers
2877 foreachObserver(notifyContentsCleared(DocumentObserver::Pixmap));
2878 }
2879
2880 // free memory if in 'low' profile
2881 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2882 d->cleanupPixmapMemory();
2883 }
2884}
2885
2887{
2888 return d->m_generator;
2889}
2890
2892{
2893 if (d->m_generator) {
2895 return iface ? true : false;
2896 } else {
2897 return false;
2898 }
2899}
2900
2901bool Document::sign(const NewSignatureData &data, const QString &newPath)
2902{
2903 if (d->m_generator->canSign()) {
2904 return d->m_generator->sign(data, newPath);
2905 } else {
2906 return false;
2907 }
2908}
2909
2911{
2912 return d->m_generator ? d->m_generator->certificateStore() : nullptr;
2913}
2914
2916{
2917 d->editorCommandOverride = editCmd;
2918}
2919
2921{
2922 return d->editorCommandOverride;
2923}
2924
2926{
2929 keys << ks;
2930 }
2931
2932 return documentInfo(keys);
2933}
2934
2936{
2937 DocumentInfo result = d->m_documentInfo;
2938 const QSet<DocumentInfo::Key> missingKeys = keys - d->m_documentInfoAskedKeys;
2939
2940 if (d->m_generator && !missingKeys.isEmpty()) {
2941 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2942
2943 if (missingKeys.contains(DocumentInfo::FilePath)) {
2944 info.set(DocumentInfo::FilePath, currentDocument().toDisplayString());
2945 }
2946
2947 if (d->m_docSize != -1 && missingKeys.contains(DocumentInfo::DocumentSize)) {
2948 const QString sizeString = KFormat().formatByteSize(d->m_docSize);
2949 info.set(DocumentInfo::DocumentSize, sizeString);
2950 }
2951 if (missingKeys.contains(DocumentInfo::PagesSize)) {
2952 const QString pagesSize = d->pagesSizeString();
2953 if (!pagesSize.isEmpty()) {
2954 info.set(DocumentInfo::PagesSize, pagesSize);
2955 }
2956 }
2957
2958 if (missingKeys.contains(DocumentInfo::Pages) && info.get(DocumentInfo::Pages).isEmpty()) {
2960 }
2961
2962 d->m_documentInfo.d->values.insert(info.d->values);
2963 d->m_documentInfo.d->titles.insert(info.d->titles);
2964 result.d->values.insert(info.d->values);
2965 result.d->titles.insert(info.d->titles);
2966 }
2967 d->m_documentInfoAskedKeys += keys;
2968
2969 return result;
2970}
2971
2973{
2974 return d->m_generator ? d->m_generator->generateDocumentSynopsis() : nullptr;
2975}
2976
2978{
2979 if (!d->m_generator || !d->m_generator->hasFeature(Generator::FontInfo) || d->m_fontThread) {
2980 return;
2981 }
2982
2983 if (d->m_fontsCached) {
2984 // in case we have cached fonts, simulate a reading
2985 // this way the API is the same, and users no need to care about the
2986 // internal caching
2987 for (int i = 0; i < d->m_fontsCache.count(); ++i) {
2988 Q_EMIT gotFont(d->m_fontsCache.at(i));
2990 }
2992 return;
2993 }
2994
2995 d->m_fontThread = new FontExtractionThread(d->m_generator, pages());
2996 connect(d->m_fontThread, &FontExtractionThread::gotFont, this, [this](const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2997 connect(d->m_fontThread.data(), &FontExtractionThread::progress, this, [this](int p) { d->slotFontReadingProgress(p); });
2998
2999 d->m_fontThread->startExtraction(/*d->m_generator->hasFeature( Generator::Threaded )*/ true);
3000}
3001
3003{
3004 if (!d->m_fontThread) {
3005 return;
3006 }
3007
3008 disconnect(d->m_fontThread, nullptr, this, nullptr);
3009 d->m_fontThread->stopExtraction();
3010 d->m_fontThread = nullptr;
3011 d->m_fontsCache.clear();
3012}
3013
3015{
3016 return d->m_generator ? d->m_generator->hasFeature(Generator::FontInfo) : false;
3017}
3018
3020{
3021 return d->m_generator ? d->m_generator->canSign() : false;
3022}
3023
3025{
3026 return d->m_generator ? d->m_generator->embeddedFiles() : nullptr;
3027}
3028
3029const Page *Document::page(int n) const
3030{
3031 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) : nullptr;
3032}
3033
3035{
3036 return (*d->m_viewportIterator);
3037}
3038
3040{
3041 return d->m_pageRects;
3042}
3043
3045{
3046 QVector<VisiblePageRect *>::const_iterator vIt = d->m_pageRects.constBegin();
3047 QVector<VisiblePageRect *>::const_iterator vEnd = d->m_pageRects.constEnd();
3048 for (; vIt != vEnd; ++vIt) {
3049 delete *vIt;
3050 }
3051 d->m_pageRects = visiblePageRects;
3052 // notify change to all other (different from id) observers
3053 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3054 if (o != excludeObserver) {
3055 o->notifyVisibleRectsChanged();
3056 }
3057 }
3058}
3059
3061{
3062 return (*d->m_viewportIterator).pageNumber;
3063}
3064
3066{
3067 return d->m_pagesVector.size();
3068}
3069
3071{
3072 return d->m_url;
3073}
3074
3076{
3077 if (action == Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3078 return false;
3079 }
3080 if (action == Okular::AllowFillForms && d->m_docdataMigrationNeeded) {
3081 return false;
3082 }
3083
3084#if !OKULAR_FORCE_DRM
3085 if (KAuthorized::authorize(QStringLiteral("skip_drm")) && !SettingsCore::obeyDRM()) {
3086 return true;
3087 }
3088#endif
3089
3090 return d->m_generator ? d->m_generator->isAllowed(action) : false;
3091}
3092
3094{
3095 return d->m_generator ? d->m_generator->hasFeature(Generator::TextExtraction) : false;
3096}
3097
3099{
3100 return d->m_generator ? d->m_generator->hasFeature(Generator::PageSizes) : false;
3101}
3102
3104{
3105 return d->m_generator ? d->m_generator->hasFeature(Generator::TiledRendering) : false;
3106}
3107
3108PageSize::List Document::pageSizes() const
3109{
3110 if (d->m_generator) {
3111 if (d->m_pageSizes.isEmpty()) {
3112 d->m_pageSizes = d->m_generator->pageSizes();
3113 }
3114 return d->m_pageSizes;
3115 }
3116 return PageSize::List();
3117}
3118
3120{
3121 if (!d->m_generator) {
3122 return false;
3123 }
3124
3125 d->cacheExportFormats();
3126 return !d->m_exportToText.isNull();
3127}
3128
3129bool Document::exportToText(const QString &fileName) const
3130{
3131 if (!d->m_generator) {
3132 return false;
3133 }
3134
3135 d->cacheExportFormats();
3136 if (d->m_exportToText.isNull()) {
3137 return false;
3138 }
3139
3140 return d->m_generator->exportTo(fileName, d->m_exportToText);
3141}
3142
3143ExportFormat::List Document::exportFormats() const
3144{
3145 if (!d->m_generator) {
3146 return ExportFormat::List();
3147 }
3148
3149 d->cacheExportFormats();
3150 return d->m_exportFormats;
3151}
3152
3153bool Document::exportTo(const QString &fileName, const ExportFormat &format) const
3154{
3155 return d->m_generator ? d->m_generator->exportTo(fileName, format) : false;
3156}
3157
3159{
3160 return d->m_viewportIterator == d->m_viewportHistory.begin();
3161}
3162
3164{
3165 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3166}
3167
3168QVariant Document::metaData(const QString &key, const QVariant &option) const
3169{
3170 // if option starts with "src:" assume that we are handling a
3171 // source reference
3172 if (key == QLatin1String("NamedViewport") && option.toString().startsWith(QLatin1String("src:"), Qt::CaseInsensitive) && d->m_synctex_scanner) {
3173 const QString reference = option.toString();
3174
3175 // The reference is of form "src:1111Filename", where "1111"
3176 // points to line number 1111 in the file "Filename".
3177 // Extract the file name and the numeral part from the reference string.
3178 // This will fail if Filename starts with a digit.
3179 QString name, lineString;
3180 // Remove "src:". Presence of substring has been checked before this
3181 // function is called.
3182 name = reference.mid(4);
3183 // split
3184 int nameLength = name.length();
3185 int i = 0;
3186 for (i = 0; i < nameLength; ++i) {
3187 if (!name[i].isDigit()) {
3188 break;
3189 }
3190 }
3191 lineString = name.left(i);
3192 name = name.mid(i);
3193 // Remove spaces.
3194 name = name.trimmed();
3195 lineString = lineString.trimmed();
3196 // Convert line to integer.
3197 bool ok;
3198 int line = lineString.toInt(&ok);
3199 if (!ok) {
3200 line = -1;
3201 }
3202
3203 // Use column == -1 for now.
3204 if (synctex_display_query(d->m_synctex_scanner, QFile::encodeName(name).constData(), line, -1, 0) > 0) {
3205 synctex_node_p node;
3206 // For now use the first hit. Could possibly be made smarter
3207 // in case there are multiple hits.
3208 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3210
3211 // TeX pages start at 1.
3212 viewport.pageNumber = synctex_node_page(node) - 1;
3213
3214 if (viewport.pageNumber >= 0) {
3215 const QSizeF dpi = d->m_generator->dpi();
3216
3217 // TeX small points ...
3218 double px = (synctex_node_visible_h(node) * dpi.width()) / 72.27;
3219 double py = (synctex_node_visible_v(node) * dpi.height()) / 72.27;
3220 viewport.rePos.normalizedX = px / page(viewport.pageNumber)->width();
3221 viewport.rePos.normalizedY = (py + 0.5) / page(viewport.pageNumber)->height();
3222 viewport.rePos.enabled = true;
3224
3225 return viewport.toString();
3226 }
3227 }
3228 }
3229 }
3230 return d->m_generator ? d->m_generator->metaData(key, option) : QVariant();
3231}
3232
3234{
3235 return d->m_rotation;
3236}
3237
3239{
3240 bool allPagesSameSize = true;
3241 QSizeF size;
3242 for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3243 const Page *p = d->m_pagesVector.at(i);
3244 if (i == 0) {
3245 size = QSizeF(p->width(), p->height());
3246 } else {
3247 allPagesSameSize = (size == QSizeF(p->width(), p->height()));
3248 }
3249 }
3250 if (allPagesSameSize) {
3251 return size;
3252 } else {
3253 return QSizeF();
3254 }
3255}
3256
3258{
3259 if (d->m_generator) {
3260 if (d->m_generator->pagesSizeMetric() != Generator::None) {
3261 const Page *p = d->m_pagesVector.at(page);
3262 return d->localizedSize(QSizeF(p->width(), p->height()));
3263 }
3264 }
3265 return QString();
3266}
3267
3268static bool shouldCancelRenderingBecauseOf(const PixmapRequest &executingRequest, const PixmapRequest &otherRequest)
3269{
3270 // New request has higher priority -> cancel
3271 if (executingRequest.priority() > otherRequest.priority()) {
3272 return true;
3273 }
3274
3275 // New request has lower priority -> don't cancel
3276 if (executingRequest.priority() < otherRequest.priority()) {
3277 return false;
3278 }
3279
3280 // New request has same priority and is from a different observer -> don't cancel
3281 // AFAIK this never happens since all observers have different priorities
3282 if (executingRequest.observer() != otherRequest.observer()) {
3283 return false;
3284 }
3285
3286 // Same priority and observer, different page number -> don't cancel
3287 // may still end up cancelled later in the parent caller if none of the requests
3288 // is of the executingRequest page and RemoveAllPrevious is specified
3289 if (executingRequest.pageNumber() != otherRequest.pageNumber()) {
3290 return false;
3291 }
3292
3293 // Same priority, observer, page, different size -> cancel
3294 if (executingRequest.width() != otherRequest.width()) {
3295 return true;
3296 }
3297
3298 // Same priority, observer, page, different size -> cancel
3299 if (executingRequest.height() != otherRequest.height()) {
3300 return true;
3301 }
3302
3303 // Same priority, observer, page, different tiling -> cancel
3304 if (executingRequest.isTile() != otherRequest.isTile()) {
3305 return true;
3306 }
3307
3308 // Same priority, observer, page, different tiling -> cancel
3309 if (executingRequest.isTile()) {
3310 const NormalizedRect bothRequestsRect = executingRequest.normalizedRect() | otherRequest.normalizedRect();
3311 if (!(bothRequestsRect == executingRequest.normalizedRect())) {
3312 return true;
3313 }
3314 }
3315
3316 return false;
3317}
3318
3319bool DocumentPrivate::cancelRenderingBecauseOf(PixmapRequest *executingRequest, PixmapRequest *newRequest)
3320{
3321 // No point in aborting the rendering already finished, let it go through
3322 if (!executingRequest->d->mResultImage.isNull()) {
3323 return false;
3324 }
3325
3326 if (newRequest && newRequest->asynchronous() && executingRequest->partialUpdatesWanted()) {
3327 newRequest->setPartialUpdatesWanted(true);
3328 }
3329
3330 TilesManager *tm = executingRequest->d->tilesManager();
3331 if (tm) {
3332 tm->setPixmap(nullptr, executingRequest->normalizedRect(), true /*isPartialPixmap*/);
3333 tm->setRequest(NormalizedRect(), 0, 0);
3334 }
3335 PagePrivate::PixmapObject object = executingRequest->page()->d->m_pixmaps.take(executingRequest->observer());
3336 delete object.m_pixmap;
3337
3338 if (executingRequest->d->mShouldAbortRender != 0) {
3339 return false;
3340 }
3341
3342 executingRequest->d->mShouldAbortRender = 1;
3343
3344 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->page()) {
3345 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3346 }
3347
3348 return true;
3349}
3350
3352{
3354}
3355
3356void Document::requestPixmaps(const QList<PixmapRequest *> &requests, PixmapRequestFlags reqOptions)
3357{
3358 if (requests.isEmpty()) {
3359 return;
3360 }
3361
3362 if (!d->m_pageController) {
3363 // delete requests..
3364 qDeleteAll(requests);
3365 // ..and return
3366 return;
3367 }
3368
3369 QSet<DocumentObserver *> observersPixmapCleared;
3370
3371 // 1. [CLEAN STACK] remove previous requests of requesterID
3372 const DocumentObserver *requesterObserver = requests.first()->observer();
3373 QSet<int> requestedPages;
3374 {
3375 for (const PixmapRequest *request : requests) {
3376 Q_ASSERT(request->observer() == requesterObserver);
3377 requestedPages.insert(request->pageNumber());
3378 }
3379 }
3380 const bool removeAllPrevious = reqOptions & RemoveAllPrevious;
3381 d->m_pixmapRequestsMutex.lock();
3382 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3383 while (sIt != sEnd) {
3384 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.contains((*sIt)->pageNumber()))) {
3385 // delete request and remove it from stack
3386 delete *sIt;
3387 sIt = d->m_pixmapRequestsStack.erase(sIt);
3388 } else {
3389 ++sIt;
3390 }
3391 }
3392
3393 // 1.B [PREPROCESS REQUESTS] tweak some values of the requests
3394 for (PixmapRequest *request : requests) {
3395 // set the 'page field' (see PixmapRequest) and check if it is valid
3396 qCDebug(OkularCoreDebug).nospace() << "request observer=" << request->observer() << " " << request->width() << "x" << request->height() << "@" << request->pageNumber();
3397 if (d->m_pagesVector.value(request->pageNumber()) == nullptr) {
3398 // skip requests referencing an invalid page (must not happen)
3399 delete request;
3400 continue;
3401 }
3402
3403 request->d->mPage = d->m_pagesVector.value(request->pageNumber());
3404
3405 if (request->isTile()) {
3406 // Change the current request rect so that only invalid tiles are
3407 // requested. Also make sure the rect is tile-aligned.
3408 NormalizedRect tilesRect;
3409 const QList<Tile> tiles = request->d->tilesManager()->tilesAt(request->normalizedRect(), TilesManager::TerminalTile);
3410 QList<Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
3411 while (tIt != tEnd) {
3412 const Tile &tile = *tIt;
3413 if (!tile.isValid()) {
3414 if (tilesRect.isNull()) {
3415 tilesRect = tile.rect();
3416 } else {
3417 tilesRect |= tile.rect();
3418 }
3419 }
3420
3421 tIt++;
3422 }
3423
3424 request->setNormalizedRect(tilesRect);
3425 }
3426
3427 if (!request->asynchronous()) {
3428 request->d->mPriority = 0;
3429 }
3430 }
3431
3432 // 1.C [CANCEL REQUESTS] cancel those requests that are running and should be cancelled because of the new requests coming in
3433 if (d->m_generator->hasFeature(Generator::SupportsCancelling)) {
3434 for (PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
3435 bool newRequestsContainExecutingRequestPage = false;
3436 bool requestCancelled = false;
3437 for (PixmapRequest *newRequest : requests) {
3438 if (newRequest->pageNumber() == executingRequest->pageNumber() && requesterObserver == executingRequest->observer()) {
3439 newRequestsContainExecutingRequestPage = true;
3440 }
3441
3442 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3443 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3444 }
3445 }
3446
3447 // If we were told to remove all the previous requests and the executing request page is not part of the new requests, cancel it
3448 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->observer() && !newRequestsContainExecutingRequestPage) {
3449 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, nullptr);
3450 }
3451
3452 if (requestCancelled) {
3453 observersPixmapCleared << executingRequest->observer();
3454 }
3455 }
3456 }
3457
3458 // 2. [ADD TO STACK] add requests to stack
3459 for (PixmapRequest *request : requests) {
3460 // add request to the 'stack' at the right place
3461 if (!request->priority()) {
3462 // add priority zero requests to the top of the stack
3463 d->m_pixmapRequestsStack.push_back(request);
3464 } else {
3465 // insert in stack sorted by priority
3466 sIt = d->m_pixmapRequestsStack.begin();
3467 sEnd = d->m_pixmapRequestsStack.end();
3468 while (sIt != sEnd && (*sIt)->priority() > request->priority()) {
3469 ++sIt;
3470 }
3471 d->m_pixmapRequestsStack.insert(sIt, request);
3472 }
3473 }
3474 d->m_pixmapRequestsMutex.unlock();
3475
3476 // 3. [START FIRST GENERATION] if <NO>generator is ready, start a new generation,
3477 // or else (if gen is running) it will be started when the new contents will
3478 // come from generator (in requestDone())</NO>
3479 // all handling of requests put into sendGeneratorPixmapRequest
3480 // if ( generator->canRequestPixmap() )
3481 d->sendGeneratorPixmapRequest();
3482
3483 for (DocumentObserver *o : std::as_const(observersPixmapCleared)) {
3484 o->notifyContentsCleared(Okular::DocumentObserver::Pixmap);
3485 }
3486}
3487
3488void Document::requestTextPage(uint pageNumber)
3489{
3490 Page *kp = d->m_pagesVector[pageNumber];
3491 if (!d->m_generator || !kp) {
3492 return;
3493 }
3494
3495 // Memory management for TextPages
3496
3497 d->m_generator->generateTextPage(kp);
3498}
3499
3500void DocumentPrivate::notifyAnnotationChanges(int page)
3501{
3502 foreachObserverD(notifyPageChanged(page, DocumentObserver::Annotations));
3503}
3504
3505void DocumentPrivate::notifyFormChanges(int /*page*/)
3506{
3507 recalculateForms();
3508}
3509
3511{
3512 d->recalculateForms();
3513}
3514
3516{
3517 // Transform annotation's base boundary rectangle into unrotated coordinates
3518 Page *p = d->m_pagesVector[page];
3519 QTransform t = p->d->rotationMatrix();
3520 annotation->d_ptr->baseTransform(t.inverted());
3521 QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page);
3522 d->m_undoStack->push(uc);
3523}
3524
3526{
3527 if (!annotation || (annotation->flags() & Annotation::DenyWrite)) {
3528 return false;
3529 }
3530
3532 return false;
3533 }
3534
3535 if ((annotation->flags() & Annotation::External) && !d->canModifyExternalAnnotations()) {
3536 return false;
3537 }
3538
3539 switch (annotation->subType()) {
3540 case Annotation::AText:
3541 case Annotation::ALine:
3542 case Annotation::AGeom:
3544 case Annotation::AStamp:
3545 case Annotation::AInk:
3546 return true;
3548#if HAVE_NEW_SIGNATURE_API
3549 return dynamic_cast<const SignatureAnnotation *>(annotation);
3550#endif
3551 return false;
3552 default:
3553 return false;
3554 }
3555}
3556
3558{
3559 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3560 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3561 qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3562 return;
3563 }
3564 d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode();
3565}
3566
3568{
3569 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3570 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3571 qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3572 return;
3573 }
3574 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3575 QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand(d, annotation, page, prevProps, annotation->getAnnotationPropertiesDomNode());
3576 d->m_undoStack->push(uc);
3577 d->m_prevPropsOfAnnotBeingModified.clear();
3578}
3579
3581{
3582 int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0;
3583 QUndoCommand *uc = new Okular::TranslateAnnotationCommand(d, annotation, page, delta, complete);
3584 d->m_undoStack->push(uc);
3585}
3586
3588{
3589 const bool complete = (annotation->flags() & Okular::Annotation::BeingResized) == 0;
3590 QUndoCommand *uc = new Okular::AdjustAnnotationCommand(d, annotation, page, delta1, delta2, complete);
3591 d->m_undoStack->push(uc);
3592}
3593
3594void Document::editPageAnnotationContents(int page, Annotation *annotation, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
3595{
3596 QString prevContents = annotation->contents();
3597 QUndoCommand *uc = new EditAnnotationContentsCommand(d, annotation, page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3598 d->m_undoStack->push(uc);
3599}
3600
3602{
3603 if (!annotation || (annotation->flags() & Annotation::DenyDelete)) {
3604 return false;
3605 }
3606
3607 if ((annotation->flags() & Annotation::External) && !d->canRemoveExternalAnnotations()) {
3608 return false;
3609 }
3610
3611 switch (annotation->subType()) {
3612 case Annotation::AText:
3613 case Annotation::ALine:
3614 case Annotation::AGeom:
3616 case Annotation::AStamp:
3617 case Annotation::AInk:
3618 case Annotation::ACaret:
3619 return true;
3620 default:
3621 return false;
3622 }
3623}
3624
3626{
3627 QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page);
3628 d->m_undoStack->push(uc);
3629}
3630
3632{
3633 d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations"));
3634 for (Annotation *annotation : annotations) {
3635 QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page);
3636 d->m_undoStack->push(uc);
3637 }
3638 d->m_undoStack->endMacro();
3639}
3640
3641bool DocumentPrivate::canAddAnnotationsNatively() const
3642{
3643 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3644
3646 return true;
3647 }
3648
3649 return false;
3650}
3651
3652bool DocumentPrivate::canModifyExternalAnnotations() const
3653{
3654 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3655
3657 return true;
3658 }
3659
3660 return false;
3661}
3662
3663bool DocumentPrivate::canRemoveExternalAnnotations() const
3664{
3665 Okular::SaveInterface *iface = qobject_cast<Okular::SaveInterface *>(m_generator);
3666
3668 return true;
3669 }
3670
3671 return false;
3672}
3673
3674void Document::setPageTextSelection(int page, std::unique_ptr<RegularAreaRect> &&rect, const QColor &color)
3675{
3676 Page *kp = d->m_pagesVector[page];
3677 if (!d->m_generator || !kp) {
3678 return;
3679 }
3680
3681 // add or remove the selection basing whether rect is null or not
3682 if (rect) {
3683 kp->d->setTextSelections(*rect, color);
3684 } else {
3685 kp->d->deleteTextSelections();
3686 }
3687
3688 // notify observers about the change
3689 foreachObserver(notifyPageChanged(page, DocumentObserver::TextSelection));
3690}
3691
3693{
3694 return d->m_undoStack->canUndo();
3695}
3696
3698{
3699 return d->m_undoStack->canRedo();
3700}
3701
3702/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
3703void Document::setNextPage()
3704{
3705 // advance page and set viewport on observers
3706 if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
3707 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) );
3708}
3709
3710void Document::setPrevPage()
3711{
3712 // go to previous page and set viewport on observers
3713 if ( (*d->m_viewportIterator).pageNumber > 0 )
3714 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) );
3715}
3716*/
3717
3718void Document::setViewport(const DocumentViewport &viewport, DocumentObserver *excludeObserver, bool smoothMove, bool updateHistory)
3719{
3720 if (!viewport.isValid()) {
3721 qCDebug(OkularCoreDebug) << "invalid viewport:" << viewport.toString();
3722 return;
3723 }
3724 if (viewport.pageNumber >= int(d->m_pagesVector.count())) {
3725 // qCDebug(OkularCoreDebug) << "viewport out of document:" << viewport.toString();
3726 return;
3727 }
3728
3729 // if already broadcasted, don't redo it
3730 DocumentViewport &oldViewport = *d->m_viewportIterator;
3731 // disabled by enrico on 2005-03-18 (less debug output)
3732 // if ( viewport == oldViewport )
3733 // qCDebug(OkularCoreDebug) << "setViewport with the same viewport.";
3734
3735 const int oldPageNumber = oldViewport.pageNumber;
3736
3737 // set internal viewport taking care of history
3738 if (oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() || !updateHistory) {
3739 // if page is unchanged save the viewport at current position in queue
3740 oldViewport = viewport;
3741 } else {
3742 // remove elements after viewportIterator in queue
3743 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3744
3745 // keep the list to a reasonable size by removing head when needed
3746 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3747 d->m_viewportHistory.pop_front();
3748 }
3749
3750 // add the item at the end of the queue
3751 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(), viewport);
3752 }
3753
3754 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3755
3756 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3757
3758 // notify change to all other (different from id) observers
3759 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3760 if (o != excludeObserver) {
3761 o->notifyViewportChanged(smoothMove);
3762 }
3763
3764 if (currentPageChanged) {
3765 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3766 }
3767 }
3768}
3769
3770void Document::setViewportPage(int page, DocumentObserver *excludeObserver, bool smoothMove)
3771{
3772 // clamp page in range [0 ... numPages-1]
3773 if (page < 0) {
3774 page = 0;
3775 } else if (page > (int)d->m_pagesVector.count()) {
3776 page = d->m_pagesVector.count() - 1;
3777 }
3778
3779 // make a viewport from the page and broadcast it
3780 setViewport(DocumentViewport(page), excludeObserver, smoothMove);
3781}
3782
3783void Document::setZoom(int factor, DocumentObserver *excludeObserver)
3784{
3785 // notify change to all other (different from id) observers
3786 for (DocumentObserver *o : std::as_const(d->m_observers)) {
3787 if (o != excludeObserver) {
3788 o->notifyZoom(factor);
3789 }
3790 }
3791}
3792
3794// restore viewport from the history
3795{
3796 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3797 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3798
3799 // restore previous viewport and notify it to observers
3800 --d->m_viewportIterator;
3801 foreachObserver(notifyViewportChanged(true));
3802
3803 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3804 if (oldViewportPage != currentViewportPage)
3805 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3806 }
3807}
3808
3810// restore next viewport from the history
3811{
3812 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3813 ++nextIterator;
3814 if (nextIterator != d->m_viewportHistory.end()) {
3815 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3816
3817 // restore next viewport and notify it to observers
3818 ++d->m_viewportIterator;
3819 foreachObserver(notifyViewportChanged(true));
3820
3821 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3822 if (oldViewportPage != currentViewportPage)
3823 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3824 }
3825}
3826
3828{
3829 d->m_nextDocumentViewport = viewport;
3830}
3831
3833{
3834 d->m_nextDocumentDestination = namedDestination;
3835}
3836
3837void Document::searchText(int searchID, const QString &text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor &color)
3838{
3839 d->m_searchCancelled = false;
3840
3841 // safety checks: don't perform searches on empty or unsearchable docs
3842 if (!d->m_generator || !d->m_generator->hasFeature(Generator::TextExtraction) || d->m_pagesVector.isEmpty()) {
3844 return;
3845 }
3846
3847 // if searchID search not recorded, create new descriptor and init params
3848 QMap<int, RunningSearch *>::iterator searchIt = d->m_searches.find(searchID);
3849 if (searchIt == d->m_searches.end()) {
3850 RunningSearch *search = new RunningSearch();
3851 search->continueOnPage = -1;
3852 searchIt = d->m_searches.insert(searchID, search);
3853 }
3854 RunningSearch *s = *searchIt;
3855
3856 // update search structure
3857 bool newText = text != s->cachedString;
3858 s->cachedString = text;
3859 s->cachedType = type;
3860 s->cachedCaseSensitivity = caseSensitivity;
3861 s->cachedViewportMove = moveViewport;
3862 s->cachedColor = color;
3863 s->isCurrentlySearching = true;
3864
3865 // global data for search
3866 QSet<int> *pagesToNotify = new QSet<int>;
3867
3868 // remove highlights from pages and queue them for notifying changes
3869 *pagesToNotify += s->highlightedPages;
3870 for (const int pageNumber : std::as_const(s->highlightedPages)) {
3871 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3872 }
3873 s->highlightedPages.clear();
3874
3875 // set hourglass cursor
3877
3878 // 1. ALLDOC - process all document marking pages
3879 if (type == AllDocument) {
3881
3882 // search and highlight 'text' (as a solid phrase) on all pages
3883 QTimer::singleShot(0, this, [this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3884 }
3885 // 2. NEXTMATCH - find next matching item (or start from top)
3886 // 3. PREVMATCH - find previous matching item (or start from bottom)
3887 else if (type == NextMatch || type == PreviousMatch) {
3888 // find out from where to start/resume search from
3889 const bool forward = type == NextMatch;
3890 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3891 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3892 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3893 const Page *lastPage = fromStart ? nullptr : d->m_pagesVector[currentPage];
3894 int pagesDone = 0;
3895
3896 // continue checking last TextPage first (if it is the current page)
3897 RegularAreaRect *match = nullptr;
3898 if (lastPage && lastPage->number() == s->continueOnPage) {
3899 if (newText) {
3900 match = lastPage->findText(searchID, text, forward ? FromTop : FromBottom, caseSensitivity);
3901 } else {
3902 match = lastPage->findText(searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch);
3903 }
3904 if (!match) {
3905 if (forward) {
3906 currentPage++;
3907 } else {
3908 currentPage--;
3909 }
3910 pagesDone++;
3911 }
3912 }
3913
3914 s->pagesDone = pagesDone;
3915
3916 DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct();
3917 searchStruct->pagesToNotify = pagesToNotify;
3918 searchStruct->match = match;
3919 searchStruct->currentPage = currentPage;
3920 searchStruct->searchID = searchID;
3921
3922 QTimer::singleShot(0, this, [this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3923 }
3924 // 4. GOOGLE* - process all document marking pages
3925 else if (type == GoogleAll || type == GoogleAny) {
3927 const QStringList words = text.split(QLatin1Char(' '), Qt::SkipEmptyParts);
3928
3929 // search and highlight every word in 'text' on all pages
3930 QTimer::singleShot(0, this, [this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3931 }
3932}
3933
3935{
3936 // check if searchID is present in runningSearches
3937 QMap<int, RunningSearch *>::const_iterator it = d->m_searches.constFind(searchID);
3938 if (it == d->m_searches.constEnd()) {
3940 return;
3941 }
3942
3943 // start search with cached parameters from last search by searchID
3944 RunningSearch *p = *it;
3945 if (!p->isCurrentlySearching) {
3946 searchText(searchID, p->cachedString, false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3947 }
3948}
3949
3950void Document::continueSearch(int searchID, SearchType type)
3951{
3952 // check if searchID is present in runningSearches
3953 QMap<int, RunningSearch *>::const_iterator it = d->m_searches.constFind(searchID);
3954 if (it == d->m_searches.constEnd()) {
3956 return;
3957 }
3958
3959 // start search with cached parameters from last search by searchID
3960 RunningSearch *p = *it;
3961 if (!p->isCurrentlySearching) {
3962 searchText(searchID, p->cachedString, false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3963 }
3964}
3965
3966void Document::resetSearch(int searchID)
3967{
3968 // if we are closing down, don't bother doing anything
3969 if (!d->m_generator) {
3970 return;
3971 }
3972
3973 // check if searchID is present in runningSearches
3974 QMap<int, RunningSearch *>::iterator searchIt = d->m_searches.find(searchID);
3975 if (searchIt == d->m_searches.end()) {
3976 return;
3977 }
3978
3979 // get previous parameters for search
3980 RunningSearch *s = *searchIt;
3981
3982 // unhighlight pages and inform observers about that
3983 for (const int pageNumber : std::as_const(s->highlightedPages)) {
3984 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3985 foreachObserver(notifyPageChanged(pageNumber, DocumentObserver::Highlights));
3986 }
3987
3988 // send the setup signal too (to update views that filter on matches)
3989 foreachObserver(notifySetup(d->m_pagesVector, 0));
3990
3991 // remove search from the runningSearches list and delete it
3992 d->m_searches.erase(searchIt);
3993 delete s;
3994}
3995
3997{
3998 d->m_searchCancelled = true;
3999}
4000
4002{
4003 d->m_undoStack->undo();
4004}
4005
4007{
4008 d->m_undoStack->redo();
4009}
4010
4011void Document::editFormText(int pageNumber, Okular::FormFieldText *form, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos)
4012{
4013 QUndoCommand *uc = new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos);
4014 d->m_undoStack->push(uc);
4015}
4016
4017void Document::editFormText(int pageNumber, Okular::FormFieldText *form, const QString &newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos, const QString &oldContents)
4018{
4019 QUndoCommand *uc = new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, oldContents, prevCursorPos, prevAnchorPos);
4020 d->m_undoStack->push(uc);
4021}
4022
4023void Document::editFormList(int pageNumber, FormFieldChoice *form, const QList<int> &newChoices)
4024{
4025 const QList<int> prevChoices = form->currentChoices();
4026 QUndoCommand *uc = new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
4027 d->m_undoStack->push(uc);
4028}
4029
4030void Document::editFormCombo(int pageNumber, FormFieldChoice *form, const QString &newText, int newCursorPos, int prevCursorPos, int prevAnchorPos)
4031{
4032 QString prevText;
4033 if (form->currentChoices().isEmpty()) {
4034 prevText = form->editChoice();
4035 } else {
4036 prevText = form->choices().at(form->currentChoices().constFirst());
4037 }
4038
4039 QUndoCommand *uc = new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4040 d->m_undoStack->push(uc);
4041}
4042
4043void Document::editFormButtons(int pageNumber, const QList<FormFieldButton *> &formButtons, const QList<bool> &newButtonStates)
4044{
4045 QUndoCommand *uc = new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4046 d->m_undoStack->push(uc);
4047}
4048
4050{
4051 const int numOfPages = pages();
4052 for (int i = currentPage(); i >= 0; i--) {
4053 d->refreshPixmaps(i);
4054 }
4055 for (int i = currentPage() + 1; i < numOfPages; i++) {
4056 d->refreshPixmaps(i);
4057 }
4058}
4059
4061{
4062 return d->m_bookmarkManager;
4063}
4064
4066{
4067 QList<int> list;
4068 uint docPages = pages();
4069
4070 // pages are 0-indexed internally, but 1-indexed externally
4071 for (uint i = 0; i < docPages; i++) {
4072 if (bookmarkManager()->isBookmarked(i)) {
4073 list << i + 1;
4074 }
4075 }
4076 return list;
4077}
4078
4080{
4081 // Code formerly in Part::slotPrint()
4082 // range detecting
4083 QString range;
4084 uint docPages = pages();
4085 int startId = -1;
4086 int endId = -1;
4087
4088 for (uint i = 0; i < docPages; ++i) {
4089 if (bookmarkManager()->isBookmarked(i)) {
4090 if (startId < 0) {
4091 startId = i;
4092 }
4093 if (endId < 0) {
4094 endId = startId;
4095 } else {
4096 ++endId;
4097 }
4098 } else if (startId >= 0 && endId >= 0) {
4099 if (!range.isEmpty()) {
4100 range += QLatin1Char(',');
4101 }
4102
4103 if (endId - startId > 0) {
4104 range += QStringLiteral("%1-%2").arg(startId + 1).arg(endId + 1);
4105 } else {
4106 range += QString::number(startId + 1);
4107 }
4108 startId = -1;
4109 endId = -1;
4110 }
4111 }
4112 if (startId >= 0 && endId >= 0) {
4113 if (!range.isEmpty()) {
4114 range += QLatin1Char(',');
4115 }
4116
4117 if (endId - startId > 0) {
4118 range += QStringLiteral("%1-%2").arg(startId + 1).arg(endId + 1);
4119 } else {
4120 range += QString::number(startId + 1);
4121 }
4122 }
4123 return range;
4124}
4125
4126struct ExecuteNextActionsHelper : public QObject, private DocumentObserver {
4127 Q_OBJECT
4128public:
4129 explicit ExecuteNextActionsHelper(Document *doc)
4130 : m_doc(doc)
4131 {
4132 doc->addObserver(this);
4133 connect(doc, &Document::aboutToClose, this, [this] { b = false; });
4134 }
4135
4136 ~ExecuteNextActionsHelper() override
4137 {
4138 m_doc->removeObserver(this);
4139 }
4140
4141 void notifySetup(const QVector<Okular::Page *> & /*pages*/, int setupFlags) override
4142 {
4143 if (setupFlags == DocumentChanged || setupFlags == UrlChanged) {
4144 b = false;
4145 }
4146 }
4147
4148 bool shouldExecuteNextAction() const
4149 {
4150 return b;
4151 }
4152
4153private:
4154 Document *const m_doc;
4155 bool b = true;
4156};
4157
4159{
4160 if (!action) {
4161 return;
4162 }
4163
4164 // Don't execute next actions if the action itself caused the closing of the document
4165 const ExecuteNextActionsHelper executeNextActionsHelper(this);
4166
4167 switch (action->actionType()) {
4168 case Action::Goto: {
4169 const GotoAction *go = static_cast<const GotoAction *>(action);
4170 d->m_nextDocumentViewport = go->destViewport();
4171 d->m_nextDocumentDestination = go->destinationName();
4172
4173 // Explanation of why d->m_nextDocumentViewport is needed:
4174 // all openRelativeFile does is launch a signal telling we
4175 // want to open another URL, the problem is that when the file is
4176 // non local, the loading is done asynchronously so you can't
4177 // do a setViewport after the if as it was because you are doing the setViewport
4178 // on the old file and when the new arrives there is no setViewport for it and
4179 // it does not show anything
4180
4181 // first open filename if link is pointing outside this document
4182 const QString filename = go->fileName();
4183 if (go->isExternal() && !d->openRelativeFile(filename)) {
4184 qCWarning(OkularCoreDebug).nospace() << "Action: Error opening '" << filename << "'.";
4185 break;
4186 } else {
4187 const DocumentViewport nextViewport = d->nextDocumentViewport();
4188 // skip local links that point to nowhere (broken ones)
4189 if (!nextViewport.isValid()) {
4190 break;
4191 }
4192
4193 setViewport(nextViewport, nullptr, true);
4194 d->m_nextDocumentViewport = DocumentViewport();
4195 d->m_nextDocumentDestination = QString();
4196 }
4197
4198 } break;
4199
4200 case Action::Execute: {
4201 const ExecuteAction *exe = static_cast<const ExecuteAction *>(action);
4202 const QString fileName = exe->fileName();
4203 if (fileName.endsWith(QLatin1String(".pdf"), Qt::CaseInsensitive)) {
4204 d->openRelativeFile(fileName);
4205 break;
4206 }
4207
4208 // Albert: the only pdf i have that has that kind of link don't define
4209 // an application and use the fileName as the file to open
4210 QUrl url = d->giveAbsoluteUrl(fileName);
4211 QMimeDatabase db;
4212 QMimeType mime = db.mimeTypeForUrl(url);
4213 // Check executables
4214 if (KIO::OpenUrlJob::isExecutableFile(url, mime.name())) {
4215 // Don't have any pdf that uses this code path, just a guess on how it should work
4216 if (!exe->parameters().isEmpty()) {
4217 url = d->giveAbsoluteUrl(exe->parameters());
4218 mime = db.mimeTypeForUrl(url);
4219
4220 if (KIO::OpenUrlJob::isExecutableFile(url, mime.name())) {
4221 // this case is a link pointing to an executable with a parameter
4222 // that also is an executable, possibly a hand-crafted pdf
4223 Q_EMIT error(i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4224 break;
4225 }
4226 } else {
4227 // this case is a link pointing to an executable with no parameters
4228 // core developers find unacceptable executing it even after asking the user
4229 Q_EMIT error(i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4230 break;
4231 }
4232 }
4233
4234 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, mime.name());
4236 job->start();
4237 connect(job, &KIO::OpenUrlJob::result, this, [this, mime](KJob *job) {
4238 if (job->error()) {
4239 Q_EMIT error(i18n("No application found for opening file of mimetype %1.", mime.name()), -1);
4240 }
4241 });
4242 } break;
4243
4244 case Action::DocAction: {
4245 const DocumentAction *docaction = static_cast<const DocumentAction *>(action);
4246 switch (docaction->documentActionType()) {
4248 setViewportPage(0);
4249 break;
4251 if ((*d->m_viewportIterator).pageNumber > 0) {
4252 setViewportPage((*d->m_viewportIterator).pageNumber - 1);
4253 }
4254 break;
4256 if ((*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1) {
4257 setViewportPage((*d->m_viewportIterator).pageNumber + 1);
4258 }
4259 break;
4261 setViewportPage(d->m_pagesVector.count() - 1);
4262 break;
4265 break;
4268 break;
4270 Q_EMIT quit();
4271 break;
4274 break;
4277 break;
4279 Q_EMIT linkFind();
4280 break;
4283 break;
4285 Q_EMIT close();
4286 break;
4289 break;
4292 break;
4293 }
4294 } break;
4295
4296 case Action::Browse: {
4297 const BrowseAction *browse = static_cast<const BrowseAction *>(action);
4298 QString lilySource;
4299 int lilyRow = 0, lilyCol = 0;
4300 // if the url is a mailto one, invoke mailer
4301 if (browse->url().scheme() == QLatin1String("mailto")) {
4302 QDesktopServices::openUrl(browse->url());
4303 } else if (extractLilyPondSourceReference(browse->url(), &lilySource, &lilyRow, &lilyCol)) {
4304 const SourceReference ref(lilySource, lilyRow, lilyCol);
4306 } else {
4307 const QUrl url = browse->url();
4308
4309 // fix for #100366, documents with relative links that are the form of http:foo.pdf
4310 if ((url.scheme() == QLatin1String("http")) && url.host().isEmpty() && url.fileName().endsWith(QLatin1String("pdf"))) {
4311 d->openRelativeFile(url.fileName());
4312 break;
4313 }
4314
4315 // handle documents with relative path
4316 QUrl realUrl;
4317 if (d->m_url.isValid()) {
4318 realUrl = KIO::upUrl(d->m_url).resolved(url);
4319 } else if (!url.isRelative()) {
4320 realUrl = url;
4321 }
4322 if (realUrl.isValid()) {
4323 auto *job = new KIO::OpenUrlJob(realUrl);
4324 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->m_widget.data()));
4325 job->start();
4326 }
4327 }
4328 } break;
4329
4330 case Action::Sound: {
4331 const SoundAction *linksound = static_cast<const SoundAction *>(action);
4332 AudioPlayer::instance()->playSound(linksound->sound(), linksound);
4333 } break;
4334
4335 case Action::Script: {
4336 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4337 if (!d->m_scripter) {
4338 d->m_scripter = new Scripter(d);
4339 }
4340 d->m_scripter->execute(linkscript->scriptType(), linkscript->script());
4341 } break;
4342
4343 case Action::Movie:
4344 Q_EMIT processMovieAction(static_cast<const MovieAction *>(action));
4345 break;
4346 case Action::Rendition: {
4347 const RenditionAction *linkrendition = static_cast<const RenditionAction *>(action);
4348 if (!linkrendition->script().isEmpty()) {
4349 if (!d->m_scripter) {
4350 d->m_scripter = new Scripter(d);
4351 }
4352 d->m_scripter->execute(linkrendition->scriptType(), linkrendition->script());
4353 }
4354
4355 Q_EMIT processRenditionAction(static_cast<const RenditionAction *>(action));
4356 } break;
4357 case Action::BackendOpaque: {
4358 const BackendOpaqueAction *backendOpaqueAction = static_cast<const BackendOpaqueAction *>(action);
4359 Okular::BackendOpaqueAction::OpaqueActionResult res = d->m_generator->opaqueAction(backendOpaqueAction);
4360 if (res & Okular::BackendOpaqueAction::RefreshForms) {
4361 for (const Page *p : std::as_const(d->m_pagesVector)) {
4362 const QList<Okular::FormField *> forms = p->formFields();
4363 for (FormField *form : forms) {
4365 }
4366 d->refreshPixmaps(p->number());
4367 }
4368 }
4369 } break;
4370 }
4371
4372 if (executeNextActionsHelper.shouldExecuteNextAction()) {
4373 const QVector<Action *> nextActions = action->nextActions();
4374 for (const Action *a : nextActions) {
4375 processAction(a);
4376 }
4377 }
4378}
4379
4381{
4382 processFormatAction(action, static_cast<FormField *>(fft));
4383}
4384
4386{
4387 if (action->actionType() != Action::Script) {
4388 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for formatting.";
4389 return;
4390 }
4391
4392 // Lookup the page of the FormFieldText
4393 int foundPage = d->findFieldPageNumber(ff);
4394
4395 if (foundPage == -1) {
4396 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4397 return;
4398 }
4399
4400 const QString unformattedText = ff->value().toString();
4401
4402 std::shared_ptr<Event> event = Event::createFormatEvent(ff, d->m_pagesVector[foundPage]);
4403
4404 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4405
4406 d->executeScriptEvent(event, linkscript);
4407
4408 const QString formattedText = event->value().toString();
4409 ff->commitFormattedValue(formattedText);
4410 if (formattedText != unformattedText) {
4411 // We set the formattedText, because when we call refreshFormWidget
4412 // It will set the QLineEdit to this formattedText
4413 ff->setValue(QVariant(formattedText));
4414 ff->setAppearanceValue(QVariant(formattedText));
4416 d->refreshPixmaps(foundPage);
4417 // Then we make the form have the unformatted text, to use
4418 // in calculations and other things
4419 ff->setValue(QVariant(unformattedText));
4421 // When the field was calculated we need to refresh even
4422 // if the format script changed nothing. e.g. on error.
4423 // This is because the recalculateForms function delegated
4424 // the responsiblity for the refresh to us.
4426 d->refreshPixmaps(foundPage);
4427 }
4428}
4429
4430QString DocumentPrivate::evaluateKeystrokeEventChange(const QString &oldVal, const QString &newVal, int selStart, int selEnd)
4431{
4432 /*
4433 The change needs to be evaluated here in accordance with code points.
4434 selStart and selEnd parameters passed to this method should be been adjusted accordingly.
4435
4436 Since QString methods work in terms of code units, we convert the strings to UTF-32.
4437 */
4438 std::u32string oldUcs4 = oldVal.toStdU32String();
4439 std::u32string newUcs4 = newVal.toStdU32String();
4440 if (selStart < 0 || selEnd < 0 || (selEnd - selStart) + (static_cast<int>(newUcs4.size()) - static_cast<int>(oldUcs4.size())) < 0) {
4441 // Prevent Okular from crashing if incorrect parameters are passed or some bug causes incorrect calculation
4442 return {};
4443 }
4444 const size_t changeLength = (selEnd - selStart) + (newUcs4.size() - oldUcs4.size());
4445 auto subview = std::u32string_view {newUcs4}.substr(selStart, changeLength);
4446 if (subview.empty()) {
4447 // If subview is empty (in scenarios when selStart is at end and changeLength is non-zero) fromUcs4 returns \u0000.
4448 // This should not happen, but just a guard.
4449 return {};
4450 }
4451 Q_ASSERT(subview.length() == changeLength);
4452 return QString::fromUcs4(subview.data(), subview.length());
4453}
4454
4455void Document::processKeystrokeAction(const Action *action, Okular::FormField *ff, const QVariant &newValue, int prevCursorPos, int prevAnchorPos)
4456{
4457 if (action->actionType() != Action::Script) {
4458 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4459 return;
4460 }
4461 // Lookup the page of the FormFieldText
4462 int foundPage = d->findFieldPageNumber(ff);
4463
4464 if (foundPage == -1) {
4465 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4466 return;
4467 }
4468
4469 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4470
4471 /* Set the selStart and selEnd event properties
4472
4473 QString using UTF-16 counts a code point as made up of 1 or 2 16-bit code units.
4474
4475 When encoded using 2 code units, the units are referred to as surrogate pairs.
4476 selectionStart() and selectionEnd() methods evaluate prevCursorPos and prevAnchorPos based on code units during selection.
4477
4478 While this unit-based evaulation is suitable for detecting changes, for providing consistency with Adobe Reader for values of selStart and selEnd,
4479 it would be best to evaluate in terms of code points rather than the code units.
4480
4481 To correct the values of selStart and selEnd accordingly, we iterate over the code units. If a surrogate pair is encountered, then selStart and
4482 selEnd are accordingly decremented.
4483 */
4484 int selStart = std::min(prevCursorPos, prevAnchorPos);
4485 int selEnd = std::max(prevCursorPos, prevAnchorPos);
4486 int codeUnit;
4487 int initialSelStart = selStart;
4488 int initialSelEnd = selEnd;
4489 QString inputString = ff->value().toString();
4490 for (codeUnit = 0; codeUnit < initialSelStart && codeUnit < inputString.size(); codeUnit++) {
4491 if (inputString.at(codeUnit).isHighSurrogate()) {
4492 // skip the low surrogate and decrement selStart and selEnd
4493 codeUnit++;
4494 selStart--;
4495 selEnd--;
4496 }
4497 }
4498 for (; codeUnit < initialSelEnd && codeUnit < inputString.size(); codeUnit++) {
4499 if (inputString.at(codeUnit).isHighSurrogate()) {
4500 // skip the low surrogate and decrement selEnd
4501 codeUnit++;
4502 selEnd--;
4503 }
4504 }
4505 std::u32string oldUcs4 = inputString.toStdU32String();
4506 std::u32string newUcs4 = newValue.toString().toStdU32String();
4507 // It is necessary to count size in terms of code points rather than code units for deletion.
4508 if (oldUcs4.size() - newUcs4.size() == 1 && selStart == selEnd) {
4509 // consider a one character removal as selection of that character and then its removal.
4510 selStart--;
4511 }
4512 event->setSelStart(selStart);
4513 event->setSelEnd(selEnd);
4514 // Use the corrected selStart and selEnd for evaluating the change.
4515 event->setChange(DocumentPrivate::evaluateKeystrokeEventChange(inputString, newValue.toString(), selStart, selEnd));
4516 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4517
4518 d->executeScriptEvent(event, linkscript);
4519
4520 if (event->returnCode()) {
4521 ff->setValue(newValue);
4522 } else {
4524 }
4525}
4526
4528{
4529 // use -1 as default
4530 processKeystrokeAction(action, fft, newValue, -1, -1);
4531}
4532
4534{
4535 bool returnCode = false;
4536 processKeystrokeCommitAction(action, fft, returnCode);
4537}
4538
4539void Document::processKeystrokeCommitAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4540{
4541 if (action->actionType() != Action::Script) {
4542 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4543 return;
4544 }
4545 // Lookup the page of the FormFieldText
4546 int foundPage = d->findFieldPageNumber(ff);
4547
4548 if (foundPage == -1) {
4549 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4550 return;
4551 }
4552
4553 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4554 event->setWillCommit(true);
4555
4556 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4557
4558 d->executeScriptEvent(event, linkscript);
4559
4560 if (!event->returnCode()) {
4563 ff->setValue(QVariant(ff->committedValue()));
4564 } else {
4565 ff->setValue(QVariant(event->value().toString()));
4567 }
4568 returnCode = event->returnCode();
4569}
4570
4572{
4573 if (!action || action->actionType() != Action::Script) {
4574 return;
4575 }
4576
4577 // Lookup the page of the FormFieldText
4578 int foundPage = d->findFieldPageNumber(field);
4579
4580 if (foundPage == -1) {
4581 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4582 return;
4583 }
4584
4585 std::shared_ptr<Event> event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4586
4587 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4588
4589 d->executeScriptEvent(event, linkscript);
4590}
4591
4592void Document::processValidateAction(const Action *action, Okular::FormFieldText *fft, bool &returnCode)
4593{
4594 processValidateAction(action, static_cast<FormField *>(fft), returnCode);
4595}
4596
4597void Document::processValidateAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4598{
4599 if (!action || action->actionType() != Action::Script) {
4600 return;
4601 }
4602
4603 // Lookup the page of the FormFieldText
4604 int foundPage = d->findFieldPageNumber(ff);
4605
4606 if (foundPage == -1) {
4607 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4608 return;
4609 }
4610
4611 std::shared_ptr<Event> event = Event::createFormValidateEvent(ff, d->m_pagesVector[foundPage]);
4612
4613 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4614
4615 d->executeScriptEvent(event, linkscript);
4616 if (!event->returnCode()) {
4619 ff->setValue(QVariant(ff->committedValue()));
4620 } else {
4621 ff->setValue(QVariant(event->value().toString()));
4623 }
4624 returnCode = event->returnCode();
4625}
4626
4628{
4629 if (ff->value().toString() == ff->committedValue()) {
4632 ff->setValue(QVariant(ff->committedValue()));
4633 return;
4634 }
4635
4636 bool returnCode = true;
4639 }
4640
4642 if (returnCode) {
4643 processValidateAction(action, ff, returnCode);
4644 }
4645 }
4646
4647 if (!returnCode) {
4648 return;
4649 } else {
4650 ff->commitValue(ff->value().toString());
4651 }
4652
4654
4656 processFormatAction(action, ff);
4657 } else {
4658 ff->commitFormattedValue(ff->value().toString());
4659 }
4660}
4661
4663{
4664 if (!action || action->actionType() != Action::Script) {
4665 return;
4666 }
4667
4668 Event::EventType eventType = Okular::Event::UnknownEvent;
4669
4670 switch (type) {
4671 case Document::CloseDocument:
4672 eventType = Okular::Event::DocWillClose;
4673 break;
4674 case Document::SaveDocumentStart:
4675 eventType = Okular::Event::DocWillSave;
4676 break;
4677 case Document::SaveDocumentFinish:
4678 eventType = Okular::Event::DocDidSave;
4679 break;
4680 case Document::PrintDocumentStart:
4681 eventType = Okular::Event::DocWillPrint;
4682 break;
4683 case Document::PrintDocumentFinish:
4684 eventType = Okular::Event::DocDidPrint;
4685 break;
4686 }
4687
4688 std::shared_ptr<Event> event = Event::createDocEvent(eventType);
4689
4690 const ScriptAction *linkScript = static_cast<const ScriptAction *>(action);
4691
4692 d->executeScriptEvent(event, linkScript);
4693}
4694
4696{
4697 if (!action || action->actionType() != Action::Script) {
4698 return;
4699 }
4700
4701 Okular::Event::EventType eventType = Okular::Event::UnknownEvent;
4702
4703 switch (fieldMouseEventType) {
4704 case Document::FieldMouseDown:
4705 eventType = Okular::Event::FieldMouseDown;
4706 break;
4708 eventType = Okular::Event::FieldMouseEnter;
4709 break;
4711 eventType = Okular::Event::FieldMouseExit;
4712 break;
4714 eventType = Okular::Event::FieldMouseUp;
4715 break;
4716 }
4717
4718 // Lookup the page of the FormFieldText
4719 int foundPage = d->findFieldPageNumber(ff);
4720
4721 if (foundPage == -1) {
4722 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4723 return;
4724 }
4725
4726 std::shared_ptr<Event> event = Event::createFieldMouseEvent(ff, d->m_pagesVector[foundPage], eventType);
4727
4728 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4729
4730 d->executeScriptEvent(event, linkscript);
4731}
4732
4737
4739{
4740 if (!ref) {
4741 return;
4742 }
4743
4744 const QUrl url = d->giveAbsoluteUrl(ref->fileName());
4745 if (!url.isLocalFile()) {
4746 qCDebug(OkularCoreDebug) << url.url() << "is not a local file.";
4747 return;
4748 }
4749
4750 const QString absFileName = url.toLocalFile();
4751 if (!QFile::exists(absFileName)) {
4752 qCDebug(OkularCoreDebug) << "No such file:" << absFileName;
4753 return;
4754 }
4755
4756 bool handled = false;
4757 Q_EMIT sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled);
4758 if (handled) {
4759 return;
4760 }
4761
4762 static QHash<int, QString> editors;
4763 // init the editors table if empty (on first run, usually)
4764 if (editors.isEmpty()) {
4765 editors = buildEditorsMap();
4766 }
4767
4768 // prefer the editor from the command line
4769 QString p = d->editorCommandOverride;
4770 if (p.isEmpty()) {
4771 QHash<int, QString>::const_iterator it = editors.constFind(SettingsCore::externalEditor());
4772 if (it != editors.constEnd()) {
4773 p = *it;
4774 } else {
4775 p = SettingsCore::externalEditorCommand();
4776 }
4777 }
4778 // custom editor not yet configured
4779 if (p.isEmpty()) {
4780 return;
4781 }
4782
4783 // manually append the %f placeholder if not specified
4784 if (p.indexOf(QLatin1String("%f")) == -1) {
4785 p.append(QLatin1String(" %f"));
4786 }
4787
4788 // replacing the placeholders
4790 map.insert(QLatin1Char('f'), absFileName);
4791 map.insert(QLatin1Char('c'), QString::number(ref->column()));
4792 map.insert(QLatin1Char('l'), QString::number(ref->row()));
4794 if (cmd.isEmpty()) {
4795 return;
4796 }
4797 QStringList args = KShell::splitArgs(cmd);
4798 if (args.isEmpty()) {
4799 return;
4800 }
4801
4802 const QString prog = args.takeFirst();
4803 // Make sure prog is in PATH and not just in the CWD
4804 const QString progFullPath = QStandardPaths::findExecutable(prog);
4805 if (progFullPath.isEmpty()) {
4806 return;
4807 }
4808
4809 KProcess::startDetached(progFullPath, args);
4810}
4811
4812const SourceReference *Document::dynamicSourceReference(int pageNr, double absX, double absY)
4813{
4814 if (!d->m_synctex_scanner) {
4815 return nullptr;
4816 }
4817
4818 const QSizeF dpi = d->m_generator->dpi();
4819
4820 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.width(), absY * 72. / dpi.height()) > 0) {
4821 synctex_node_p node;
4822 // TODO what should we do if there is really more than one node?
4823 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4824 int line = synctex_node_line(node);
4825 int col = synctex_node_column(node);
4826 // column extraction does not seem to be implemented in synctex so far. set the SourceReference default value.
4827 if (col == -1) {
4828 col = 0;
4829 }
4830 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4831
4832 return new Okular::SourceReference(QFile::decodeName(name), line, col);
4833 }
4834 }
4835 return nullptr;
4836}
4837
4839{
4840 if (d->m_generator) {
4841 if (d->m_generator->hasFeature(Generator::PrintNative)) {
4842 return NativePrinting;
4843 }
4844
4845#ifndef Q_OS_WIN
4846 if (d->m_generator->hasFeature(Generator::PrintPostscript)) {
4847 return PostscriptPrinting;
4848 }
4849#endif
4850 }
4851
4852 return NoPrinting;
4853}
4854
4856{
4857 return d->m_generator ? d->m_generator->hasFeature(Generator::PrintToFile) : false;
4858}
4859
4861{
4862 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentStart)) {
4863 processDocumentAction(action, PrintDocumentStart);
4864 }
4865 const Document::PrintError printError = d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4866 if (printError == Document::NoPrintError) {
4867 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentFinish)) {
4868 processDocumentAction(action, PrintDocumentFinish);
4869 }
4870 }
4871 return printError;
4872}
4873
4875{
4876 switch (error) {
4877 case TemporaryFileOpenPrintError:
4878 return i18n("Could not open a temporary file");
4879 case FileConversionPrintError:
4880 return i18n("Print conversion failed");
4881 case PrintingProcessCrashPrintError:
4882 return i18n("Printing process crashed");
4883 case PrintingProcessStartPrintError:
4884 return i18n("Printing process could not start");
4885 case PrintToFilePrintError:
4886 return i18n("Printing to file failed");
4887 case InvalidPrinterStatePrintError:
4888 return i18n("Printer was in invalid state");
4889 case UnableToFindFilePrintError:
4890 return i18n("Unable to find file to print");
4891 case NoFileToPrintError:
4892 return i18n("There was no file to print");
4893 case NoBinaryToPrintError:
4894 return i18n("Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4895 case InvalidPageSizePrintError:
4896 return i18n("The page print size is invalid");
4897 case NoPrintError:
4898 return QString();
4899 case UnknownPrintError:
4900 return QString();
4901 }
4902
4903 return QString();
4904}
4905
4907{
4908 if (d->m_generator) {
4910 return iface ? iface->printConfigurationWidget() : nullptr;
4911 } else {
4912 return nullptr;
4913 }
4914}
4915
4917{
4918 if (!dialog) {
4919 return;
4920 }
4921
4922 // We know it's a BackendConfigDialog, but check anyway
4923 BackendConfigDialog *bcd = dynamic_cast<BackendConfigDialog *>(dialog);
4924 if (!bcd) {
4925 return;
4926 }
4927
4928 // ensure that we have all the generators with settings loaded
4929 QVector<KPluginMetaData> offers = DocumentPrivate::configurableGenerators();
4930 d->loadServiceList(offers);
4931
4932 // We want the generators to be sorted by name so let's fill in a QMap
4933 // this sorts by internal id which is not awesome, but at least the sorting
4934 // is stable between runs that before it wasn't
4935 QMap<QString, GeneratorInfo> sortedGenerators;
4936 QHash<QString, GeneratorInfo>::iterator it = d->m_loadedGenerators.begin();
4937 QHash<QString, GeneratorInfo>::iterator itEnd = d->m_loadedGenerators.end();
4938 for (; it != itEnd; ++it) {
4939 sortedGenerators.insert(it.key(), it.value());
4940 }
4941
4942 bool pagesAdded = false;
4943 QMap<QString, GeneratorInfo>::iterator sit = sortedGenerators.begin();
4944 QMap<QString, GeneratorInfo>::iterator sitEnd = sortedGenerators.end();
4945 for (; sit != sitEnd; ++sit) {
4946 Okular::ConfigInterface *iface = d->generatorConfig(sit.value());
4947 if (iface) {
4948 iface->addPages(dialog);
4949 pagesAdded = true;
4950
4951 if (sit.value().generator == d->m_generator) {
4952 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4953 KPageView *view = bcd->thePageWidget();
4954 view->setCurrentPage(view->model()->index(rowCount - 1, 0));
4955 }
4956 }
4957 }
4958 if (pagesAdded) {
4959 connect(dialog, &KConfigDialog::settingsChanged, this, [this] { d->slotGeneratorConfigChanged(); });
4960 }
4961}
4962
4963QVector<KPluginMetaData> DocumentPrivate::configurableGenerators()
4964{
4965 const QVector<KPluginMetaData> available = availableGenerators();
4967 for (const KPluginMetaData &md : available) {
4968 if (md.rawData().value(QStringLiteral("X-KDE-okularHasInternalSettings")).toBool()) {
4969 result << md;
4970 }
4971 }
4972 return result;
4973}
4974
4976{
4977 if (!d->m_generator) {
4978 return KPluginMetaData();
4979 }
4980
4981 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4982 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4983 return genIt.value().metadata;
4984}
4985
4987{
4988 return DocumentPrivate::configurableGenerators().size();
4989}
4990
4992{
4993 // TODO: make it a static member of DocumentPrivate?
4994 QStringList result = d->m_supportedMimeTypes;
4995 if (result.isEmpty()) {
4996 const QVector<KPluginMetaData> available = DocumentPrivate::availableGenerators();
4997 for (const KPluginMetaData &md : available) {
4998 result << md.mimeTypes();
4999 }
5000
5001 // Remove duplicate mimetypes represented by different names
5002 QMimeDatabase mimeDatabase;
5003 QSet<QMimeType> uniqueMimetypes;
5004 for (const QString &mimeName : std::as_const(result)) {
5005 uniqueMimetypes.insert(mimeDatabase.mimeTypeForName(mimeName));
5006 }
5007 result.clear();
5008 for (const QMimeType &mimeType : uniqueMimetypes) {
5009 result.append(mimeType.name());
5010 }
5011
5012 // Add the Okular archive mimetype
5013 result << QStringLiteral("application/vnd.kde.okular-archive");
5014
5015 // Sorting by mimetype name doesn't make a ton of sense,
5016 // but ensures that the list is ordered the same way every time
5017 std::sort(result.begin(), result.end());
5018
5019 d->m_supportedMimeTypes = result;
5020 }
5021 return result;
5022}
5023
5025{
5026 if (!d->m_generator) {
5027 return false;
5028 }
5029
5030 return d->m_generator->hasFeature(Generator::SwapBackingFile);
5031}
5032
5033bool Document::swapBackingFile(const QString &newFileName, const QUrl &url)
5034{
5035 if (!d->m_generator) {
5036 return false;
5037 }
5038
5039 if (!d->m_generator->hasFeature(Generator::SwapBackingFile)) {
5040 return false;
5041 }
5042
5043 // Save metadata about the file we're about to close
5044 d->saveDocumentInfo();
5045
5046 d->clearAndWaitForRequests();
5047
5048 qCDebug(OkularCoreDebug) << "Swapping backing file to" << newFileName;
5049 QVector<Page *> newPagesVector;
5050 Generator::SwapBackingFileResult result = d->m_generator->swapBackingFile(newFileName, newPagesVector);
5051 if (result != Generator::SwapBackingFileError) {
5052 QList<ObjectRect *> rectsToDelete;
5053 QList<Annotation *> annotationsToDelete;
5054 QSet<PagePrivate *> pagePrivatesToDelete;
5055
5056 if (result == Generator::SwapBackingFileReloadInternalData) {
5057 // Here we need to replace everything that the old generator
5058 // had created with what the new one has without making it look like
5059 // we have actually closed and opened the file again
5060
5061 // Simple sanity check
5062 if (newPagesVector.count() != d->m_pagesVector.count()) {
5063 return false;
5064 }
5065
5066 // Update the undo stack contents
5067 for (int i = 0; i < d->m_undoStack->count(); ++i) {
5068 // Trust me on the const_cast ^_^
5069 QUndoCommand *uc = const_cast<QUndoCommand *>(d->m_undoStack->command(i));
5070 if (OkularUndoCommand *ouc = dynamic_cast<OkularUndoCommand *>(uc)) {
5071 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
5072 if (!success) {
5073 qWarning() << "Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
5074 return false;
5075 }
5076 } else {
5077 qWarning() << "Document::swapBackingFile: Unhandled undo command" << uc;
5078 return false;
5079 }
5080 }
5081
5082 for (int i = 0; i < d->m_pagesVector.count(); ++i) {
5083 // switch the PagePrivate* from newPage to oldPage
5084 // this way everyone still holding Page* doesn't get
5085 // disturbed by it
5086 Page *oldPage = d->m_pagesVector[i];
5087 Page *newPage = newPagesVector[i];
5088 newPage->d->adoptGeneratedContents(oldPage->d);
5089
5090 pagePrivatesToDelete << oldPage->d;
5091 oldPage->d = newPage->d;
5092 oldPage->d->m_page = oldPage;
5093 oldPage->d->m_doc = d;
5094 newPage->d = nullptr;
5095
5096 annotationsToDelete << oldPage->m_annotations;
5097 rectsToDelete << oldPage->m_rects;
5098 oldPage->m_annotations = newPage->m_annotations;
5099 oldPage->m_rects = newPage->m_rects;
5100 }
5101 qDeleteAll(newPagesVector);
5102 }
5103
5104 d->m_url = url;
5105 d->m_docFileName = newFileName;
5106 d->updateMetadataXmlNameAndDocSize();
5107 d->m_bookmarkManager->setUrl(d->m_url);
5108 d->m_documentInfo = DocumentInfo();
5109 d->m_documentInfoAskedKeys.clear();
5110
5111 if (d->m_synctex_scanner) {
5112 synctex_scanner_free(d->m_synctex_scanner);
5113 d->m_synctex_scanner = synctex_scanner_new_with_output_file(QFile::encodeName(newFileName).constData(), nullptr, 1);
5114 if (!d->m_synctex_scanner && QFile::exists(newFileName + QLatin1String("sync"))) {
5115 d->loadSyncFile(newFileName);
5116 }
5117 }
5118
5119 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::UrlChanged));
5120
5121 qDeleteAll(annotationsToDelete);
5122 qDeleteAll(rectsToDelete);
5123 qDeleteAll(pagePrivatesToDelete);
5124
5125 return true;
5126 } else {
5127 return false;
5128 }
5129}
5130
5131bool Document::swapBackingFileArchive(const QString &newFileName, const QUrl &url)
5132{
5133 qCDebug(OkularCoreDebug) << "Swapping backing archive to" << newFileName;
5134
5135 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
5136 if (!newArchive) {
5137 return false;
5138 }
5139
5140 const QString tempFileName = newArchive->document.fileName();
5141
5142 const bool success = swapBackingFile(tempFileName, url);
5143
5144 if (success) {
5145 delete d->m_archiveData;
5146 d->m_archiveData = newArchive;
5147 }
5148
5149 return success;
5150}
5151
5153{
5154 if (clean) {
5155 d->m_undoStack->setClean();
5156 } else {
5157 d->m_undoStack->resetClean();
5158 }
5159}
5160
5161bool Document::isHistoryClean() const
5162{
5163 return d->m_undoStack->isClean();
5164}
5165
5167{
5168 d->m_undoStack->clear();
5169}
5170
5172{
5173 if (!d->m_generator) {
5174 return false;
5175 }
5176 Q_ASSERT(!d->m_generatorName.isEmpty());
5177
5178 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5179 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5180 SaveInterface *saveIface = d->generatorSave(genIt.value());
5181 if (!saveIface) {
5182 return false;
5183 }
5184
5185 return saveIface->supportsOption(SaveInterface::SaveChanges);
5186}
5187
5189{
5190 switch (cap) {
5192 /* Assume that if the generator supports saving, forms can be saved.
5193 * We have no means to actually query the generator at the moment
5194 * TODO: Add some method to query the generator in SaveInterface */
5195 return canSaveChanges();
5196
5198 return d->canAddAnnotationsNatively();
5199 }
5200
5201 return false;
5202}
5203
5204bool Document::saveChanges(const QString &fileName)
5205{
5206 QString errorText;
5207 return saveChanges(fileName, &errorText);
5208}
5209
5210bool Document::saveChanges(const QString &fileName, QString *errorText)
5211{
5212 if (!d->m_generator || fileName.isEmpty()) {
5213 return false;
5214 }
5215 Q_ASSERT(!d->m_generatorName.isEmpty());
5216
5217 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5218 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5219 SaveInterface *saveIface = d->generatorSave(genIt.value());
5220 if (!saveIface || !saveIface->supportsOption(SaveInterface::SaveChanges)) {
5221 return false;
5222 }
5223
5224 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentStart)) {
5225 processDocumentAction(action, SaveDocumentStart);
5226 }
5227
5228 bool success = saveIface->save(fileName, SaveInterface::SaveChanges, errorText);
5229 if (success) {
5230 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentFinish)) {
5231 processDocumentAction(action, SaveDocumentFinish);
5232 }
5233 }
5234 return success;
5235}
5236
5238{
5239 if (!view) {
5240 return;
5241 }
5242
5243 Document *viewDoc = view->viewDocument();
5244 if (viewDoc) {
5245 // check if already registered for this document
5246 if (viewDoc == this) {
5247 return;
5248 }
5249
5250 viewDoc->unregisterView(view);
5251 }
5252
5253 d->m_views.insert(view);
5254 view->d_func()->document = d;
5255}
5256
5258{
5259 if (!view) {
5260 return;
5261 }
5262
5263 const Document *viewDoc = view->viewDocument();
5264 if (!viewDoc || viewDoc != this) {
5265 return;
5266 }
5267
5268 view->d_func()->document = nullptr;
5269 d->m_views.remove(view);
5270}
5271
5273{
5274 if (d->m_generator) {
5275 return d->m_generator->requestFontData(font);
5276 }
5277
5278 return {};
5279}
5280
5281ArchiveData *DocumentPrivate::unpackDocumentArchive(const QString &archivePath)
5282{
5283 QMimeDatabase db;
5284 const QMimeType mime = db.mimeTypeForFile(archivePath, QMimeDatabase::MatchExtension);
5285 if (!mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) {
5286 return nullptr;
5287 }
5288
5289 KZip okularArchive(archivePath);
5290 if (!okularArchive.open(QIODevice::ReadOnly)) {
5291 return nullptr;
5292 }
5293
5294 const KArchiveDirectory *mainDir = okularArchive.directory();
5295
5296 // Check the archive doesn't have folders, we don't create them when saving the archive
5297 // and folders mean paths and paths mean path traversal issues
5298 const QStringList mainDirEntries = mainDir->entries();
5299 for (const QString &entry : mainDirEntries) {
5300 if (mainDir->entry(entry)->isDirectory()) {
5301 qWarning() << "Warning: Found a directory inside" << archivePath << " - Okular does not create files like that so it is most probably forged.";
5302 return nullptr;
5303 }
5304 }
5305
5306 const KArchiveEntry *mainEntry = mainDir->entry(QStringLiteral("content.xml"));
5307 if (!mainEntry || !mainEntry->isFile()) {
5308 return nullptr;
5309 }
5310
5311 std::unique_ptr<QIODevice> mainEntryDevice(static_cast<const KZipFileEntry *>(mainEntry)->createDevice());
5312 QDomDocument doc;
5313 if (!doc.setContent(mainEntryDevice.get())) {
5314 return nullptr;
5315 }
5316 mainEntryDevice.reset();
5317
5318 QDomElement root = doc.documentElement();
5319 if (root.tagName() != QLatin1String("OkularArchive")) {
5320 return nullptr;
5321 }
5322
5323 QString documentFileName;
5324 QString metadataFileName;
5325 QDomElement el = root.firstChild().toElement();
5326 for (; !el.isNull(); el = el.nextSibling().toElement()) {
5327 if (el.tagName() == QLatin1String("Files")) {
5328 QDomElement fileEl = el.firstChild().toElement();
5329 for (; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement()) {
5330 if (fileEl.tagName() == QLatin1String("DocumentFileName")) {
5331 documentFileName = fileEl.text();
5332 } else if (fileEl.tagName() == QLatin1String("MetadataFileName")) {
5333 metadataFileName = fileEl.text();
5334 }
5335 }
5336 }
5337 }
5338 if (documentFileName.isEmpty()) {
5339 return nullptr;
5340 }
5341
5342 const KArchiveEntry *docEntry = mainDir->entry(documentFileName);
5343 if (!docEntry || !docEntry->isFile()) {
5344 return nullptr;
5345 }
5346
5347 std::unique_ptr<ArchiveData> archiveData(new ArchiveData());
5348 const int dotPos = documentFileName.indexOf(QLatin1Char('.'));
5349 if (dotPos != -1) {
5350 archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos));
5351 }
5352 if (!archiveData->document.open()) {
5353 return nullptr;
5354 }
5355
5356 archiveData->originalFileName = documentFileName;
5357
5358 {
5359 std::unique_ptr<QIODevice> docEntryDevice(static_cast<const KZipFileEntry *>(docEntry)->createDevice());
5360 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5361 archiveData->document.close();
5362 }
5363
5364 const KArchiveEntry *metadataEntry = mainDir->entry(metadataFileName);
5365 if (metadataEntry && metadataEntry->isFile()) {
5366 std::unique_ptr<QIODevice> metadataEntryDevice(static_cast<const KZipFileEntry *>(metadataEntry)->createDevice());
5367 archiveData->metadataFile.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.xml"));
5368 if (archiveData->metadataFile.open()) {
5369 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5370 archiveData->metadataFile.close();
5371 }
5372 }
5373
5374 return archiveData.release();
5375}
5376
5377Document::OpenResult Document::openDocumentArchive(const QString &docFile, const QUrl &url, const QString &password)
5378{
5379 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5380 if (!d->m_archiveData) {
5381 return OpenError;
5382 }
5383
5384 const QString tempFileName = d->m_archiveData->document.fileName();
5385 QMimeDatabase db;
5386 const QMimeType docMime = db.mimeTypeForFile(tempFileName, QMimeDatabase::MatchExtension);
5387 const OpenResult ret = openDocument(tempFileName, url, docMime, password);
5388
5389 if (ret != OpenSuccess) {
5390 delete d->m_archiveData;
5391 d->m_archiveData = nullptr;
5392 }
5393
5394 return ret;
5395}
5396
5398{
5399 if (!d->m_generator) {
5400 return false;
5401 }
5402
5403 /* If we opened an archive, use the name of original file (eg foo.pdf)
5404 * instead of the archive's one (eg foo.okular) */
5405 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5406 if (docFileName == QLatin1String("-")) {
5407 return false;
5408 }
5409
5410 QString docPath = d->m_docFileName;
5411 const QFileInfo fi(docPath);
5412 if (fi.isSymLink()) {
5413 docPath = fi.symLinkTarget();
5414 }
5415
5416 KZip okularArchive(fileName);
5417 if (!okularArchive.open(QIODevice::WriteOnly)) {
5418 return false;
5419 }
5420
5421 const KUser user;
5422#ifndef Q_OS_WIN
5423 const KUserGroup userGroup(user.groupId());
5424#else
5425 const KUserGroup userGroup(QStringLiteral(""));
5426#endif
5427
5428 QDomDocument contentDoc(QStringLiteral("OkularArchive"));
5429 QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
5430 contentDoc.appendChild(xmlPi);
5431 QDomElement root = contentDoc.createElement(QStringLiteral("OkularArchive"));
5432 contentDoc.appendChild(root);
5433
5434 QDomElement filesNode = contentDoc.createElement(QStringLiteral("Files"));
5435 root.appendChild(filesNode);
5436
5437 QDomElement fileNameNode = contentDoc.createElement(QStringLiteral("DocumentFileName"));
5438 filesNode.appendChild(fileNameNode);
5439 fileNameNode.appendChild(contentDoc.createTextNode(docFileName));
5440
5441 QDomElement metadataFileNameNode = contentDoc.createElement(QStringLiteral("MetadataFileName"));
5442 filesNode.appendChild(metadataFileNameNode);
5443 metadataFileNameNode.appendChild(contentDoc.createTextNode(QStringLiteral("metadata.xml")));
5444
5445 // If the generator can save annotations natively, do it
5446 QTemporaryFile modifiedFile;
5447 bool annotationsSavedNatively = false;
5448 bool formsSavedNatively = false;
5449 if (d->canAddAnnotationsNatively() || canSaveChanges(SaveFormsCapability)) {
5450 if (!modifiedFile.open()) {
5451 return false;
5452 }
5453
5454 const QString modifiedFileName = modifiedFile.fileName();
5455
5456 modifiedFile.close(); // We're only interested in the file name
5457
5458 QString errorText;
5459 if (saveChanges(modifiedFileName, &errorText)) {
5460 docPath = modifiedFileName; // Save this instead of the original file
5461 annotationsSavedNatively = d->canAddAnnotationsNatively();
5462 formsSavedNatively = canSaveChanges(SaveFormsCapability);
5463 } else {
5464 qCWarning(OkularCoreDebug) << "saveChanges failed: " << errorText;
5465 qCDebug(OkularCoreDebug) << "Falling back to saving a copy of the original file";
5466 }
5467 }
5468
5469 PageItems saveWhat = None;
5470 if (!annotationsSavedNatively) {
5471 saveWhat |= AnnotationPageItems;
5472 }
5473 if (!formsSavedNatively) {
5474 saveWhat |= FormFieldPageItems;
5475 }
5476
5477 QTemporaryFile metadataFile;
5478 if (!d->savePageDocumentInfo(&metadataFile, saveWhat)) {
5479 return false;
5480 }
5481
5482 const QByteArray contentDocXml = contentDoc.toByteArray();
5483 const mode_t perm = 0100644;
5484 okularArchive.writeFile(QStringLiteral("content.xml"), contentDocXml, perm, user.loginName(), userGroup.name());
5485
5486 okularArchive.addLocalFile(docPath, docFileName);
5487 okularArchive.addLocalFile(metadataFile.fileName(), QStringLiteral("metadata.xml"));
5488
5489 if (!okularArchive.close()) {
5490 return false;
5491 }
5492
5493 return true;
5494}
5495
5497{
5498 if (!d->m_archiveData) {
5499 return false;
5500 }
5501
5502 // Remove existing file, if present (QFile::copy doesn't overwrite by itself)
5503 QFile::remove(destFileName);
5504
5505 return d->m_archiveData->document.copy(destFileName);
5506}
5507
5509{
5510 int landscape, portrait;
5511
5512 // if some pages are landscape and others are not, the most common wins, as
5513 // QPrinter does not accept a per-page setting
5514 landscape = 0;
5515 portrait = 0;
5516 for (uint i = 0; i < pages(); i++) {
5517 const Okular::Page *currentPage = page(i);
5518 double width = currentPage->width();
5519 double height = currentPage->height();
5520 if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) {
5521 std::swap(width, height);
5522 }
5523 if (width > height) {
5524 landscape++;
5525 } else {
5526 portrait++;
5527 }
5528 }
5529 return (landscape > portrait) ? QPageLayout::Landscape : QPageLayout::Portrait;
5530}
5531
5533{
5534 d->m_annotationEditingEnabled = enable;
5535 foreachObserver(notifySetup(d->m_pagesVector, 0));
5536}
5537
5538void Document::walletDataForFile(const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey) const
5539{
5540 if (d->m_generator) {
5541 d->m_generator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5542 } else if (d->m_walletGenerator) {
5543 d->m_walletGenerator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5544 }
5545}
5546
5548{
5549 return d->m_docdataMigrationNeeded;
5550}
5551
5553{
5554 if (d->m_docdataMigrationNeeded) {
5555 d->m_docdataMigrationNeeded = false;
5556 foreachObserver(notifySetup(d->m_pagesVector, 0));
5557 }
5558}
5559
5561{
5562 return d->m_generator ? d->m_generator->layersModel() : nullptr;
5563}
5564
5566{
5567 return d->m_openError;
5568}
5569
5570QByteArray Document::requestSignedRevisionData(const Okular::SignatureInfo &info)
5571{
5572 QFile f(d->m_docFileName);
5573 if (!f.open(QIODevice::ReadOnly)) {
5574 Q_EMIT error(i18n("Could not open '%1'. File does not exist", d->m_docFileName), -1);
5575 return {};
5576 }
5577
5578 const QList<qint64> byteRange = info.signedRangeBounds();
5579 f.seek(byteRange.first());
5580 QByteArray data = f.read(byteRange.last() - byteRange.first());
5581 f.close();
5582
5583 return data;
5584}
5585
5586void Document::refreshPixmaps(int pageNumber)
5587{
5588 d->refreshPixmaps(pageNumber);
5589}
5590
5591void DocumentPrivate::executeScript(const QString &function)
5592{
5593 if (!m_scripter) {
5594 m_scripter = new Scripter(this);
5595 }
5596 m_scripter->execute(JavaScript, function);
5597}
5598
5599void DocumentPrivate::requestDone(PixmapRequest *req)
5600{
5601 if (!req) {
5602 return;
5603 }
5604
5605 if (!m_generator || m_closingLoop) {
5606 m_pixmapRequestsMutex.lock();
5607 m_executingPixmapRequests.remove(req);
5608 m_pixmapRequestsMutex.unlock();
5609 delete req;
5610 if (m_closingLoop) {
5611 m_closingLoop->exit();
5612 }
5613 return;
5614 }
5615
5616#ifndef NDEBUG
5617 if (!m_generator->canGeneratePixmap()) {
5618 qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state.";
5619 }
5620#endif
5621
5622 if (!req->shouldAbortRender()) {
5623 // [MEM] 1.1 find and remove a previous entry for the same page and id
5624 std::list<AllocatedPixmap *>::iterator aIt = m_allocatedPixmaps.begin();
5625 std::list<AllocatedPixmap *>::iterator aEnd = m_allocatedPixmaps.end();
5626 for (; aIt != aEnd; ++aIt) {
5627 if ((*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer()) {
5628 AllocatedPixmap *p = *aIt;
5629 m_allocatedPixmaps.erase(aIt);
5630 m_allocatedPixmapsTotalMemory -= p->memory;
5631 delete p;
5632 break;
5633 }
5634 }
5635
5636 DocumentObserver *observer = req->observer();
5637 if (m_observers.contains(observer)) {
5638 // [MEM] 1.2 append memory allocation descriptor to the FIFO
5639 qulonglong memoryBytes = 0;
5640 const TilesManager *tm = req->d->tilesManager();
5641 if (tm) {
5642 memoryBytes = tm->totalMemory();
5643 } else {
5644 memoryBytes = 4 * req->width() * req->height();
5645 }
5646
5647 AllocatedPixmap *memoryPage = new AllocatedPixmap(req->observer(), req->pageNumber(), memoryBytes);
5648 m_allocatedPixmaps.push_back(memoryPage);
5649 m_allocatedPixmapsTotalMemory += memoryBytes;
5650
5651 // 2. notify an observer that its pixmap changed
5653 }
5654#ifndef NDEBUG
5655 else {
5656 qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer;
5657 }
5658#endif
5659 }
5660
5661 // 3. delete request
5662 m_pixmapRequestsMutex.lock();
5663 m_executingPixmapRequests.remove(req);
5664 m_pixmapRequestsMutex.unlock();
5665 delete req;
5666
5667 // 4. start a new generation if some is pending
5668 m_pixmapRequestsMutex.lock();
5669 bool hasPixmaps = !m_pixmapRequestsStack.empty();
5670 m_pixmapRequestsMutex.unlock();
5671 if (hasPixmaps) {
5672 sendGeneratorPixmapRequest();
5673 }
5674}
5675
5676void DocumentPrivate::setPageBoundingBox(int page, const NormalizedRect &boundingBox)
5677{
5678 Page *kp = m_pagesVector[page];
5679 if (!m_generator || !kp) {
5680 return;
5681 }
5682
5683 if (kp->boundingBox() == boundingBox) {
5684 return;
5685 }
5686 kp->setBoundingBox(boundingBox);
5687
5688 // notify observers about the change
5689 foreachObserverD(notifyPageChanged(page, DocumentObserver::BoundingBox));
5690
5691 // 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.
5692 // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away.
5693 // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker.
5694 // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off).
5695}
5696
5697void DocumentPrivate::calculateMaxTextPages()
5698{
5699 int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB
5700 switch (SettingsCore::memoryLevel()) {
5701 case SettingsCore::EnumMemoryLevel::Low:
5702 m_maxAllocatedTextPages = multipliers * 2;
5703 break;
5704
5705 case SettingsCore::EnumMemoryLevel::Normal:
5706 m_maxAllocatedTextPages = multipliers * 50;
5707 break;
5708
5709 case SettingsCore::EnumMemoryLevel::Aggressive:
5710 m_maxAllocatedTextPages = multipliers * 250;
5711 break;
5712
5713 case SettingsCore::EnumMemoryLevel::Greedy:
5714 m_maxAllocatedTextPages = multipliers * 1250;
5715 break;
5716 }
5717}
5718
5719void DocumentPrivate::textGenerationDone(Page *page)
5720{
5721 if (!m_pageController) {
5722 return;
5723 }
5724
5725 // 1. If we reached the cache limit, delete the first text page from the fifo
5726 if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) {
5727 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
5728 if (pageToKick != page->number()) // this should never happen but better be safe than sorry
5729 {
5730 m_pagesVector.at(pageToKick)->setTextPage(nullptr); // deletes the textpage
5731 }
5732 }
5733
5734 // 2. Add the page to the fifo of generated text pages
5735 m_allocatedTextPagesFifo.append(page->number());
5736}
5737
5739{
5740 d->setRotationInternal(r, true);
5741}
5742
5743void DocumentPrivate::setRotationInternal(int r, bool notify)
5744{
5745 Rotation rotation = (Rotation)r;
5746 if (!m_generator || (m_rotation == rotation)) {
5747 return;
5748 }
5749
5750 // tell the pages to rotate
5751 QVector<Okular::Page *>::const_iterator pIt = m_pagesVector.constBegin();
5752 QVector<Okular::Page *>::const_iterator pEnd = m_pagesVector.constEnd();
5753 for (; pIt != pEnd; ++pIt) {
5754 (*pIt)->d->rotateAt(rotation);
5755 }
5756 if (notify) {
5757 // notify the generator that the current rotation has changed
5758 m_generator->rotationChanged(rotation, m_rotation);
5759 }
5760 // set the new rotation
5761 m_rotation = rotation;
5762
5763 if (notify) {
5764 foreachObserverD(notifySetup(m_pagesVector, DocumentObserver::NewLayoutForPages));
5765 foreachObserverD(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations));
5766 }
5767 qCDebug(OkularCoreDebug) << "Rotated:" << r;
5768}
5769
5771{
5772 if (!d->m_generator || !d->m_generator->hasFeature(Generator::PageSizes)) {
5773 return;
5774 }
5775
5776 if (d->m_pageSizes.isEmpty()) {
5777 d->m_pageSizes = d->m_generator->pageSizes();
5778 }
5779 int sizeid = d->m_pageSizes.indexOf(size);
5780 if (sizeid == -1) {
5781 return;
5782 }
5783
5784 // tell the pages to change size
5785 QVector<Okular::Page *>::const_iterator pIt = d->m_pagesVector.constBegin();
5786 QVector<Okular::Page *>::const_iterator pEnd = d->m_pagesVector.constEnd();
5787 for (; pIt != pEnd; ++pIt) {
5788 (*pIt)->d->changeSize(size);
5789 }
5790 // clear 'memory allocation' descriptors
5791 qDeleteAll(d->m_allocatedPixmaps);
5792 d->m_allocatedPixmaps.clear();
5793 d->m_allocatedPixmapsTotalMemory = 0;
5794 // notify the generator that the current page size has changed
5795 d->m_generator->pageSizeChanged(size, d->m_pageSize);
5796 // set the new page size
5797 d->m_pageSize = size;
5798
5799 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::NewLayoutForPages));
5800 foreachObserver(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights));
5801 qCDebug(OkularCoreDebug) << "New PageSize id:" << sizeid;
5802}
5803
5804/** DocumentViewport **/
5805
5807 : pageNumber(n)
5808{
5809 // default settings
5810 rePos.enabled = false;
5811 rePos.normalizedX = 0.5;
5812 rePos.normalizedY = 0.0;
5813 rePos.pos = Center;
5814 autoFit.enabled = false;
5815 autoFit.width = false;
5816 autoFit.height = false;
5817}
5818
5820 : pageNumber(-1)
5821{
5822 // default settings (maybe overridden below)
5823 rePos.enabled = false;
5824 rePos.normalizedX = 0.5;
5825 rePos.normalizedY = 0.0;
5826 rePos.pos = Center;
5827 autoFit.enabled = false;
5828 autoFit.width = false;
5829 autoFit.height = false;
5830
5831 // check for string presence
5832 if (xmlDesc.isEmpty()) {
5833 return;
5834 }
5835
5836 // decode the string
5837 bool ok;
5838 int field = 0;
5839 QString token = xmlDesc.section(QLatin1Char(';'), field, field);
5840 while (!token.isEmpty()) {
5841 // decode the current token
5842 if (field == 0) {
5843 pageNumber = token.toInt(&ok);
5844 if (!ok) {
5845 return;
5846 }
5847 } else if (token.startsWith(QLatin1String("C1"))) {
5848 rePos.enabled = true;
5849 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5850 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5851 rePos.pos = Center;
5852 } else if (token.startsWith(QLatin1String("C2"))) {
5853 rePos.enabled = true;
5854 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5855 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5856 if (token.section(QLatin1Char(':'), 3, 3).toInt() == 1) {
5857 rePos.pos = Center;
5858 } else {
5859 rePos.pos = TopLeft;
5860 }
5861 } else if (token.startsWith(QLatin1String("AF1"))) {
5862 autoFit.enabled = true;
5863 autoFit.width = token.section(QLatin1Char(':'), 1, 1) == QLatin1String("T");
5864 autoFit.height = token.section(QLatin1Char(':'), 2, 2) == QLatin1String("T");
5865 }
5866 // proceed tokenizing string
5867 field++;
5868 token = xmlDesc.section(QLatin1Char(';'), field, field);
5869 }
5870}
5871
5873{
5874 // start string with page number
5876 // if has center coordinates, save them on string
5877 if (rePos.enabled) {
5878 s += QStringLiteral(";C2:") + QString::number(rePos.normalizedX) + QLatin1Char(':') + QString::number(rePos.normalizedY) + QLatin1Char(':') + QString::number(rePos.pos);
5879 }
5880 // if has autofit enabled, save its state on string
5881 if (autoFit.enabled) {
5882 s += QStringLiteral(";AF1:") + (autoFit.width ? QLatin1Char('T') : QLatin1Char('F')) + QLatin1Char(':') + (autoFit.height ? QLatin1Char('T') : QLatin1Char('F'));
5883 }
5884 return s;
5885}
5886
5888{
5889 return pageNumber >= 0;
5890}
5891
5893{
5894 bool equal = (pageNumber == other.pageNumber) && (rePos.enabled == other.rePos.enabled) && (autoFit.enabled == other.autoFit.enabled);
5895 if (!equal) {
5896 return false;
5897 }
5898 if (rePos.enabled && ((rePos.normalizedX != other.rePos.normalizedX) || (rePos.normalizedY != other.rePos.normalizedY) || rePos.pos != other.rePos.pos)) {
5899 return false;
5900 }
5901 if (autoFit.enabled && ((autoFit.width != other.autoFit.width) || (autoFit.height != other.autoFit.height))) {
5902 return false;
5903 }
5904 return true;
5905}
5906
5907bool DocumentViewport::operator<(const DocumentViewport &other) const
5908{
5909 // TODO: Check autoFit and Position
5910
5911 if (pageNumber != other.pageNumber) {
5912 return pageNumber < other.pageNumber;
5913 }
5914
5915 if (!rePos.enabled && other.rePos.enabled) {
5916 return true;
5917 }
5918
5919 if (!other.rePos.enabled) {
5920 return false;
5921 }
5922
5923 if (rePos.normalizedY != other.rePos.normalizedY) {
5924 return rePos.normalizedY < other.rePos.normalizedY;
5925 }
5926
5927 return rePos.normalizedX < other.rePos.normalizedX;
5928}
5929
5930/** DocumentInfo **/
5931
5933 : d(new DocumentInfoPrivate())
5934{
5935}
5936
5938 : d(new DocumentInfoPrivate())
5939{
5940 *this = info;
5941}
5942
5943DocumentInfo &DocumentInfo::operator=(const DocumentInfo &info)
5944{
5945 if (this != &info) {
5946 d->values = info.d->values;
5947 d->titles = info.d->titles;
5948 }
5949 return *this;
5950}
5951
5952DocumentInfo::~DocumentInfo()
5953{
5954 delete d;
5955}
5956
5957void DocumentInfo::set(const QString &key, const QString &value, const QString &title)
5958{
5959 d->values[key] = value;
5960 d->titles[key] = title;
5961}
5962
5963void DocumentInfo::set(Key key, const QString &value)
5964{
5965 d->values[getKeyString(key)] = value;
5966}
5967
5969{
5970 return d->values.keys();
5971}
5972
5974{
5975 return get(getKeyString(key));
5976}
5977
5979{
5980 return d->values[key];
5981}
5982
5984{
5985 switch (key) {
5986 case Title:
5987 return QStringLiteral("title");
5988 break;
5989 case Subject:
5990 return QStringLiteral("subject");
5991 break;
5992 case Description:
5993 return QStringLiteral("description");
5994 break;
5995 case Author:
5996 return QStringLiteral("author");
5997 break;
5998 case Creator:
5999 return QStringLiteral("creator");
6000 break;
6001 case Producer:
6002 return QStringLiteral("producer");
6003 break;
6004 case Copyright:
6005 return QStringLiteral("copyright");
6006 break;
6007 case Pages:
6008 return QStringLiteral("pages");
6009 break;
6010 case CreationDate:
6011 return QStringLiteral("creationDate");
6012 break;
6013 case ModificationDate:
6014 return QStringLiteral("modificationDate");
6015 break;
6016 case MimeType:
6017 return QStringLiteral("mimeType");
6018 break;
6019 case Category:
6020 return QStringLiteral("category");
6021 break;
6022 case Keywords:
6023 return QStringLiteral("keywords");
6024 break;
6025 case FilePath:
6026 return QStringLiteral("filePath");
6027 break;
6028 case DocumentSize:
6029 return QStringLiteral("documentSize");
6030 break;
6031 case PagesSize:
6032 return QStringLiteral("pageSize");
6033 break;
6034 default:
6035 qCWarning(OkularCoreDebug) << "Unknown" << key;
6036 return QString();
6037 break;
6038 }
6039}
6040
6042{
6043 if (key == QLatin1String("title")) {
6044 return Title;
6045 } else if (key == QLatin1String("subject")) {
6046 return Subject;
6047 } else if (key == QLatin1String("description")) {
6048 return Description;
6049 } else if (key == QLatin1String("author")) {
6050 return Author;
6051 } else if (key == QLatin1String("creator")) {
6052 return Creator;
6053 } else if (key == QLatin1String("producer")) {
6054 return Producer;
6055 } else if (key == QLatin1String("copyright")) {
6056 return Copyright;
6057 } else if (key == QLatin1String("pages")) {
6058 return Pages;
6059 } else if (key == QLatin1String("creationDate")) {
6060 return CreationDate;
6061 } else if (key == QLatin1String("modificationDate")) {
6062 return ModificationDate;
6063 } else if (key == QLatin1String("mimeType")) {
6064 return MimeType;
6065 } else if (key == QLatin1String("category")) {
6066 return Category;
6067 } else if (key == QLatin1String("keywords")) {
6068 return Keywords;
6069 } else if (key == QLatin1String("filePath")) {
6070 return FilePath;
6071 } else if (key == QLatin1String("documentSize")) {
6072 return DocumentSize;
6073 } else if (key == QLatin1String("pageSize")) {
6074 return PagesSize;
6075 } else {
6076 return Invalid;
6077 }
6078}
6079
6081{
6082 switch (key) {
6083 case Title:
6084 return i18n("Title");
6085 break;
6086 case Subject:
6087 return i18n("Subject");
6088 break;
6089 case Description:
6090 return i18n("Description");
6091 break;
6092 case Author:
6093 return i18n("Author");
6094 break;
6095 case Creator:
6096 return i18n("Creator");
6097 break;
6098 case Producer:
6099 return i18n("Producer");
6100 break;
6101 case Copyright:
6102 return i18n("Copyright");
6103 break;
6104 case Pages:
6105 return i18n("Pages");
6106 break;
6107 case CreationDate:
6108 return i18n("Created");
6109 break;
6110 case ModificationDate:
6111 return i18n("Modified");
6112 break;
6113 case MimeType:
6114 return i18n("MIME Type");
6115 break;
6116 case Category:
6117 return i18n("Category");
6118 break;
6119 case Keywords:
6120 return i18n("Keywords");
6121 break;
6122 case FilePath:
6123 return i18n("File Path");
6124 break;
6125 case DocumentSize:
6126 return i18n("File Size");
6127 break;
6128 case PagesSize:
6129 return i18n("Page Size");
6130 break;
6131 default:
6132 return QString();
6133 break;
6134 }
6135}
6136
6138{
6139 QString title = getKeyTitle(getKeyFromString(key));
6140 if (title.isEmpty()) {
6141 title = d->titles[key];
6142 }
6143 return title;
6144}
6145
6146/** DocumentSynopsis **/
6147
6149 : QDomDocument(QStringLiteral("DocumentSynopsis"))
6150{
6151 // void implementation, only subclassed for naming
6152}
6153
6155 : QDomDocument(document)
6156{
6157}
6158
6159/** EmbeddedFile **/
6160
6164
6168
6170 : pageNumber(page)
6171 , rect(rectangle)
6172{
6173}
6174
6175/** NewSignatureData **/
6176
6177struct Okular::NewSignatureDataPrivate {
6178 NewSignatureDataPrivate() = default;
6179
6180 QString certNickname;
6181 QString certSubjectCommonName;
6182 QString password;
6183 QString documentPassword;
6184 QString location;
6185 QString reason;
6186 QString backgroundImagePath;
6187 double fontSize = 10;
6188 double leftFontSize = 20;
6189 int page = -1;
6190 NormalizedRect boundingRectangle;
6191};
6192
6193NewSignatureData::NewSignatureData()
6194 : d(new NewSignatureDataPrivate())
6195{
6196}
6197
6198NewSignatureData::~NewSignatureData()
6199{
6200 delete d;
6201}
6202
6203QString NewSignatureData::certNickname() const
6204{
6205 return d->certNickname;
6206}
6207
6208void NewSignatureData::setCertNickname(const QString &certNickname)
6209{
6210 d->certNickname = certNickname;
6211}
6212
6213QString NewSignatureData::certSubjectCommonName() const
6214{
6215 return d->certSubjectCommonName;
6216}
6217
6218void NewSignatureData::setCertSubjectCommonName(const QString &certSubjectCommonName)
6219{
6220 d->certSubjectCommonName = certSubjectCommonName;
6221}
6222
6223QString NewSignatureData::password() const
6224{
6225 return d->password;
6226}
6227
6228void NewSignatureData::setPassword(const QString &password)
6229{
6230 d->password = password;
6231}
6232
6233int NewSignatureData::page() const
6234{
6235 return d->page;
6236}
6237
6238void NewSignatureData::setPage(int page)
6239{
6240 d->page = page;
6241}
6242
6243NormalizedRect NewSignatureData::boundingRectangle() const
6244{
6245 return d->boundingRectangle;
6246}
6247
6248void NewSignatureData::setBoundingRectangle(const NormalizedRect &rect)
6249{
6250 d->boundingRectangle = rect;
6251}
6252
6254{
6255 return d->documentPassword;
6256}
6257
6259{
6260 d->documentPassword = password;
6261}
6262
6264{
6265 return d->location;
6266}
6267
6269{
6270 d->location = location;
6271}
6272
6274{
6275 return d->reason;
6276}
6277
6279{
6280 d->reason = reason;
6281}
6282
6284{
6285 return d->backgroundImagePath;
6286}
6287
6289{
6290 d->backgroundImagePath = path;
6291}
6292
6294{
6295 return d->fontSize;
6296}
6297
6299{
6300 d->fontSize = fontSize;
6301}
6302
6304{
6305 return d->leftFontSize;
6306}
6307
6309{
6310 d->leftFontSize = fontSize;
6311}
6312
6313#undef foreachObserver
6314#undef foreachObserverD
6315
6316#include "document.moc"
6317
6318/* 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)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT QUrl upUrl(const QUrl &url)
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
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)
QString name(StandardAction id)
const QList< QKeySequence > & forward()
const QList< QKeySequence > & end()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
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 Apr 25 2025 11:55:34 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.