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 Q_ASSERT(subview.length() == changeLength);
4446 return QString::fromUcs4(subview.data(), subview.length());
4447}
4448
4449void Document::processKeystrokeAction(const Action *action, Okular::FormField *ff, const QVariant &newValue, int prevCursorPos, int prevAnchorPos)
4450{
4451 if (action->actionType() != Action::Script) {
4452 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4453 return;
4454 }
4455 // Lookup the page of the FormFieldText
4456 int foundPage = d->findFieldPageNumber(ff);
4457
4458 if (foundPage == -1) {
4459 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4460 return;
4461 }
4462
4463 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4464
4465 /* Set the selStart and selEnd event properties
4466
4467 QString using UTF-16 counts a code point as made up of 1 or 2 16-bit code units.
4468
4469 When encoded using 2 code units, the units are referred to as surrogate pairs.
4470 selectionStart() and selectionEnd() methods evaluate prevCursorPos and prevAnchorPos based on code units during selection.
4471
4472 While this unit-based evaulation is suitable for detecting changes, for providing consistency with Adobe Reader for values of selStart and selEnd,
4473 it would be best to evaluate in terms of code points rather than the code units.
4474
4475 To correct the values of selStart and selEnd accordingly, we iterate over the code units. If a surrogate pair is encountered, then selStart and
4476 selEnd are accordingly decremented.
4477 */
4478 int selStart = std::min(prevCursorPos, prevAnchorPos);
4479 int selEnd = std::max(prevCursorPos, prevAnchorPos);
4480 int codeUnit;
4481 int initialSelStart = selStart;
4482 int initialSelEnd = selEnd;
4483 QString inputString = ff->value().toString();
4484 for (codeUnit = 0; codeUnit < initialSelStart && codeUnit < inputString.size(); codeUnit++) {
4485 if (inputString.at(codeUnit).isHighSurrogate()) {
4486 // skip the low surrogate and decrement selStart and selEnd
4487 codeUnit++;
4488 selStart--;
4489 selEnd--;
4490 }
4491 }
4492 for (; codeUnit < initialSelEnd && codeUnit < inputString.size(); codeUnit++) {
4493 if (inputString.at(codeUnit).isHighSurrogate()) {
4494 // skip the low surrogate and decrement selEnd
4495 codeUnit++;
4496 selEnd--;
4497 }
4498 }
4499 std::u32string oldUcs4 = inputString.toStdU32String();
4500 std::u32string newUcs4 = newValue.toString().toStdU32String();
4501 // It is necessary to count size in terms of code points rather than code units for deletion.
4502 if (oldUcs4.size() - newUcs4.size() == 1 && selStart == selEnd) {
4503 // consider a one character removal as selection of that character and then its removal.
4504 selStart--;
4505 }
4506 event->setSelStart(selStart);
4507 event->setSelEnd(selEnd);
4508 // Use the corrected selStart and selEnd for evaluating the change.
4509 event->setChange(DocumentPrivate::evaluateKeystrokeEventChange(inputString, newValue.toString(), selStart, selEnd));
4510 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4511
4512 d->executeScriptEvent(event, linkscript);
4513
4514 if (event->returnCode()) {
4515 ff->setValue(newValue);
4516 } else {
4518 }
4519}
4520
4522{
4523 // use -1 as default
4524 processKeystrokeAction(action, fft, newValue, -1, -1);
4525}
4526
4528{
4529 bool returnCode = false;
4530 processKeystrokeCommitAction(action, fft, returnCode);
4531}
4532
4533void Document::processKeystrokeCommitAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4534{
4535 if (action->actionType() != Action::Script) {
4536 qCDebug(OkularCoreDebug) << "Unsupported action type" << action->actionType() << "for keystroke.";
4537 return;
4538 }
4539 // Lookup the page of the FormFieldText
4540 int foundPage = d->findFieldPageNumber(ff);
4541
4542 if (foundPage == -1) {
4543 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4544 return;
4545 }
4546
4547 std::shared_ptr<Event> event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4548 event->setWillCommit(true);
4549
4550 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4551
4552 d->executeScriptEvent(event, linkscript);
4553
4554 if (!event->returnCode()) {
4557 ff->setValue(QVariant(ff->committedValue()));
4558 } else {
4559 ff->setValue(QVariant(event->value().toString()));
4561 }
4562 returnCode = event->returnCode();
4563}
4564
4566{
4567 if (!action || action->actionType() != Action::Script) {
4568 return;
4569 }
4570
4571 // Lookup the page of the FormFieldText
4572 int foundPage = d->findFieldPageNumber(field);
4573
4574 if (foundPage == -1) {
4575 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4576 return;
4577 }
4578
4579 std::shared_ptr<Event> event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4580
4581 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4582
4583 d->executeScriptEvent(event, linkscript);
4584}
4585
4586void Document::processValidateAction(const Action *action, Okular::FormFieldText *fft, bool &returnCode)
4587{
4588 processValidateAction(action, static_cast<FormField *>(fft), returnCode);
4589}
4590
4591void Document::processValidateAction(const Action *action, Okular::FormField *ff, bool &returnCode)
4592{
4593 if (!action || action->actionType() != Action::Script) {
4594 return;
4595 }
4596
4597 // Lookup the page of the FormFieldText
4598 int foundPage = d->findFieldPageNumber(ff);
4599
4600 if (foundPage == -1) {
4601 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4602 return;
4603 }
4604
4605 std::shared_ptr<Event> event = Event::createFormValidateEvent(ff, d->m_pagesVector[foundPage]);
4606
4607 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4608
4609 d->executeScriptEvent(event, linkscript);
4610 if (!event->returnCode()) {
4613 ff->setValue(QVariant(ff->committedValue()));
4614 } else {
4615 ff->setValue(QVariant(event->value().toString()));
4617 }
4618 returnCode = event->returnCode();
4619}
4620
4622{
4623 if (ff->value().toString() == ff->committedValue()) {
4626 ff->setValue(QVariant(ff->committedValue()));
4627 return;
4628 }
4629
4630 bool returnCode = true;
4633 }
4634
4636 if (returnCode) {
4637 processValidateAction(action, ff, returnCode);
4638 }
4639 }
4640
4641 if (!returnCode) {
4642 return;
4643 } else {
4644 ff->commitValue(ff->value().toString());
4645 }
4646
4648
4650 processFormatAction(action, ff);
4651 } else {
4652 ff->commitFormattedValue(ff->value().toString());
4653 }
4654}
4655
4657{
4658 if (!action || action->actionType() != Action::Script) {
4659 return;
4660 }
4661
4662 Event::EventType eventType = Okular::Event::UnknownEvent;
4663
4664 switch (type) {
4665 case Document::CloseDocument:
4666 eventType = Okular::Event::DocWillClose;
4667 break;
4668 case Document::SaveDocumentStart:
4669 eventType = Okular::Event::DocWillSave;
4670 break;
4671 case Document::SaveDocumentFinish:
4672 eventType = Okular::Event::DocDidSave;
4673 break;
4674 case Document::PrintDocumentStart:
4675 eventType = Okular::Event::DocWillPrint;
4676 break;
4677 case Document::PrintDocumentFinish:
4678 eventType = Okular::Event::DocDidPrint;
4679 break;
4680 }
4681
4682 std::shared_ptr<Event> event = Event::createDocEvent(eventType);
4683
4684 const ScriptAction *linkScript = static_cast<const ScriptAction *>(action);
4685
4686 d->executeScriptEvent(event, linkScript);
4687}
4688
4690{
4691 if (!action || action->actionType() != Action::Script) {
4692 return;
4693 }
4694
4695 Okular::Event::EventType eventType = Okular::Event::UnknownEvent;
4696
4697 switch (fieldMouseEventType) {
4698 case Document::FieldMouseDown:
4699 eventType = Okular::Event::FieldMouseDown;
4700 break;
4702 eventType = Okular::Event::FieldMouseEnter;
4703 break;
4705 eventType = Okular::Event::FieldMouseExit;
4706 break;
4708 eventType = Okular::Event::FieldMouseUp;
4709 break;
4710 }
4711
4712 // Lookup the page of the FormFieldText
4713 int foundPage = d->findFieldPageNumber(ff);
4714
4715 if (foundPage == -1) {
4716 qCDebug(OkularCoreDebug) << "Could not find page for formfield!";
4717 return;
4718 }
4719
4720 std::shared_ptr<Event> event = Event::createFieldMouseEvent(ff, d->m_pagesVector[foundPage], eventType);
4721
4722 const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
4723
4724 d->executeScriptEvent(event, linkscript);
4725}
4726
4731
4733{
4734 if (!ref) {
4735 return;
4736 }
4737
4738 const QUrl url = d->giveAbsoluteUrl(ref->fileName());
4739 if (!url.isLocalFile()) {
4740 qCDebug(OkularCoreDebug) << url.url() << "is not a local file.";
4741 return;
4742 }
4743
4744 const QString absFileName = url.toLocalFile();
4745 if (!QFile::exists(absFileName)) {
4746 qCDebug(OkularCoreDebug) << "No such file:" << absFileName;
4747 return;
4748 }
4749
4750 bool handled = false;
4751 Q_EMIT sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled);
4752 if (handled) {
4753 return;
4754 }
4755
4756 static QHash<int, QString> editors;
4757 // init the editors table if empty (on first run, usually)
4758 if (editors.isEmpty()) {
4759 editors = buildEditorsMap();
4760 }
4761
4762 // prefer the editor from the command line
4763 QString p = d->editorCommandOverride;
4764 if (p.isEmpty()) {
4765 QHash<int, QString>::const_iterator it = editors.constFind(SettingsCore::externalEditor());
4766 if (it != editors.constEnd()) {
4767 p = *it;
4768 } else {
4769 p = SettingsCore::externalEditorCommand();
4770 }
4771 }
4772 // custom editor not yet configured
4773 if (p.isEmpty()) {
4774 return;
4775 }
4776
4777 // manually append the %f placeholder if not specified
4778 if (p.indexOf(QLatin1String("%f")) == -1) {
4779 p.append(QLatin1String(" %f"));
4780 }
4781
4782 // replacing the placeholders
4784 map.insert(QLatin1Char('f'), absFileName);
4785 map.insert(QLatin1Char('c'), QString::number(ref->column()));
4786 map.insert(QLatin1Char('l'), QString::number(ref->row()));
4788 if (cmd.isEmpty()) {
4789 return;
4790 }
4791 QStringList args = KShell::splitArgs(cmd);
4792 if (args.isEmpty()) {
4793 return;
4794 }
4795
4796 const QString prog = args.takeFirst();
4797 // Make sure prog is in PATH and not just in the CWD
4798 const QString progFullPath = QStandardPaths::findExecutable(prog);
4799 if (progFullPath.isEmpty()) {
4800 return;
4801 }
4802
4803 KProcess::startDetached(progFullPath, args);
4804}
4805
4806const SourceReference *Document::dynamicSourceReference(int pageNr, double absX, double absY)
4807{
4808 if (!d->m_synctex_scanner) {
4809 return nullptr;
4810 }
4811
4812 const QSizeF dpi = d->m_generator->dpi();
4813
4814 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.width(), absY * 72. / dpi.height()) > 0) {
4815 synctex_node_p node;
4816 // TODO what should we do if there is really more than one node?
4817 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4818 int line = synctex_node_line(node);
4819 int col = synctex_node_column(node);
4820 // column extraction does not seem to be implemented in synctex so far. set the SourceReference default value.
4821 if (col == -1) {
4822 col = 0;
4823 }
4824 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4825
4826 return new Okular::SourceReference(QFile::decodeName(name), line, col);
4827 }
4828 }
4829 return nullptr;
4830}
4831
4833{
4834 if (d->m_generator) {
4835 if (d->m_generator->hasFeature(Generator::PrintNative)) {
4836 return NativePrinting;
4837 }
4838
4839#ifndef Q_OS_WIN
4840 if (d->m_generator->hasFeature(Generator::PrintPostscript)) {
4841 return PostscriptPrinting;
4842 }
4843#endif
4844 }
4845
4846 return NoPrinting;
4847}
4848
4850{
4851 return d->m_generator ? d->m_generator->hasFeature(Generator::PrintToFile) : false;
4852}
4853
4855{
4856 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentStart)) {
4857 processDocumentAction(action, PrintDocumentStart);
4858 }
4859 const Document::PrintError printError = d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4860 if (printError == Document::NoPrintError) {
4861 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentFinish)) {
4862 processDocumentAction(action, PrintDocumentFinish);
4863 }
4864 }
4865 return printError;
4866}
4867
4869{
4870 switch (error) {
4871 case TemporaryFileOpenPrintError:
4872 return i18n("Could not open a temporary file");
4873 case FileConversionPrintError:
4874 return i18n("Print conversion failed");
4875 case PrintingProcessCrashPrintError:
4876 return i18n("Printing process crashed");
4877 case PrintingProcessStartPrintError:
4878 return i18n("Printing process could not start");
4879 case PrintToFilePrintError:
4880 return i18n("Printing to file failed");
4881 case InvalidPrinterStatePrintError:
4882 return i18n("Printer was in invalid state");
4883 case UnableToFindFilePrintError:
4884 return i18n("Unable to find file to print");
4885 case NoFileToPrintError:
4886 return i18n("There was no file to print");
4887 case NoBinaryToPrintError:
4888 return i18n("Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4889 case InvalidPageSizePrintError:
4890 return i18n("The page print size is invalid");
4891 case NoPrintError:
4892 return QString();
4893 case UnknownPrintError:
4894 return QString();
4895 }
4896
4897 return QString();
4898}
4899
4901{
4902 if (d->m_generator) {
4904 return iface ? iface->printConfigurationWidget() : nullptr;
4905 } else {
4906 return nullptr;
4907 }
4908}
4909
4911{
4912 if (!dialog) {
4913 return;
4914 }
4915
4916 // We know it's a BackendConfigDialog, but check anyway
4917 BackendConfigDialog *bcd = dynamic_cast<BackendConfigDialog *>(dialog);
4918 if (!bcd) {
4919 return;
4920 }
4921
4922 // ensure that we have all the generators with settings loaded
4923 QVector<KPluginMetaData> offers = DocumentPrivate::configurableGenerators();
4924 d->loadServiceList(offers);
4925
4926 // We want the generators to be sorted by name so let's fill in a QMap
4927 // this sorts by internal id which is not awesome, but at least the sorting
4928 // is stable between runs that before it wasn't
4929 QMap<QString, GeneratorInfo> sortedGenerators;
4930 QHash<QString, GeneratorInfo>::iterator it = d->m_loadedGenerators.begin();
4931 QHash<QString, GeneratorInfo>::iterator itEnd = d->m_loadedGenerators.end();
4932 for (; it != itEnd; ++it) {
4933 sortedGenerators.insert(it.key(), it.value());
4934 }
4935
4936 bool pagesAdded = false;
4937 QMap<QString, GeneratorInfo>::iterator sit = sortedGenerators.begin();
4938 QMap<QString, GeneratorInfo>::iterator sitEnd = sortedGenerators.end();
4939 for (; sit != sitEnd; ++sit) {
4940 Okular::ConfigInterface *iface = d->generatorConfig(sit.value());
4941 if (iface) {
4942 iface->addPages(dialog);
4943 pagesAdded = true;
4944
4945 if (sit.value().generator == d->m_generator) {
4946 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4947 KPageView *view = bcd->thePageWidget();
4948 view->setCurrentPage(view->model()->index(rowCount - 1, 0));
4949 }
4950 }
4951 }
4952 if (pagesAdded) {
4953 connect(dialog, &KConfigDialog::settingsChanged, this, [this] { d->slotGeneratorConfigChanged(); });
4954 }
4955}
4956
4957QVector<KPluginMetaData> DocumentPrivate::configurableGenerators()
4958{
4959 const QVector<KPluginMetaData> available = availableGenerators();
4961 for (const KPluginMetaData &md : available) {
4962 if (md.rawData().value(QStringLiteral("X-KDE-okularHasInternalSettings")).toBool()) {
4963 result << md;
4964 }
4965 }
4966 return result;
4967}
4968
4970{
4971 if (!d->m_generator) {
4972 return KPluginMetaData();
4973 }
4974
4975 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4976 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4977 return genIt.value().metadata;
4978}
4979
4981{
4982 return DocumentPrivate::configurableGenerators().size();
4983}
4984
4986{
4987 // TODO: make it a static member of DocumentPrivate?
4988 QStringList result = d->m_supportedMimeTypes;
4989 if (result.isEmpty()) {
4990 const QVector<KPluginMetaData> available = DocumentPrivate::availableGenerators();
4991 for (const KPluginMetaData &md : available) {
4992 result << md.mimeTypes();
4993 }
4994
4995 // Remove duplicate mimetypes represented by different names
4996 QMimeDatabase mimeDatabase;
4997 QSet<QMimeType> uniqueMimetypes;
4998 for (const QString &mimeName : std::as_const(result)) {
4999 uniqueMimetypes.insert(mimeDatabase.mimeTypeForName(mimeName));
5000 }
5001 result.clear();
5002 for (const QMimeType &mimeType : uniqueMimetypes) {
5003 result.append(mimeType.name());
5004 }
5005
5006 // Add the Okular archive mimetype
5007 result << QStringLiteral("application/vnd.kde.okular-archive");
5008
5009 // Sorting by mimetype name doesn't make a ton of sense,
5010 // but ensures that the list is ordered the same way every time
5011 std::sort(result.begin(), result.end());
5012
5013 d->m_supportedMimeTypes = result;
5014 }
5015 return result;
5016}
5017
5019{
5020 if (!d->m_generator) {
5021 return false;
5022 }
5023
5024 return d->m_generator->hasFeature(Generator::SwapBackingFile);
5025}
5026
5027bool Document::swapBackingFile(const QString &newFileName, const QUrl &url)
5028{
5029 if (!d->m_generator) {
5030 return false;
5031 }
5032
5033 if (!d->m_generator->hasFeature(Generator::SwapBackingFile)) {
5034 return false;
5035 }
5036
5037 // Save metadata about the file we're about to close
5038 d->saveDocumentInfo();
5039
5040 d->clearAndWaitForRequests();
5041
5042 qCDebug(OkularCoreDebug) << "Swapping backing file to" << newFileName;
5043 QVector<Page *> newPagesVector;
5044 Generator::SwapBackingFileResult result = d->m_generator->swapBackingFile(newFileName, newPagesVector);
5045 if (result != Generator::SwapBackingFileError) {
5046 QList<ObjectRect *> rectsToDelete;
5047 QList<Annotation *> annotationsToDelete;
5048 QSet<PagePrivate *> pagePrivatesToDelete;
5049
5050 if (result == Generator::SwapBackingFileReloadInternalData) {
5051 // Here we need to replace everything that the old generator
5052 // had created with what the new one has without making it look like
5053 // we have actually closed and opened the file again
5054
5055 // Simple sanity check
5056 if (newPagesVector.count() != d->m_pagesVector.count()) {
5057 return false;
5058 }
5059
5060 // Update the undo stack contents
5061 for (int i = 0; i < d->m_undoStack->count(); ++i) {
5062 // Trust me on the const_cast ^_^
5063 QUndoCommand *uc = const_cast<QUndoCommand *>(d->m_undoStack->command(i));
5064 if (OkularUndoCommand *ouc = dynamic_cast<OkularUndoCommand *>(uc)) {
5065 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
5066 if (!success) {
5067 qWarning() << "Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
5068 return false;
5069 }
5070 } else {
5071 qWarning() << "Document::swapBackingFile: Unhandled undo command" << uc;
5072 return false;
5073 }
5074 }
5075
5076 for (int i = 0; i < d->m_pagesVector.count(); ++i) {
5077 // switch the PagePrivate* from newPage to oldPage
5078 // this way everyone still holding Page* doesn't get
5079 // disturbed by it
5080 Page *oldPage = d->m_pagesVector[i];
5081 Page *newPage = newPagesVector[i];
5082 newPage->d->adoptGeneratedContents(oldPage->d);
5083
5084 pagePrivatesToDelete << oldPage->d;
5085 oldPage->d = newPage->d;
5086 oldPage->d->m_page = oldPage;
5087 oldPage->d->m_doc = d;
5088 newPage->d = nullptr;
5089
5090 annotationsToDelete << oldPage->m_annotations;
5091 rectsToDelete << oldPage->m_rects;
5092 oldPage->m_annotations = newPage->m_annotations;
5093 oldPage->m_rects = newPage->m_rects;
5094 }
5095 qDeleteAll(newPagesVector);
5096 }
5097
5098 d->m_url = url;
5099 d->m_docFileName = newFileName;
5100 d->updateMetadataXmlNameAndDocSize();
5101 d->m_bookmarkManager->setUrl(d->m_url);
5102 d->m_documentInfo = DocumentInfo();
5103 d->m_documentInfoAskedKeys.clear();
5104
5105 if (d->m_synctex_scanner) {
5106 synctex_scanner_free(d->m_synctex_scanner);
5107 d->m_synctex_scanner = synctex_scanner_new_with_output_file(QFile::encodeName(newFileName).constData(), nullptr, 1);
5108 if (!d->m_synctex_scanner && QFile::exists(newFileName + QLatin1String("sync"))) {
5109 d->loadSyncFile(newFileName);
5110 }
5111 }
5112
5113 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::UrlChanged));
5114
5115 qDeleteAll(annotationsToDelete);
5116 qDeleteAll(rectsToDelete);
5117 qDeleteAll(pagePrivatesToDelete);
5118
5119 return true;
5120 } else {
5121 return false;
5122 }
5123}
5124
5125bool Document::swapBackingFileArchive(const QString &newFileName, const QUrl &url)
5126{
5127 qCDebug(OkularCoreDebug) << "Swapping backing archive to" << newFileName;
5128
5129 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
5130 if (!newArchive) {
5131 return false;
5132 }
5133
5134 const QString tempFileName = newArchive->document.fileName();
5135
5136 const bool success = swapBackingFile(tempFileName, url);
5137
5138 if (success) {
5139 delete d->m_archiveData;
5140 d->m_archiveData = newArchive;
5141 }
5142
5143 return success;
5144}
5145
5147{
5148 if (clean) {
5149 d->m_undoStack->setClean();
5150 } else {
5151 d->m_undoStack->resetClean();
5152 }
5153}
5154
5155bool Document::isHistoryClean() const
5156{
5157 return d->m_undoStack->isClean();
5158}
5159
5161{
5162 d->m_undoStack->clear();
5163}
5164
5166{
5167 if (!d->m_generator) {
5168 return false;
5169 }
5170 Q_ASSERT(!d->m_generatorName.isEmpty());
5171
5172 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5173 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5174 SaveInterface *saveIface = d->generatorSave(genIt.value());
5175 if (!saveIface) {
5176 return false;
5177 }
5178
5179 return saveIface->supportsOption(SaveInterface::SaveChanges);
5180}
5181
5183{
5184 switch (cap) {
5186 /* Assume that if the generator supports saving, forms can be saved.
5187 * We have no means to actually query the generator at the moment
5188 * TODO: Add some method to query the generator in SaveInterface */
5189 return canSaveChanges();
5190
5192 return d->canAddAnnotationsNatively();
5193 }
5194
5195 return false;
5196}
5197
5198bool Document::saveChanges(const QString &fileName)
5199{
5200 QString errorText;
5201 return saveChanges(fileName, &errorText);
5202}
5203
5204bool Document::saveChanges(const QString &fileName, QString *errorText)
5205{
5206 if (!d->m_generator || fileName.isEmpty()) {
5207 return false;
5208 }
5209 Q_ASSERT(!d->m_generatorName.isEmpty());
5210
5211 QHash<QString, GeneratorInfo>::iterator genIt = d->m_loadedGenerators.find(d->m_generatorName);
5212 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5213 SaveInterface *saveIface = d->generatorSave(genIt.value());
5214 if (!saveIface || !saveIface->supportsOption(SaveInterface::SaveChanges)) {
5215 return false;
5216 }
5217
5218 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentStart)) {
5219 processDocumentAction(action, SaveDocumentStart);
5220 }
5221
5222 bool success = saveIface->save(fileName, SaveInterface::SaveChanges, errorText);
5223 if (success) {
5224 if (const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentFinish)) {
5225 processDocumentAction(action, SaveDocumentFinish);
5226 }
5227 }
5228 return success;
5229}
5230
5232{
5233 if (!view) {
5234 return;
5235 }
5236
5237 Document *viewDoc = view->viewDocument();
5238 if (viewDoc) {
5239 // check if already registered for this document
5240 if (viewDoc == this) {
5241 return;
5242 }
5243
5244 viewDoc->unregisterView(view);
5245 }
5246
5247 d->m_views.insert(view);
5248 view->d_func()->document = d;
5249}
5250
5252{
5253 if (!view) {
5254 return;
5255 }
5256
5257 const Document *viewDoc = view->viewDocument();
5258 if (!viewDoc || viewDoc != this) {
5259 return;
5260 }
5261
5262 view->d_func()->document = nullptr;
5263 d->m_views.remove(view);
5264}
5265
5267{
5268 if (d->m_generator) {
5269 return d->m_generator->requestFontData(font);
5270 }
5271
5272 return {};
5273}
5274
5275ArchiveData *DocumentPrivate::unpackDocumentArchive(const QString &archivePath)
5276{
5277 QMimeDatabase db;
5278 const QMimeType mime = db.mimeTypeForFile(archivePath, QMimeDatabase::MatchExtension);
5279 if (!mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) {
5280 return nullptr;
5281 }
5282
5283 KZip okularArchive(archivePath);
5284 if (!okularArchive.open(QIODevice::ReadOnly)) {
5285 return nullptr;
5286 }
5287
5288 const KArchiveDirectory *mainDir = okularArchive.directory();
5289
5290 // Check the archive doesn't have folders, we don't create them when saving the archive
5291 // and folders mean paths and paths mean path traversal issues
5292 const QStringList mainDirEntries = mainDir->entries();
5293 for (const QString &entry : mainDirEntries) {
5294 if (mainDir->entry(entry)->isDirectory()) {
5295 qWarning() << "Warning: Found a directory inside" << archivePath << " - Okular does not create files like that so it is most probably forged.";
5296 return nullptr;
5297 }
5298 }
5299
5300 const KArchiveEntry *mainEntry = mainDir->entry(QStringLiteral("content.xml"));
5301 if (!mainEntry || !mainEntry->isFile()) {
5302 return nullptr;
5303 }
5304
5305 std::unique_ptr<QIODevice> mainEntryDevice(static_cast<const KZipFileEntry *>(mainEntry)->createDevice());
5306 QDomDocument doc;
5307 if (!doc.setContent(mainEntryDevice.get())) {
5308 return nullptr;
5309 }
5310 mainEntryDevice.reset();
5311
5312 QDomElement root = doc.documentElement();
5313 if (root.tagName() != QLatin1String("OkularArchive")) {
5314 return nullptr;
5315 }
5316
5317 QString documentFileName;
5318 QString metadataFileName;
5319 QDomElement el = root.firstChild().toElement();
5320 for (; !el.isNull(); el = el.nextSibling().toElement()) {
5321 if (el.tagName() == QLatin1String("Files")) {
5322 QDomElement fileEl = el.firstChild().toElement();
5323 for (; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement()) {
5324 if (fileEl.tagName() == QLatin1String("DocumentFileName")) {
5325 documentFileName = fileEl.text();
5326 } else if (fileEl.tagName() == QLatin1String("MetadataFileName")) {
5327 metadataFileName = fileEl.text();
5328 }
5329 }
5330 }
5331 }
5332 if (documentFileName.isEmpty()) {
5333 return nullptr;
5334 }
5335
5336 const KArchiveEntry *docEntry = mainDir->entry(documentFileName);
5337 if (!docEntry || !docEntry->isFile()) {
5338 return nullptr;
5339 }
5340
5341 std::unique_ptr<ArchiveData> archiveData(new ArchiveData());
5342 const int dotPos = documentFileName.indexOf(QLatin1Char('.'));
5343 if (dotPos != -1) {
5344 archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos));
5345 }
5346 if (!archiveData->document.open()) {
5347 return nullptr;
5348 }
5349
5350 archiveData->originalFileName = documentFileName;
5351
5352 {
5353 std::unique_ptr<QIODevice> docEntryDevice(static_cast<const KZipFileEntry *>(docEntry)->createDevice());
5354 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5355 archiveData->document.close();
5356 }
5357
5358 const KArchiveEntry *metadataEntry = mainDir->entry(metadataFileName);
5359 if (metadataEntry && metadataEntry->isFile()) {
5360 std::unique_ptr<QIODevice> metadataEntryDevice(static_cast<const KZipFileEntry *>(metadataEntry)->createDevice());
5361 archiveData->metadataFile.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.xml"));
5362 if (archiveData->metadataFile.open()) {
5363 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5364 archiveData->metadataFile.close();
5365 }
5366 }
5367
5368 return archiveData.release();
5369}
5370
5371Document::OpenResult Document::openDocumentArchive(const QString &docFile, const QUrl &url, const QString &password)
5372{
5373 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5374 if (!d->m_archiveData) {
5375 return OpenError;
5376 }
5377
5378 const QString tempFileName = d->m_archiveData->document.fileName();
5379 QMimeDatabase db;
5380 const QMimeType docMime = db.mimeTypeForFile(tempFileName, QMimeDatabase::MatchExtension);
5381 const OpenResult ret = openDocument(tempFileName, url, docMime, password);
5382
5383 if (ret != OpenSuccess) {
5384 delete d->m_archiveData;
5385 d->m_archiveData = nullptr;
5386 }
5387
5388 return ret;
5389}
5390
5392{
5393 if (!d->m_generator) {
5394 return false;
5395 }
5396
5397 /* If we opened an archive, use the name of original file (eg foo.pdf)
5398 * instead of the archive's one (eg foo.okular) */
5399 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5400 if (docFileName == QLatin1String("-")) {
5401 return false;
5402 }
5403
5404 QString docPath = d->m_docFileName;
5405 const QFileInfo fi(docPath);
5406 if (fi.isSymLink()) {
5407 docPath = fi.symLinkTarget();
5408 }
5409
5410 KZip okularArchive(fileName);
5411 if (!okularArchive.open(QIODevice::WriteOnly)) {
5412 return false;
5413 }
5414
5415 const KUser user;
5416#ifndef Q_OS_WIN
5417 const KUserGroup userGroup(user.groupId());
5418#else
5419 const KUserGroup userGroup(QStringLiteral(""));
5420#endif
5421
5422 QDomDocument contentDoc(QStringLiteral("OkularArchive"));
5423 QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\""));
5424 contentDoc.appendChild(xmlPi);
5425 QDomElement root = contentDoc.createElement(QStringLiteral("OkularArchive"));
5426 contentDoc.appendChild(root);
5427
5428 QDomElement filesNode = contentDoc.createElement(QStringLiteral("Files"));
5429 root.appendChild(filesNode);
5430
5431 QDomElement fileNameNode = contentDoc.createElement(QStringLiteral("DocumentFileName"));
5432 filesNode.appendChild(fileNameNode);
5433 fileNameNode.appendChild(contentDoc.createTextNode(docFileName));
5434
5435 QDomElement metadataFileNameNode = contentDoc.createElement(QStringLiteral("MetadataFileName"));
5436 filesNode.appendChild(metadataFileNameNode);
5437 metadataFileNameNode.appendChild(contentDoc.createTextNode(QStringLiteral("metadata.xml")));
5438
5439 // If the generator can save annotations natively, do it
5440 QTemporaryFile modifiedFile;
5441 bool annotationsSavedNatively = false;
5442 bool formsSavedNatively = false;
5443 if (d->canAddAnnotationsNatively() || canSaveChanges(SaveFormsCapability)) {
5444 if (!modifiedFile.open()) {
5445 return false;
5446 }
5447
5448 const QString modifiedFileName = modifiedFile.fileName();
5449
5450 modifiedFile.close(); // We're only interested in the file name
5451
5452 QString errorText;
5453 if (saveChanges(modifiedFileName, &errorText)) {
5454 docPath = modifiedFileName; // Save this instead of the original file
5455 annotationsSavedNatively = d->canAddAnnotationsNatively();
5456 formsSavedNatively = canSaveChanges(SaveFormsCapability);
5457 } else {
5458 qCWarning(OkularCoreDebug) << "saveChanges failed: " << errorText;
5459 qCDebug(OkularCoreDebug) << "Falling back to saving a copy of the original file";
5460 }
5461 }
5462
5463 PageItems saveWhat = None;
5464 if (!annotationsSavedNatively) {
5465 saveWhat |= AnnotationPageItems;
5466 }
5467 if (!formsSavedNatively) {
5468 saveWhat |= FormFieldPageItems;
5469 }
5470
5471 QTemporaryFile metadataFile;
5472 if (!d->savePageDocumentInfo(&metadataFile, saveWhat)) {
5473 return false;
5474 }
5475
5476 const QByteArray contentDocXml = contentDoc.toByteArray();
5477 const mode_t perm = 0100644;
5478 okularArchive.writeFile(QStringLiteral("content.xml"), contentDocXml, perm, user.loginName(), userGroup.name());
5479
5480 okularArchive.addLocalFile(docPath, docFileName);
5481 okularArchive.addLocalFile(metadataFile.fileName(), QStringLiteral("metadata.xml"));
5482
5483 if (!okularArchive.close()) {
5484 return false;
5485 }
5486
5487 return true;
5488}
5489
5491{
5492 if (!d->m_archiveData) {
5493 return false;
5494 }
5495
5496 // Remove existing file, if present (QFile::copy doesn't overwrite by itself)
5497 QFile::remove(destFileName);
5498
5499 return d->m_archiveData->document.copy(destFileName);
5500}
5501
5503{
5504 int landscape, portrait;
5505
5506 // if some pages are landscape and others are not, the most common wins, as
5507 // QPrinter does not accept a per-page setting
5508 landscape = 0;
5509 portrait = 0;
5510 for (uint i = 0; i < pages(); i++) {
5511 const Okular::Page *currentPage = page(i);
5512 double width = currentPage->width();
5513 double height = currentPage->height();
5514 if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) {
5515 std::swap(width, height);
5516 }
5517 if (width > height) {
5518 landscape++;
5519 } else {
5520 portrait++;
5521 }
5522 }
5523 return (landscape > portrait) ? QPageLayout::Landscape : QPageLayout::Portrait;
5524}
5525
5527{
5528 d->m_annotationEditingEnabled = enable;
5529 foreachObserver(notifySetup(d->m_pagesVector, 0));
5530}
5531
5532void Document::walletDataForFile(const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey) const
5533{
5534 if (d->m_generator) {
5535 d->m_generator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5536 } else if (d->m_walletGenerator) {
5537 d->m_walletGenerator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5538 }
5539}
5540
5542{
5543 return d->m_docdataMigrationNeeded;
5544}
5545
5547{
5548 if (d->m_docdataMigrationNeeded) {
5549 d->m_docdataMigrationNeeded = false;
5550 foreachObserver(notifySetup(d->m_pagesVector, 0));
5551 }
5552}
5553
5555{
5556 return d->m_generator ? d->m_generator->layersModel() : nullptr;
5557}
5558
5560{
5561 return d->m_openError;
5562}
5563
5564QByteArray Document::requestSignedRevisionData(const Okular::SignatureInfo &info)
5565{
5566 QFile f(d->m_docFileName);
5567 if (!f.open(QIODevice::ReadOnly)) {
5568 Q_EMIT error(i18n("Could not open '%1'. File does not exist", d->m_docFileName), -1);
5569 return {};
5570 }
5571
5572 const QList<qint64> byteRange = info.signedRangeBounds();
5573 f.seek(byteRange.first());
5574 QByteArray data = f.read(byteRange.last() - byteRange.first());
5575 f.close();
5576
5577 return data;
5578}
5579
5580void Document::refreshPixmaps(int pageNumber)
5581{
5582 d->refreshPixmaps(pageNumber);
5583}
5584
5585void DocumentPrivate::executeScript(const QString &function)
5586{
5587 if (!m_scripter) {
5588 m_scripter = new Scripter(this);
5589 }
5590 m_scripter->execute(JavaScript, function);
5591}
5592
5593void DocumentPrivate::requestDone(PixmapRequest *req)
5594{
5595 if (!req) {
5596 return;
5597 }
5598
5599 if (!m_generator || m_closingLoop) {
5600 m_pixmapRequestsMutex.lock();
5601 m_executingPixmapRequests.remove(req);
5602 m_pixmapRequestsMutex.unlock();
5603 delete req;
5604 if (m_closingLoop) {
5605 m_closingLoop->exit();
5606 }
5607 return;
5608 }
5609
5610#ifndef NDEBUG
5611 if (!m_generator->canGeneratePixmap()) {
5612 qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state.";
5613 }
5614#endif
5615
5616 if (!req->shouldAbortRender()) {
5617 // [MEM] 1.1 find and remove a previous entry for the same page and id
5618 std::list<AllocatedPixmap *>::iterator aIt = m_allocatedPixmaps.begin();
5619 std::list<AllocatedPixmap *>::iterator aEnd = m_allocatedPixmaps.end();
5620 for (; aIt != aEnd; ++aIt) {
5621 if ((*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer()) {
5622 AllocatedPixmap *p = *aIt;
5623 m_allocatedPixmaps.erase(aIt);
5624 m_allocatedPixmapsTotalMemory -= p->memory;
5625 delete p;
5626 break;
5627 }
5628 }
5629
5630 DocumentObserver *observer = req->observer();
5631 if (m_observers.contains(observer)) {
5632 // [MEM] 1.2 append memory allocation descriptor to the FIFO
5633 qulonglong memoryBytes = 0;
5634 const TilesManager *tm = req->d->tilesManager();
5635 if (tm) {
5636 memoryBytes = tm->totalMemory();
5637 } else {
5638 memoryBytes = 4 * req->width() * req->height();
5639 }
5640
5641 AllocatedPixmap *memoryPage = new AllocatedPixmap(req->observer(), req->pageNumber(), memoryBytes);
5642 m_allocatedPixmaps.push_back(memoryPage);
5643 m_allocatedPixmapsTotalMemory += memoryBytes;
5644
5645 // 2. notify an observer that its pixmap changed
5647 }
5648#ifndef NDEBUG
5649 else {
5650 qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer;
5651 }
5652#endif
5653 }
5654
5655 // 3. delete request
5656 m_pixmapRequestsMutex.lock();
5657 m_executingPixmapRequests.remove(req);
5658 m_pixmapRequestsMutex.unlock();
5659 delete req;
5660
5661 // 4. start a new generation if some is pending
5662 m_pixmapRequestsMutex.lock();
5663 bool hasPixmaps = !m_pixmapRequestsStack.empty();
5664 m_pixmapRequestsMutex.unlock();
5665 if (hasPixmaps) {
5666 sendGeneratorPixmapRequest();
5667 }
5668}
5669
5670void DocumentPrivate::setPageBoundingBox(int page, const NormalizedRect &boundingBox)
5671{
5672 Page *kp = m_pagesVector[page];
5673 if (!m_generator || !kp) {
5674 return;
5675 }
5676
5677 if (kp->boundingBox() == boundingBox) {
5678 return;
5679 }
5680 kp->setBoundingBox(boundingBox);
5681
5682 // notify observers about the change
5683 foreachObserverD(notifyPageChanged(page, DocumentObserver::BoundingBox));
5684
5685 // 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.
5686 // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away.
5687 // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker.
5688 // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off).
5689}
5690
5691void DocumentPrivate::calculateMaxTextPages()
5692{
5693 int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB
5694 switch (SettingsCore::memoryLevel()) {
5695 case SettingsCore::EnumMemoryLevel::Low:
5696 m_maxAllocatedTextPages = multipliers * 2;
5697 break;
5698
5699 case SettingsCore::EnumMemoryLevel::Normal:
5700 m_maxAllocatedTextPages = multipliers * 50;
5701 break;
5702
5703 case SettingsCore::EnumMemoryLevel::Aggressive:
5704 m_maxAllocatedTextPages = multipliers * 250;
5705 break;
5706
5707 case SettingsCore::EnumMemoryLevel::Greedy:
5708 m_maxAllocatedTextPages = multipliers * 1250;
5709 break;
5710 }
5711}
5712
5713void DocumentPrivate::textGenerationDone(Page *page)
5714{
5715 if (!m_pageController) {
5716 return;
5717 }
5718
5719 // 1. If we reached the cache limit, delete the first text page from the fifo
5720 if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) {
5721 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
5722 if (pageToKick != page->number()) // this should never happen but better be safe than sorry
5723 {
5724 m_pagesVector.at(pageToKick)->setTextPage(nullptr); // deletes the textpage
5725 }
5726 }
5727
5728 // 2. Add the page to the fifo of generated text pages
5729 m_allocatedTextPagesFifo.append(page->number());
5730}
5731
5733{
5734 d->setRotationInternal(r, true);
5735}
5736
5737void DocumentPrivate::setRotationInternal(int r, bool notify)
5738{
5739 Rotation rotation = (Rotation)r;
5740 if (!m_generator || (m_rotation == rotation)) {
5741 return;
5742 }
5743
5744 // tell the pages to rotate
5745 QVector<Okular::Page *>::const_iterator pIt = m_pagesVector.constBegin();
5746 QVector<Okular::Page *>::const_iterator pEnd = m_pagesVector.constEnd();
5747 for (; pIt != pEnd; ++pIt) {
5748 (*pIt)->d->rotateAt(rotation);
5749 }
5750 if (notify) {
5751 // notify the generator that the current rotation has changed
5752 m_generator->rotationChanged(rotation, m_rotation);
5753 }
5754 // set the new rotation
5755 m_rotation = rotation;
5756
5757 if (notify) {
5758 foreachObserverD(notifySetup(m_pagesVector, DocumentObserver::NewLayoutForPages));
5759 foreachObserverD(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations));
5760 }
5761 qCDebug(OkularCoreDebug) << "Rotated:" << r;
5762}
5763
5765{
5766 if (!d->m_generator || !d->m_generator->hasFeature(Generator::PageSizes)) {
5767 return;
5768 }
5769
5770 if (d->m_pageSizes.isEmpty()) {
5771 d->m_pageSizes = d->m_generator->pageSizes();
5772 }
5773 int sizeid = d->m_pageSizes.indexOf(size);
5774 if (sizeid == -1) {
5775 return;
5776 }
5777
5778 // tell the pages to change size
5779 QVector<Okular::Page *>::const_iterator pIt = d->m_pagesVector.constBegin();
5780 QVector<Okular::Page *>::const_iterator pEnd = d->m_pagesVector.constEnd();
5781 for (; pIt != pEnd; ++pIt) {
5782 (*pIt)->d->changeSize(size);
5783 }
5784 // clear 'memory allocation' descriptors
5785 qDeleteAll(d->m_allocatedPixmaps);
5786 d->m_allocatedPixmaps.clear();
5787 d->m_allocatedPixmapsTotalMemory = 0;
5788 // notify the generator that the current page size has changed
5789 d->m_generator->pageSizeChanged(size, d->m_pageSize);
5790 // set the new page size
5791 d->m_pageSize = size;
5792
5793 foreachObserver(notifySetup(d->m_pagesVector, DocumentObserver::NewLayoutForPages));
5794 foreachObserver(notifyContentsCleared(DocumentObserver::Pixmap | DocumentObserver::Highlights));
5795 qCDebug(OkularCoreDebug) << "New PageSize id:" << sizeid;
5796}
5797
5798/** DocumentViewport **/
5799
5801 : pageNumber(n)
5802{
5803 // default settings
5804 rePos.enabled = false;
5805 rePos.normalizedX = 0.5;
5806 rePos.normalizedY = 0.0;
5807 rePos.pos = Center;
5808 autoFit.enabled = false;
5809 autoFit.width = false;
5810 autoFit.height = false;
5811}
5812
5814 : pageNumber(-1)
5815{
5816 // default settings (maybe overridden below)
5817 rePos.enabled = false;
5818 rePos.normalizedX = 0.5;
5819 rePos.normalizedY = 0.0;
5820 rePos.pos = Center;
5821 autoFit.enabled = false;
5822 autoFit.width = false;
5823 autoFit.height = false;
5824
5825 // check for string presence
5826 if (xmlDesc.isEmpty()) {
5827 return;
5828 }
5829
5830 // decode the string
5831 bool ok;
5832 int field = 0;
5833 QString token = xmlDesc.section(QLatin1Char(';'), field, field);
5834 while (!token.isEmpty()) {
5835 // decode the current token
5836 if (field == 0) {
5837 pageNumber = token.toInt(&ok);
5838 if (!ok) {
5839 return;
5840 }
5841 } else if (token.startsWith(QLatin1String("C1"))) {
5842 rePos.enabled = true;
5843 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5844 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5845 rePos.pos = Center;
5846 } else if (token.startsWith(QLatin1String("C2"))) {
5847 rePos.enabled = true;
5848 rePos.normalizedX = token.section(QLatin1Char(':'), 1, 1).toDouble();
5849 rePos.normalizedY = token.section(QLatin1Char(':'), 2, 2).toDouble();
5850 if (token.section(QLatin1Char(':'), 3, 3).toInt() == 1) {
5851 rePos.pos = Center;
5852 } else {
5853 rePos.pos = TopLeft;
5854 }
5855 } else if (token.startsWith(QLatin1String("AF1"))) {
5856 autoFit.enabled = true;
5857 autoFit.width = token.section(QLatin1Char(':'), 1, 1) == QLatin1String("T");
5858 autoFit.height = token.section(QLatin1Char(':'), 2, 2) == QLatin1String("T");
5859 }
5860 // proceed tokenizing string
5861 field++;
5862 token = xmlDesc.section(QLatin1Char(';'), field, field);
5863 }
5864}
5865
5867{
5868 // start string with page number
5870 // if has center coordinates, save them on string
5871 if (rePos.enabled) {
5872 s += QStringLiteral(";C2:") + QString::number(rePos.normalizedX) + QLatin1Char(':') + QString::number(rePos.normalizedY) + QLatin1Char(':') + QString::number(rePos.pos);
5873 }
5874 // if has autofit enabled, save its state on string
5875 if (autoFit.enabled) {
5876 s += QStringLiteral(";AF1:") + (autoFit.width ? QLatin1Char('T') : QLatin1Char('F')) + QLatin1Char(':') + (autoFit.height ? QLatin1Char('T') : QLatin1Char('F'));
5877 }
5878 return s;
5879}
5880
5882{
5883 return pageNumber >= 0;
5884}
5885
5887{
5888 bool equal = (pageNumber == other.pageNumber) && (rePos.enabled == other.rePos.enabled) && (autoFit.enabled == other.autoFit.enabled);
5889 if (!equal) {
5890 return false;
5891 }
5892 if (rePos.enabled && ((rePos.normalizedX != other.rePos.normalizedX) || (rePos.normalizedY != other.rePos.normalizedY) || rePos.pos != other.rePos.pos)) {
5893 return false;
5894 }
5895 if (autoFit.enabled && ((autoFit.width != other.autoFit.width) || (autoFit.height != other.autoFit.height))) {
5896 return false;
5897 }
5898 return true;
5899}
5900
5901bool DocumentViewport::operator<(const DocumentViewport &other) const
5902{
5903 // TODO: Check autoFit and Position
5904
5905 if (pageNumber != other.pageNumber) {
5906 return pageNumber < other.pageNumber;
5907 }
5908
5909 if (!rePos.enabled && other.rePos.enabled) {
5910 return true;
5911 }
5912
5913 if (!other.rePos.enabled) {
5914 return false;
5915 }
5916
5917 if (rePos.normalizedY != other.rePos.normalizedY) {
5918 return rePos.normalizedY < other.rePos.normalizedY;
5919 }
5920
5921 return rePos.normalizedX < other.rePos.normalizedX;
5922}
5923
5924/** DocumentInfo **/
5925
5927 : d(new DocumentInfoPrivate())
5928{
5929}
5930
5932 : d(new DocumentInfoPrivate())
5933{
5934 *this = info;
5935}
5936
5937DocumentInfo &DocumentInfo::operator=(const DocumentInfo &info)
5938{
5939 if (this != &info) {
5940 d->values = info.d->values;
5941 d->titles = info.d->titles;
5942 }
5943 return *this;
5944}
5945
5946DocumentInfo::~DocumentInfo()
5947{
5948 delete d;
5949}
5950
5951void DocumentInfo::set(const QString &key, const QString &value, const QString &title)
5952{
5953 d->values[key] = value;
5954 d->titles[key] = title;
5955}
5956
5957void DocumentInfo::set(Key key, const QString &value)
5958{
5959 d->values[getKeyString(key)] = value;
5960}
5961
5963{
5964 return d->values.keys();
5965}
5966
5968{
5969 return get(getKeyString(key));
5970}
5971
5973{
5974 return d->values[key];
5975}
5976
5978{
5979 switch (key) {
5980 case Title:
5981 return QStringLiteral("title");
5982 break;
5983 case Subject:
5984 return QStringLiteral("subject");
5985 break;
5986 case Description:
5987 return QStringLiteral("description");
5988 break;
5989 case Author:
5990 return QStringLiteral("author");
5991 break;
5992 case Creator:
5993 return QStringLiteral("creator");
5994 break;
5995 case Producer:
5996 return QStringLiteral("producer");
5997 break;
5998 case Copyright:
5999 return QStringLiteral("copyright");
6000 break;
6001 case Pages:
6002 return QStringLiteral("pages");
6003 break;
6004 case CreationDate:
6005 return QStringLiteral("creationDate");
6006 break;
6007 case ModificationDate:
6008 return QStringLiteral("modificationDate");
6009 break;
6010 case MimeType:
6011 return QStringLiteral("mimeType");
6012 break;
6013 case Category:
6014 return QStringLiteral("category");
6015 break;
6016 case Keywords:
6017 return QStringLiteral("keywords");
6018 break;
6019 case FilePath:
6020 return QStringLiteral("filePath");
6021 break;
6022 case DocumentSize:
6023 return QStringLiteral("documentSize");
6024 break;
6025 case PagesSize:
6026 return QStringLiteral("pageSize");
6027 break;
6028 default:
6029 qCWarning(OkularCoreDebug) << "Unknown" << key;
6030 return QString();
6031 break;
6032 }
6033}
6034
6036{
6037 if (key == QLatin1String("title")) {
6038 return Title;
6039 } else if (key == QLatin1String("subject")) {
6040 return Subject;
6041 } else if (key == QLatin1String("description")) {
6042 return Description;
6043 } else if (key == QLatin1String("author")) {
6044 return Author;
6045 } else if (key == QLatin1String("creator")) {
6046 return Creator;
6047 } else if (key == QLatin1String("producer")) {
6048 return Producer;
6049 } else if (key == QLatin1String("copyright")) {
6050 return Copyright;
6051 } else if (key == QLatin1String("pages")) {
6052 return Pages;
6053 } else if (key == QLatin1String("creationDate")) {
6054 return CreationDate;
6055 } else if (key == QLatin1String("modificationDate")) {
6056 return ModificationDate;
6057 } else if (key == QLatin1String("mimeType")) {
6058 return MimeType;
6059 } else if (key == QLatin1String("category")) {
6060 return Category;
6061 } else if (key == QLatin1String("keywords")) {
6062 return Keywords;
6063 } else if (key == QLatin1String("filePath")) {
6064 return FilePath;
6065 } else if (key == QLatin1String("documentSize")) {
6066 return DocumentSize;
6067 } else if (key == QLatin1String("pageSize")) {
6068 return PagesSize;
6069 } else {
6070 return Invalid;
6071 }
6072}
6073
6075{
6076 switch (key) {
6077 case Title:
6078 return i18n("Title");
6079 break;
6080 case Subject:
6081 return i18n("Subject");
6082 break;
6083 case Description:
6084 return i18n("Description");
6085 break;
6086 case Author:
6087 return i18n("Author");
6088 break;
6089 case Creator:
6090 return i18n("Creator");
6091 break;
6092 case Producer:
6093 return i18n("Producer");
6094 break;
6095 case Copyright:
6096 return i18n("Copyright");
6097 break;
6098 case Pages:
6099 return i18n("Pages");
6100 break;
6101 case CreationDate:
6102 return i18n("Created");
6103 break;
6104 case ModificationDate:
6105 return i18n("Modified");
6106 break;
6107 case MimeType:
6108 return i18n("MIME Type");
6109 break;
6110 case Category:
6111 return i18n("Category");
6112 break;
6113 case Keywords:
6114 return i18n("Keywords");
6115 break;
6116 case FilePath:
6117 return i18n("File Path");
6118 break;
6119 case DocumentSize:
6120 return i18n("File Size");
6121 break;
6122 case PagesSize:
6123 return i18n("Page Size");
6124 break;
6125 default:
6126 return QString();
6127 break;
6128 }
6129}
6130
6132{
6133 QString title = getKeyTitle(getKeyFromString(key));
6134 if (title.isEmpty()) {
6135 title = d->titles[key];
6136 }
6137 return title;
6138}
6139
6140/** DocumentSynopsis **/
6141
6143 : QDomDocument(QStringLiteral("DocumentSynopsis"))
6144{
6145 // void implementation, only subclassed for naming
6146}
6147
6149 : QDomDocument(document)
6150{
6151}
6152
6153/** EmbeddedFile **/
6154
6158
6162
6164 : pageNumber(page)
6165 , rect(rectangle)
6166{
6167}
6168
6169/** NewSignatureData **/
6170
6171struct Okular::NewSignatureDataPrivate {
6172 NewSignatureDataPrivate() = default;
6173
6174 QString certNickname;
6175 QString certSubjectCommonName;
6176 QString password;
6177 QString documentPassword;
6178 QString location;
6179 QString reason;
6180 QString backgroundImagePath;
6181 double fontSize = 10;
6182 double leftFontSize = 20;
6183 int page = -1;
6184 NormalizedRect boundingRectangle;
6185};
6186
6187NewSignatureData::NewSignatureData()
6188 : d(new NewSignatureDataPrivate())
6189{
6190}
6191
6192NewSignatureData::~NewSignatureData()
6193{
6194 delete d;
6195}
6196
6197QString NewSignatureData::certNickname() const
6198{
6199 return d->certNickname;
6200}
6201
6202void NewSignatureData::setCertNickname(const QString &certNickname)
6203{
6204 d->certNickname = certNickname;
6205}
6206
6207QString NewSignatureData::certSubjectCommonName() const
6208{
6209 return d->certSubjectCommonName;
6210}
6211
6212void NewSignatureData::setCertSubjectCommonName(const QString &certSubjectCommonName)
6213{
6214 d->certSubjectCommonName = certSubjectCommonName;
6215}
6216
6217QString NewSignatureData::password() const
6218{
6219 return d->password;
6220}
6221
6222void NewSignatureData::setPassword(const QString &password)
6223{
6224 d->password = password;
6225}
6226
6227int NewSignatureData::page() const
6228{
6229 return d->page;
6230}
6231
6232void NewSignatureData::setPage(int page)
6233{
6234 d->page = page;
6235}
6236
6237NormalizedRect NewSignatureData::boundingRectangle() const
6238{
6239 return d->boundingRectangle;
6240}
6241
6242void NewSignatureData::setBoundingRectangle(const NormalizedRect &rect)
6243{
6244 d->boundingRectangle = rect;
6245}
6246
6248{
6249 return d->documentPassword;
6250}
6251
6253{
6254 d->documentPassword = password;
6255}
6256
6258{
6259 return d->location;
6260}
6261
6263{
6264 d->location = location;
6265}
6266
6268{
6269 return d->reason;
6270}
6271
6273{
6274 d->reason = reason;
6275}
6276
6278{
6279 return d->backgroundImagePath;
6280}
6281
6283{
6284 d->backgroundImagePath = path;
6285}
6286
6288{
6289 return d->fontSize;
6290}
6291
6293{
6294 d->fontSize = fontSize;
6295}
6296
6298{
6299 return d->leftFontSize;
6300}
6301
6303{
6304 d->leftFontSize = fontSize;
6305}
6306
6307#undef foreachObserver
6308#undef foreachObserverD
6309
6310#include "document.moc"
6311
6312/* 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)
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
void setLeftFontSize(double fontSize)
QString backgroundImagePath() const
double leftFontSize() const
double fontSize() const
void setBackgroundImagePath(const QString &path)
void setFontSize(double fontSize)
QString location() const
NormalizedPoint is a helper class which stores the coordinates of a normalized point.
Definition area.h:117
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition area.h:189
double bottom
The normalized bottom coordinate.
Definition area.h:435
double right
The normalized right coordinate.
Definition area.h:430
double height() const
Definition area.h:412
double left
The normalized left coordinate.
Definition area.h:420
double width() const
Definition area.h:406
QRect geometry(int xScale, int yScale) const
Returns the rectangle mapped to a reference area of xScale x yScale.
Definition area.cpp:232
double top
The normalized top coordinate.
Definition area.h:425
bool isNull() const
Returns whether this normalized rectangle is a null normalized rect.
Definition area.cpp:155
An area with normalized coordinates that contains a reference to an object.
Definition area.h:458
@ Action
An action.
Definition area.h:464
A small class that represents the size of a page.
Definition pagesize.h:24
Collector for all the data belonging to a page.
Definition page.h:48
void deletePixmap(DocumentObserver *observer)
Deletes the pixmap for the given observer.
Definition page.cpp:753
double height() const
Returns the height of the page.
Definition page.cpp:185
bool hasPixmap(DocumentObserver *observer, int width=-1, int height=-1, const NormalizedRect &rect=NormalizedRect()) const
Returns whether the page of size width x height has a pixmap in the region given by rect for the give...
Definition page.cpp:219
double width() const
Returns the width of the page.
Definition page.cpp:180
void setPageSize(DocumentObserver *observer, int width, int height)
Sets the size of the page (in screen pixels) if there is a TilesManager.
Definition page.cpp:248
Rotation rotation() const
Returns the rotation of the page as defined by the user.
Definition page.cpp:170
Describes a pixmap type request.
Definition generator.h:645
bool preload() const
Returns whether the generation request is for a page that is not important i.e.
void setTile(bool tile)
Sets whether the generator should render only the given normalized rect or the entire page.
bool asynchronous() const
Returns whether the generation should be done synchronous or asynchronous.
bool shouldAbortRender() const
Should the request be aborted if possible?
void setPartialUpdatesWanted(bool partialUpdatesWanted)
Sets whether the request should report back updates if possible.
int width() const
Returns the page width of the requested pixmap.
int height() const
Returns the page height of the requested pixmap.
int priority() const
Returns the priority (less it better, 0 is maximum) of the request.
const NormalizedRect & normalizedRect() const
Returns the normalized region of the page to request.
bool partialUpdatesWanted() const
Should the request report back updates if possible?
int pageNumber() const
Returns the page number of the request.
void setNormalizedRect(const NormalizedRect &rect)
Sets the region of the page to request.
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
bool isTile() const
Returns whether the generator should render just the region given by normalizedRect() or the entire p...
DocumentObserver * observer() const
Returns the observer of the request.
Abstract interface for advanced printing control.
virtual QWidget * printConfigurationWidget() const =0
Builds and returns a new printing configuration widget.
This is a list of NormalizedRect, to describe an area consisting of multiple rectangles using normali...
Definition area.h:927
The Rendition action executes an operation on a video or executes some JavaScript code on activation.
Definition action.h:523
ScriptType scriptType() const
Returns the type of script.
Definition action.cpp:578
QString script() const
Returns the script code.
Definition action.cpp:584
Abstract interface for saving.
virtual AnnotationProxy * annotationProxy() const =0
Returns the annotation proxy.
virtual bool save(const QString &fileName, SaveOptions options, QString *errorText)=0
Save to the specified fileName with the specified options.
virtual bool supportsOption(SaveOption option) const =0
Query for the supported saving options.
@ SaveChanges
The possibility to save with the current changes to the document.
The Script action executes a Script code.
Definition action.h:423
ScriptType scriptType() const
Returns the type of action.
Definition action.cpp:441
QString script() const
Returns the code.
Definition action.cpp:447
The Sound action plays a sound on activation.
Definition action.h:359
Okular::Sound * sound() const
Returns the sound object which contains the sound data.
Definition action.cpp:394
This class describes the object rectangle for a source reference.
Definition area.h:599
Defines a source reference.
int column() const
Returns the column of the position in the source file.
int row() const
Returns the row of the position in the source file.
QString fileName() const
Returns the filename of the source.
This class represents a rectangular portion of a page.
Definition tile.h:23
NormalizedRect rect() const
Location of the tile.
bool isValid() const
True if the pixmap is available and updated.
static QSizeF realDpi(const QWindow *windowOnScreen)
Return the real DPI of the display containing given window.
Definition utils.cpp:47
View on the document.
Definition view.h:30
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)
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('%'))
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
QString name(StandardAction id)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
KGuiItem forward(BidiMode useBidi=IgnoreRTL)
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 Fri Dec 20 2024 11:47:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.