Okular

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

KDE's Doxygen guidelines are available online.