12 #include "document_p.h"
13 #include "documentcommands_p.h"
18 #define _WIN32_WINNT 0x0500
20 #elif defined(Q_OS_FREEBSD)
23 #include <sys/types.h>
24 #include <sys/sysctl.h>
26 #include <vm/vm_param.h>
30 #include <QApplication>
31 #include <QDesktopServices>
37 #include <QMimeDatabase>
39 #include <QPrintDialog>
40 #include <QRegularExpression>
43 #include <QStandardPaths>
44 #include <QTemporaryFile>
45 #include <QTextStream>
47 #include <QUndoCommand>
49 #include <QtAlgorithms>
50 #include <private/qstringiterator_p.h>
52 #include <KApplicationTrader>
53 #include <KAuthorized>
54 #include <KConfigDialog>
57 #include <KLocalizedString>
58 #include <KMacroExpander>
59 #include <KPluginMetaData>
63 #include <Kdelibs4Migration>
68 #include "annotations.h"
69 #include "annotations_p.h"
70 #include "audioplayer.h"
71 #include "audioplayer_p.h"
72 #include "bookmarkmanager.h"
73 #include "chooseenginedialog_p.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"
85 #include "pagecontroller_p.h"
86 #include "script/event_p.h"
88 #include "settings_core.h"
89 #include "sourcereference.h"
90 #include "sourcereference_p.h"
91 #include "texteditors_p.h"
93 #include "tilesmanager_p.h"
99 #include <config-okular.h>
107 struct AllocatedPixmap {
131 struct RunningSearch {
141 bool cachedViewportMove : 1;
142 bool isCurrentlySearching : 1;
147 #define foreachObserver(cmd) \
149 QSet<DocumentObserver *>::const_iterator it = d->m_observers.constBegin(), end = d->m_observers.constEnd(); \
150 for (; it != end; ++it) { \
155 #define foreachObserverD(cmd) \
157 QSet<DocumentObserver *>::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd(); \
158 for (; it != end; ++it) { \
163 #define OKULAR_HISTORY_MAXSTEPS 100
164 #define OKULAR_HISTORY_SAVEDSTEPS 10
167 constexpr
int kMemCheckTime = 2000;
170 constexpr
int kFreeMemCacheTimeout = kMemCheckTime - 100;
174 QString DocumentPrivate::pagesSizeString()
const
178 QSizeF size = m_parent->allPagesSize();
181 return localizedSize(size);
189 for (
int i = 0; i < m_pagesVector.count(); ++i) {
190 const Page *p = m_pagesVector.at(i);
192 pageSizeFrequencies[sizeString] = pageSizeFrequencies.
value(sizeString, 0) + 1;
196 int largestFrequencySeen = 0;
199 while (i != pageSizeFrequencies.
constEnd()) {
200 if (i.value() > largestFrequencySeen) {
201 largestFrequencySeen = i.value();
202 mostCommonPageSize = i.key();
206 QString finalText =
i18nc(
"@info %1 is a page size",
"Most pages are %1.", mostCommonPageSize);
217 QString DocumentPrivate::namePaperSize(
double inchesWidth,
double inchesHeight)
const
221 const QSize pointsSize(inchesWidth * 72.0, inchesHeight * 72.0);
227 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Portrait %1", paperName);
229 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Landscape %1", paperName);
233 QString DocumentPrivate::localizedSize(
const QSizeF size)
const
235 double inchesWidth = 0, inchesHeight = 0;
236 switch (m_generator->pagesSizeMetric()) {
238 inchesWidth = size.
width() / 72.0;
239 inchesHeight = size.
height() / 72.0;
243 const QSizeF dpi = m_generator->dpi();
252 return i18nc(
"%1 is width, %2 is height, %3 is paper size name",
"%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight));
254 return i18nc(
"%1 is width, %2 is height, %3 is paper size name",
"%1 x %2 mm (%3)",
QString::number(inchesWidth * 25.4,
'd', 0),
QString::number(inchesHeight * 25.4,
'd', 0), namePaperSize(inchesWidth, inchesHeight));
258 qulonglong DocumentPrivate::calculateMemoryToFree()
261 qulonglong clipValue = 0;
262 qulonglong memoryToFree = 0;
264 switch (SettingsCore::memoryLevel()) {
265 case SettingsCore::EnumMemoryLevel::Low:
266 memoryToFree = m_allocatedPixmapsTotalMemory;
269 case SettingsCore::EnumMemoryLevel::Normal: {
270 qulonglong thirdTotalMemory = getTotalMemory() / 3;
271 qulonglong freeMemory = getFreeMemory();
272 if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) {
273 memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory;
275 if (m_allocatedPixmapsTotalMemory > freeMemory) {
276 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
280 case SettingsCore::EnumMemoryLevel::Aggressive: {
281 qulonglong freeMemory = getFreeMemory();
282 if (m_allocatedPixmapsTotalMemory > freeMemory) {
283 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
286 case SettingsCore::EnumMemoryLevel::Greedy: {
288 qulonglong freeMemory = getFreeMemory(&freeSwap);
289 const qulonglong memoryLimit = qMin(qMax(freeMemory, getTotalMemory() / 2), freeMemory + freeSwap);
290 if (m_allocatedPixmapsTotalMemory > memoryLimit) {
291 clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2;
296 if (clipValue > memoryToFree) {
297 memoryToFree = clipValue;
303 void DocumentPrivate::cleanupPixmapMemory()
305 cleanupPixmapMemory(calculateMemoryToFree());
308 void DocumentPrivate::cleanupPixmapMemory(qulonglong memoryToFree)
310 if (memoryToFree < 1) {
314 const int currentViewportPage = (*m_viewportIterator).pageNumber;
319 for (; vIt != vEnd; ++vIt) {
320 visibleRects.
insert((*vIt)->pageNumber, (*vIt));
325 while (memoryToFree > 0) {
326 AllocatedPixmap *p = searchLowestPriorityPixmap(
true,
true);
331 qCDebug(OkularCoreDebug).nospace() <<
"Evicting cache pixmap observer=" << p->observer <<
" page=" << p->page;
335 m_allocatedPixmapsTotalMemory -= p->memory;
337 if (p->memory > memoryToFree) {
340 memoryToFree -= p->memory;
344 m_pagesVector.at(p->page)->deletePixmap(p->observer);
353 std::list<AllocatedPixmap *> pixmapsToKeep;
354 while (memoryToFree > 0) {
357 AllocatedPixmap *p = searchLowestPriorityPixmap(
false,
true, observer);
364 TilesManager *tilesManager = m_pagesVector.at(p->page)->d->tilesManager(observer);
365 if (tilesManager && tilesManager->totalMemory() > 0) {
366 qulonglong memoryDiff = p->memory;
368 if (visibleRects.
contains(p->page)) {
369 visibleRect = visibleRects[p->page]->rect;
373 tilesManager->cleanupPixmapMemory(memoryToFree, visibleRect, currentViewportPage);
375 p->memory = tilesManager->totalMemory();
376 memoryDiff -= p->memory;
377 memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0;
378 m_allocatedPixmapsTotalMemory -= memoryDiff;
381 pixmapsToKeep.push_back(p);
386 pixmapsToKeep.push_back(p);
390 if (clean_hits == 0) {
395 m_allocatedPixmaps.splice(m_allocatedPixmaps.end(), pixmapsToKeep);
404 AllocatedPixmap *DocumentPrivate::searchLowestPriorityPixmap(
bool unloadableOnly,
bool thenRemoveIt,
DocumentObserver *observer)
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;
412 int maxDistance = -1;
413 while (pIt != pEnd) {
414 const AllocatedPixmap *p = *pIt;
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))) {
420 farthestPixmap = pIt;
427 if (farthestPixmap == pEnd) {
431 AllocatedPixmap *selectedPixmap = *farthestPixmap;
433 m_allocatedPixmaps.erase(farthestPixmap);
435 return selectedPixmap;
438 qulonglong DocumentPrivate::getTotalMemory()
440 static qulonglong cachedValue = 0;
445 #if defined(Q_OS_LINUX)
447 QFile memFile(QStringLiteral(
"/proc/meminfo"));
449 return (cachedValue = 134217728);
454 QString entry = readStream.readLine();
459 return (cachedValue = (Q_UINT64_C(1024) * entry.
section(
QLatin1Char(
' '), -2, -2).toULongLong()));
462 #elif defined(Q_OS_FREEBSD)
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)
471 GlobalMemoryStatusEx(&stat);
473 return (cachedValue =
stat.ullTotalPhys);
475 return (cachedValue = 134217728);
478 qulonglong DocumentPrivate::getFreeMemory(qulonglong *freeSwap)
481 static qulonglong cachedValue = 0;
482 static qulonglong cachedFreeSwap = 0;
484 if (!cacheTimer.hasExpired()) {
486 *freeSwap = cachedFreeSwap;
497 #if defined(Q_OS_LINUX)
499 QFile memFile(QStringLiteral(
"/proc/meminfo"));
506 qulonglong memoryFree = 0;
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};
514 entry = readStream.readLine();
518 for (
int i = 0; i < nElems; ++i) {
526 for (
int i = 0; found && i < nElems; ++i) {
527 found = found && foundValues[i];
534 if (values[4] > memoryFree) {
543 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
546 *freeSwap = (cachedFreeSwap = (Q_UINT64_C(1024) *
values[3]));
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);
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);
564 #elif defined(Q_OS_WIN)
567 GlobalMemoryStatusEx(&stat);
569 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
572 *freeSwap = (cachedFreeSwap =
stat.ullAvailPageFile);
573 return (cachedValue =
stat.ullAvailPhys);
580 bool DocumentPrivate::loadDocumentInfo(LoadDocumentInfoFlags loadWhat)
585 if (m_xmlFileName.isEmpty()) {
589 QFile infoFile(m_xmlFileName);
590 return loadDocumentInfo(infoFile, loadWhat);
593 bool DocumentPrivate::loadDocumentInfo(
QFile &infoFile, LoadDocumentInfoFlags loadWhat)
601 if (!doc.setContent(&infoFile)) {
602 qCDebug(OkularCoreDebug) <<
"Can't load XML pair! Check for broken xml.";
614 bool loadedAnything =
false;
622 if (catName ==
QLatin1String(
"pageList") && (loadWhat & LoadPageInfo)) {
626 if (pageElement.
hasAttribute(QStringLiteral(
"number"))) {
629 int pageNumber = pageElement.
attribute(QStringLiteral(
"number")).
toInt(&ok);
632 if (ok && pageNumber >= 0 && pageNumber < (
int)m_pagesVector.count()) {
633 if (m_pagesVector[pageNumber]->d->restoreLocalContents(pageElement)) {
634 loadedAnything =
true;
643 else if (catName ==
QLatin1String(
"generalInfo") && (loadWhat & LoadGeneralInfo)) {
651 m_viewportHistory.
clear();
656 if (historyElement.
hasAttribute(QStringLiteral(
"viewport"))) {
659 loadedAnything =
true;
664 if (m_viewportHistory.empty()) {
665 m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(),
DocumentViewport());
670 int newrotation = !str.
isEmpty() ? (str.
toInt(&ok) % 4) : 0;
671 if (ok && newrotation != 0) {
672 setRotationInternal(newrotation,
false);
673 loadedAnything =
true;
681 for (
View *view : qAsConst(m_views)) {
682 if (view->name() == viewName) {
683 loadViewsInfo(view, viewElement);
684 loadedAnything =
true;
699 return loadedAnything;
710 bool newzoom_ok =
true;
711 const double newzoom = !valueString.
isEmpty() ? valueString.
toDouble(&newzoom_ok) : 1.0;
716 bool newmode_ok =
true;
717 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
723 bool newmode_ok =
true;
724 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
730 bool newmode_ok =
true;
731 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
737 bool newmode_ok =
true;
738 const int newmode = !valueString.
isEmpty() ? valueString.
toInt(&newmode_ok) : 2;
756 if (ok && zoom != 0) {
787 QUrl DocumentPrivate::giveAbsoluteUrl(
const QString &fileName)
const
793 if (!m_url.isValid()) {
800 bool DocumentPrivate::openRelativeFile(
const QString &fileName)
802 const QUrl newUrl = giveAbsoluteUrl(fileName);
807 qCDebug(OkularCoreDebug).nospace() <<
"openRelativeFile: '" << newUrl <<
"'";
809 Q_EMIT m_parent->openUrl(newUrl);
810 return m_url == newUrl;
816 qCDebug(OkularCoreDebug) << service.
fileName();
819 qCWarning(OkularCoreDebug).nospace() <<
"Invalid plugin factory for " << service.
fileName() <<
":" << loader.errorString();
825 GeneratorInfo info(plugin, service);
826 m_loadedGenerators.insert(service.
pluginId(), info);
830 void DocumentPrivate::loadAllGeneratorLibraries()
832 if (m_generatorsLoaded) {
836 loadServiceList(availableGenerators());
838 m_generatorsLoaded =
true;
843 int count = offers.
count();
848 for (
int i = 0; i < count; ++i) {
852 if (!m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd()) {
861 void DocumentPrivate::unloadGenerator(
const GeneratorInfo &info)
863 delete info.generator;
866 void DocumentPrivate::cacheExportFormats()
868 if (m_exportCached) {
873 for (
int i = 0; i < formats.
count(); ++i) {
875 m_exportToText = formats.
at(i);
877 m_exportFormats.append(formats.
at(i));
881 m_exportCached =
true;
886 if (info.configChecked) {
890 info.config = qobject_cast<Okular::ConfigInterface *>(info.generator);
891 info.configChecked =
true;
895 SaveInterface *DocumentPrivate::generatorSave(GeneratorInfo &info)
897 if (info.saveChecked) {
901 info.save = qobject_cast<Okular::SaveInterface *>(info.generator);
902 info.saveChecked =
true;
910 m_walletGenerator =
nullptr;
911 if (genIt != m_loadedGenerators.constEnd()) {
912 m_generator = genIt.value().generator;
914 m_generator = loadGeneratorLibrary(offer);
916 return Document::OpenError;
918 genIt = m_loadedGenerators.constFind(propName);
919 Q_ASSERT(genIt != m_loadedGenerators.constEnd());
921 Q_ASSERT_X(m_generator,
"Document::load()",
"null generator?!");
923 m_generator->d_func()->m_document =
this;
935 qCDebug(OkularCoreDebug) <<
"Output DPI:" << dpi;
936 m_generator->setDPI(dpi);
940 openResult = m_generator->loadDocumentWithPassword(docFile, m_pagesVector, password);
941 }
else if (!filedata.
isEmpty()) {
943 openResult = m_generator->loadDocumentFromDataWithPassword(filedata, m_pagesVector, password);
946 if (!m_tempFile->open()) {
948 m_tempFile =
nullptr;
950 m_tempFile->write(filedata);
951 QString tmpFileName = m_tempFile->fileName();
953 openResult = m_generator->loadDocumentWithPassword(tmpFileName, m_pagesVector, password);
959 if (openResult != Document::OpenSuccess || m_pagesVector.size() <= 0) {
960 m_generator->d_func()->m_document =
nullptr;
968 m_walletGenerator = m_generator;
969 m_generator =
nullptr;
971 qDeleteAll(m_pagesVector);
972 m_pagesVector.clear();
974 m_tempFile =
nullptr;
977 if (openResult == Document::OpenSuccess) {
978 openResult = Document::OpenError;
993 bool DocumentPrivate::savePageDocumentInfo(
QTemporaryFile *infoFile,
int what)
const
995 if (infoFile->
open()) {
998 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral(
"xml"), QStringLiteral(
"version=\"1.0\" encoding=\"utf-8\""));
1000 QDomElement root = doc.createElement(QStringLiteral(
"documentInfo"));
1004 QDomElement pageList = doc.createElement(QStringLiteral(
"pageList"));
1008 for (; pIt != pEnd; ++pIt) {
1009 (*pIt)->d->saveLocalContents(pageList, doc, PageItems(what));
1015 os.setCodec(
"UTF-8");
1025 if (!m_nextDocumentDestination.isEmpty() && m_generator) {
1034 void DocumentPrivate::performAddPageAnnotation(
int page,
Annotation *annotation)
1040 Page *kp = m_pagesVector[page];
1041 if (!m_generator || !kp) {
1046 if (annotation->d_ptr->m_page) {
1059 notifyAnnotationChanges(page);
1063 refreshPixmaps(page);
1067 void DocumentPrivate::performRemovePageAnnotation(
int page,
Annotation *annotation)
1071 bool isExternallyDrawn;
1074 Page *kp = m_pagesVector[page];
1075 if (!m_generator || !kp) {
1080 isExternallyDrawn =
true;
1082 isExternallyDrawn =
false;
1086 if (m_parent->canRemovePageAnnotation(annotation)) {
1095 notifyAnnotationChanges(page);
1097 if (isExternallyDrawn) {
1099 refreshPixmaps(page);
1104 void DocumentPrivate::performModifyPageAnnotation(
int page,
Annotation *annotation,
bool appearanceChanged)
1110 Page *kp = m_pagesVector[page];
1111 if (!m_generator || !kp) {
1121 notifyAnnotationChanges(page);
1126 if (m_annotationBeingModified) {
1129 m_annotationBeingModified =
true;
1132 m_annotationBeingModified =
false;
1136 qCDebug(OkularCoreDebug) <<
"Refreshing Pixmaps";
1137 refreshPixmaps(page);
1141 void DocumentPrivate::performSetAnnotationContents(
const QString &newContents,
Annotation *annot,
int pageNumber)
1143 bool appearanceChanged =
false;
1149 Okular::TextAnnotation *txtann =
static_cast<Okular::TextAnnotation *
>(annot);
1150 if (txtann->textType() == Okular::TextAnnotation::InPlace) {
1151 appearanceChanged =
true;
1157 Okular::LineAnnotation *lineann =
static_cast<Okular::LineAnnotation *
>(annot);
1158 if (lineann->showCaption()) {
1159 appearanceChanged =
true;
1171 performModifyPageAnnotation(pageNumber, annot, appearanceChanged);
1174 void DocumentPrivate::recalculateForms()
1176 const QVariant fco = m_parent->metaData(QStringLiteral(
"FormCalculateOrder"));
1178 for (
int formId : formCalculateOrder) {
1179 for (uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++) {
1180 const Page *p = m_parent->page(pageIdx);
1182 bool pageNeedsRefresh =
false;
1185 if (form->id() == formId) {
1189 std::shared_ptr<Event>
event;
1193 event = Event::createFormCalculateEvent(fft, m_pagesVector[pageIdx]);
1195 m_scripter =
new Scripter(
this);
1199 oldVal = fft->
text();
1202 m_parent->processAction(action);
1205 m_scripter->setEvent(
nullptr);
1206 const QString newVal =
event->value().toString();
1207 if (newVal != oldVal) {
1212 m_parent->processFormatAction(action, fft);
1214 Q_EMIT m_parent->refreshFormWidget(fft);
1215 pageNeedsRefresh =
true;
1220 qWarning() <<
"Form that is part of calculate order doesn't have a calculate action";
1224 if (pageNeedsRefresh) {
1225 refreshPixmaps(p->
number());
1232 void DocumentPrivate::saveDocumentInfo()
const
1234 if (m_xmlFileName.isEmpty()) {
1238 QFile infoFile(m_xmlFileName);
1239 qCDebug(OkularCoreDebug) <<
"About to save document info to" << m_xmlFileName;
1241 qCWarning(OkularCoreDebug) <<
"Failed to open docdata file" << m_xmlFileName;
1246 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral(
"xml"), QStringLiteral(
"version=\"1.0\" encoding=\"utf-8\""));
1248 QDomElement root = doc.createElement(QStringLiteral(
"documentInfo"));
1250 doc.appendChild(root);
1254 if (m_docdataMigrationNeeded) {
1255 QDomElement pageList = doc.createElement(QStringLiteral(
"pageList"));
1263 const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems;
1266 for (; pIt != pEnd; ++pIt) {
1267 (*pIt)->d->saveLocalContents(pageList, doc, saveWhat);
1272 QDomElement generalInfo = doc.createElement(QStringLiteral(
"generalInfo"));
1276 QDomElement rotationNode = doc.createElement(QStringLiteral(
"rotation"));
1281 const auto currentViewportIterator = std::list<DocumentViewport>::const_iterator(m_viewportIterator);
1282 std::list<DocumentViewport>::const_iterator backIterator = currentViewportIterator;
1283 if (backIterator != m_viewportHistory.end()) {
1285 int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
1286 while (backSteps-- && backIterator != m_viewportHistory.begin()) {
1291 QDomElement historyNode = doc.createElement(QStringLiteral(
"history"));
1295 std::list<DocumentViewport>::const_iterator endIt = currentViewportIterator;
1297 while (backIterator != endIt) {
1298 QString name = (backIterator == currentViewportIterator) ? QStringLiteral(
"current") : QStringLiteral(
"oldPage");
1299 QDomElement historyEntry = doc.createElement(name);
1300 historyEntry.
setAttribute(QStringLiteral(
"viewport"), (*backIterator).toString());
1306 QDomElement viewsNode = doc.createElement(QStringLiteral(
"views"));
1308 for (
View *view : qAsConst(m_views)) {
1309 QDomElement viewEntry = doc.createElement(QStringLiteral(
"view"));
1312 saveViewsInfo(view, viewEntry);
1318 os.setCodec(
"UTF-8");
1323 void DocumentPrivate::slotTimedMemoryCheck()
1326 if (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024 * 1024) {
1327 cleanupPixmapMemory();
1331 void DocumentPrivate::sendGeneratorPixmapRequest()
1337 const qulonglong memoryToFree = calculateMemoryToFree();
1338 const int currentViewportPage = (*m_viewportIterator).pageNumber;
1339 int maxDistance = INT_MAX;
1341 AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap(
true);
1342 if (pixmapToReplace) {
1343 maxDistance = qAbs(pixmapToReplace->page - currentViewportPage);
1349 m_pixmapRequestsMutex.lock();
1350 while (!m_pixmapRequestsStack.empty() && !request) {
1353 m_pixmapRequestsStack.pop_back();
1358 TilesManager *tilesManager = r->d->tilesManager();
1360 const QScreen *screen =
nullptr;
1377 m_pixmapRequestsStack.pop_back();
1382 m_pixmapRequestsStack.pop_back();
1384 }
else if (!r->d->mForce && r->
preload() && qAbs(r->
pageNumber() - currentViewportPage) >= maxDistance) {
1385 m_pixmapRequestsStack.pop_back();
1391 m_pixmapRequestsStack.pop_back();
1395 else if (!tilesManager && m_generator->hasFeature(
Generator::TiledRendering) && (
long)r->
width() * (
long)r->
height() > 4L * screenSize && normalizedArea < 0.75 && normalizedArea != 0) {
1397 qCDebug(OkularCoreDebug).nospace() <<
"Start using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1403 tilesManager->setPixmap(pixmap,
NormalizedRect(0, 0, 1, 1),
true );
1411 r->
page()->d->setTilesManager(r->
observer(), tilesManager);
1420 while (tIt != tEnd) {
1422 if (tilesRect.
isNull()) {
1423 tilesRect = tile.
rect();
1425 tilesRect |= tile.
rect();
1438 m_pixmapRequestsStack.pop_back();
1443 else if (tilesManager && (
long)r->
width() * (
long)r->
height() < 3L * screenSize) {
1444 qCDebug(OkularCoreDebug).nospace() <<
"Stop using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1451 }
else if ((
long)requestRect.
width() * (
long)requestRect.
height() > 100L * screenSize && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy)) {
1452 m_pixmapRequestsStack.pop_back();
1453 if (!m_warnedOutOfMemory) {
1454 qCWarning(OkularCoreDebug).nospace() <<
"Running out of memory on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1455 qCWarning(OkularCoreDebug) <<
"this message will be reported only once.";
1456 m_warnedOutOfMemory =
true;
1466 m_pixmapRequestsMutex.unlock();
1471 qulonglong pixmapBytes = 0;
1472 TilesManager *tm = request->d->tilesManager();
1474 pixmapBytes = tm->totalMemory();
1476 pixmapBytes = 4 * request->
width() * request->
height();
1479 if (pixmapBytes > (1024 * 1024)) {
1480 cleanupPixmapMemory(memoryToFree );
1484 if (m_generator->canGeneratePixmap()) {
1485 QRect requestRect = !request->
isTile() ?
QRect(0, 0, request->
width(), request->
height()) : request->normalizedRect().geometry(request->width(), request->height());
1486 qCDebug(OkularCoreDebug).nospace() <<
"sending request observer=" << request->
observer() <<
" " << requestRect.
width() <<
"x" << requestRect.
height() <<
"@" << request->
pageNumber() <<
" async == " << request->
asynchronous()
1487 <<
" isTile == " << request->
isTile();
1488 m_pixmapRequestsStack.remove(request);
1494 if ((
int)m_rotation % 2) {
1510 m_executingPixmapRequests.push_back(request);
1511 m_pixmapRequestsMutex.unlock();
1512 m_generator->generatePixmap(request);
1514 m_pixmapRequestsMutex.unlock();
1520 void DocumentPrivate::rotationFinished(
int page,
Okular::Page *okularPage)
1522 Okular::Page *wantedPage = m_pagesVector.value(page,
nullptr);
1523 if (!wantedPage || wantedPage != okularPage) {
1532 void DocumentPrivate::slotFontReadingProgress(
int page)
1534 Q_EMIT m_parent->fontReadingProgress(page);
1536 if (page >= (
int)m_parent->pages() - 1) {
1537 Q_EMIT m_parent->fontReadingEnded();
1538 m_fontThread =
nullptr;
1539 m_fontsCached =
true;
1546 if (m_fontsCache.indexOf(font) == -1) {
1547 m_fontsCache.append(font);
1549 Q_EMIT m_parent->gotFont(font);
1553 void DocumentPrivate::slotGeneratorConfigChanged()
1560 bool configchanged =
false;
1562 for (; it != itEnd; ++it) {
1566 if (it_changed && (m_generator == it.value().generator)) {
1567 configchanged =
true;
1571 if (configchanged) {
1574 for (; it !=
end; ++it) {
1575 (*it)->deletePixmaps();
1579 qDeleteAll(m_allocatedPixmaps);
1580 m_allocatedPixmaps.clear();
1581 m_allocatedPixmapsTotalMemory = 0;
1588 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.empty() && !m_pagesVector.isEmpty()) {
1589 cleanupPixmapMemory();
1593 void DocumentPrivate::refreshPixmaps(
int pageNumber)
1595 Page *page = m_pagesVector.value(pageNumber,
nullptr);
1602 for (; it != itEnd; ++it) {
1603 const QSize size = (*it).m_pixmap->size();
1605 p->d->mForce =
true;
1606 pixmapsToRequest << p;
1620 TilesManager *tilesManager = page->d->tilesManager(observer);
1622 tilesManager->markDirty();
1624 PixmapRequest *p =
new PixmapRequest(observer, pageNumber, tilesManager->width(), tilesManager->height(), 1 , 1, PixmapRequest::Asynchronous);
1629 for (; vIt != vEnd; ++vIt) {
1630 if ((*vIt)->pageNumber == pageNumber) {
1631 visibleRect = (*vIt)->rect;
1636 if (!visibleRect.
isNull()) {
1639 p->d->mForce =
true;
1650 void DocumentPrivate::_o_configChanged()
1653 calculateMaxTextPages();
1654 while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) {
1655 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
1656 m_pagesVector.at(pageToKick)->setTextPage(
nullptr);
1660 void DocumentPrivate::doContinueDirectionMatchSearch(
void *doContinueDirectionMatchSearchStruct)
1662 DoContinueDirectionMatchSearchStruct *searchStruct =
static_cast<DoContinueDirectionMatchSearchStruct *
>(doContinueDirectionMatchSearchStruct);
1663 RunningSearch *search = m_searches.value(searchStruct->searchID);
1665 if ((m_searchCancelled && !searchStruct->match) || !search) {
1670 search->isCurrentlySearching =
false;
1674 delete searchStruct->pagesToNotify;
1675 delete searchStruct;
1680 bool doContinue =
false;
1682 if (!searchStruct->match) {
1683 const int pageCount = m_pagesVector.count();
1684 if (search->pagesDone < pageCount) {
1686 if (searchStruct->currentPage >= pageCount) {
1687 searchStruct->currentPage = 0;
1688 Q_EMIT m_parent->notice(
i18n(
"Continuing search from beginning"), 3000);
1689 }
else if (searchStruct->currentPage < 0) {
1690 searchStruct->currentPage = pageCount - 1;
1691 Q_EMIT m_parent->notice(
i18n(
"Continuing search from bottom"), 3000);
1698 Page *page = m_pagesVector[searchStruct->currentPage];
1701 m_parent->requestTextPage(page->
number());
1705 searchStruct->match = page->
findText(searchStruct->searchID, search->cachedString, forward ?
FromTop :
FromBottom, search->cachedCaseSensitivity);
1706 if (!searchStruct->match) {
1708 searchStruct->currentPage++;
1710 searchStruct->currentPage--;
1712 search->pagesDone++;
1714 search->pagesDone = 1;
1718 QTimer::singleShot(0, m_parent, [
this, searchStruct] { doContinueDirectionMatchSearch(searchStruct); });
1720 doProcessSearchMatch(searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor);
1721 delete searchStruct;
1725 void DocumentPrivate::doProcessSearchMatch(
RegularAreaRect *match, RunningSearch *search,
QSet<int> *pagesToNotify,
int currentPage,
int searchID,
bool moveViewport,
const QColor &color)
1730 bool foundAMatch =
false;
1732 search->isCurrentlySearching =
false;
1738 search->continueOnPage = currentPage;
1739 search->continueOnMatch = *
match;
1740 search->highlightedPages.insert(currentPage);
1742 m_pagesVector[currentPage]->d->setHighlight(searchID, match, color);
1745 pagesToNotify->
insert(currentPage);
1750 const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible(matchRectWithBuffer, currentPage);
1753 if (moveViewport && !matchRectFullyVisible) {
1755 searchViewport.rePos.enabled =
true;
1756 searchViewport.rePos.normalizedX = (
match->first().left +
match->first().right) / 2.0;
1757 searchViewport.rePos.normalizedY = (
match->first().top +
match->first().bottom) / 2.0;
1758 m_parent->setViewport(searchViewport,
nullptr,
true);
1764 for (
int pageNumber : qAsConst(*pagesToNotify)) {
1776 delete pagesToNotify;
1779 void DocumentPrivate::doContinueAllDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID)
1783 RunningSearch *search = m_searches.value(searchID);
1785 if (m_searchCancelled || !search) {
1791 search->isCurrentlySearching =
false;
1795 for (
const MatchesVector &mv : qAsConst(*pageMatches)) {
1799 delete pagesToNotify;
1803 if (currentPage < m_pagesVector.count()) {
1805 Page *page = m_pagesVector.at(currentPage);
1806 int pageNumber = page->
number();
1810 m_parent->requestTextPage(pageNumber);
1817 lastMatch = page->
findText(searchID, search->cachedString,
NextResult, search->cachedCaseSensitivity, lastMatch);
1819 lastMatch = page->
findText(searchID, search->cachedString,
FromTop, search->cachedCaseSensitivity);
1827 (*pageMatches)[page].
append(lastMatch);
1831 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID] { doContinueAllDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID); });
1836 search->isCurrentlySearching =
false;
1837 bool foundAMatch = pageMatches->
count() != 0;
1841 for (; it != itEnd; ++it) {
1843 it.
key()->d->setHighlight(searchID, match, search->cachedColor);
1846 search->highlightedPages.
insert(it.
key()->number());
1847 pagesToNotify->
insert(it.
key()->number());
1855 for (
int pageNumber : qAsConst(*pagesToNotify)) {
1868 delete pagesToNotify;
1872 void DocumentPrivate::doContinueGooglesDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID,
const QStringList &words)
1877 RunningSearch *search = m_searches.value(searchID);
1879 if (m_searchCancelled || !search) {
1885 search->isCurrentlySearching =
false;
1890 for (
const MatchesVector &mv : qAsConst(*pageMatches)) {
1891 for (
const MatchColor &mc : mv) {
1896 delete pagesToNotify;
1900 const int wordCount = words.
count();
1901 const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
1902 int baseHue, baseSat, baseVal;
1903 search->cachedColor.getHsv(&baseHue, &baseSat, &baseVal);
1905 if (currentPage < m_pagesVector.count()) {
1907 Page *page = m_pagesVector.at(currentPage);
1908 int pageNumber = page->
number();
1912 m_parent->requestTextPage(pageNumber);
1916 bool allMatched = wordCount > 0, anyMatched =
false;
1917 for (
int w = 0; w < wordCount; w++) {
1918 const QString &word = words[w];
1919 int newHue = baseHue - w * hueStep;
1926 bool wordMatched =
false;
1929 lastMatch = page->
findText(searchID, word,
NextResult, search->cachedCaseSensitivity, lastMatch);
1931 lastMatch = page->
findText(searchID, word,
FromTop, search->cachedCaseSensitivity);
1939 (*pageMatches)[page].
append(MatchColor(lastMatch, wordColor));
1942 allMatched = allMatched && wordMatched;
1943 anyMatched = anyMatched || wordMatched;
1948 if (!allMatched && matchAll) {
1950 for (
const MatchColor &mc : matches) {
1953 pageMatches->
remove(page);
1956 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID, words] { doContinueGooglesDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID, words); });
1961 search->isCurrentlySearching =
false;
1962 bool foundAMatch = pageMatches->
count() != 0;
1966 for (; it != itEnd; ++it) {
1967 for (
const MatchColor &mc : it.
value()) {
1968 it.
key()->d->setHighlight(searchID, mc.first, mc.second);
1971 search->highlightedPages.
insert(it.
key()->number());
1972 pagesToNotify->
insert(it.
key()->number());
1981 for (
int pageNumber : qAsConst(*pagesToNotify)) {
1994 delete pagesToNotify;
2002 bool giveDefault = option.
toBool();
2004 if ((SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper) && SettingsCore::changeColors()) {
2005 color = SettingsCore::paperColor();
2006 }
else if (giveDefault) {
2013 switch (SettingsCore::textAntialias()) {
2014 case SettingsCore::EnumTextAntialias::Enabled:
2017 case SettingsCore::EnumTextAntialias::Disabled:
2024 switch (SettingsCore::graphicsAntialias()) {
2025 case SettingsCore::EnumGraphicsAntialias::Enabled:
2028 case SettingsCore::EnumGraphicsAntialias::Disabled:
2035 switch (SettingsCore::textHinting()) {
2036 case SettingsCore::EnumTextHinting::Enabled:
2039 case SettingsCore::EnumTextHinting::Disabled:
2048 bool DocumentPrivate::isNormalizedRectangleFullyVisible(
const Okular::NormalizedRect &rectOfInterest,
int rectPage)
2050 bool rectFullyVisible =
false;
2055 for (; (vIt != vEnd) && !rectFullyVisible; ++vIt) {
2056 if ((*vIt)->pageNumber == rectPage && (*vIt)->rect.contains(rectOfInterest.
left, rectOfInterest.
top) && (*vIt)->rect.contains(rectOfInterest.
right, rectOfInterest.
bottom)) {
2057 rectFullyVisible =
true;
2060 return rectFullyVisible;
2063 struct pdfsyncpoint {
2072 void DocumentPrivate::loadSyncFile(
const QString &filePath)
2081 const QString coreName = ts.readLine();
2083 const QString versionstr = ts.readLine();
2088 if (!
match.hasMatch()) {
2094 int currentpage = -1;
2098 fileStack.
push(coreName + texStr);
2100 const QSizeF dpi = m_generator->dpi();
2103 while (!ts.atEnd()) {
2104 line = ts.readLine();
2106 const int tokenSize = tokens.
count();
2107 if (tokenSize < 1) {
2111 int id = tokens.
at(1).toInt();
2117 pt.row = tokens.
at(2).toInt();
2120 pt.file = fileStack.
top();
2124 currentpage = tokens.
at(1).toInt() - 1;
2127 qCDebug(OkularCoreDebug) <<
"PdfSync: 'p*' line ignored";
2129 int id = tokens.
at(1).toInt();
2131 if (it != points.
end()) {
2132 it->x = tokens.
at(2).toInt();
2133 it->y = tokens.
at(3).toInt();
2134 it->page = currentpage;
2143 fileStack.
push(newfile);
2148 qCDebug(OkularCoreDebug) <<
"PdfSync: going one level down too much";
2151 qCDebug(OkularCoreDebug).nospace() <<
"PdfSync: unknown line format: '" << line <<
"'";
2156 for (
const pdfsyncpoint &pt : qAsConst(points)) {
2158 if (pt.page < 0 || pt.page >= m_pagesVector.size()) {
2163 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()));
2168 for (
int i = 0; i < refRects.size(); ++i) {
2169 if (!refRects.at(i).isEmpty()) {
2170 m_pagesVector[i]->setSourceReferences(refRects.at(i));
2175 void DocumentPrivate::clearAndWaitForRequests()
2177 m_pixmapRequestsMutex.lock();
2178 std::list<PixmapRequest *>::const_iterator sIt = m_pixmapRequestsStack.begin();
2179 std::list<PixmapRequest *>::const_iterator sEnd = m_pixmapRequestsStack.end();
2180 for (; sIt != sEnd; ++sIt) {
2183 m_pixmapRequestsStack.clear();
2184 m_pixmapRequestsMutex.unlock();
2187 bool startEventLoop =
false;
2189 m_pixmapRequestsMutex.lock();
2190 startEventLoop = !m_executingPixmapRequests.empty();
2193 for (
PixmapRequest *executingRequest : qAsConst(m_executingPixmapRequests)) {
2194 executingRequest->d->mShouldAbortRender = 1;
2197 if (m_generator->d_ptr->mTextPageGenerationThread) {
2198 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
2202 m_pixmapRequestsMutex.unlock();
2203 if (startEventLoop) {
2204 m_closingLoop = &loop;
2206 m_closingLoop =
nullptr;
2208 }
while (startEventLoop);
2215 for (uint pageIdx = 0, nPages = m_parent->pages(); pageIdx < nPages; pageIdx++) {
2216 const Page *p = m_parent->
page(pageIdx);
2218 foundPage =
static_cast<int>(pageIdx);
2225 void DocumentPrivate::executeScriptEvent(
const std::shared_ptr<Event> &event,
const Okular::ScriptAction *linkscript)
2228 m_scripter =
new Scripter(
this);
2234 m_scripter->setEvent(
nullptr);
2239 , d(new DocumentPrivate(this))
2241 d->m_widget = widget;
2243 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
DocumentViewport());
2246 connect(SettingsCore::self(), &SettingsCore::configChanged,
this, [
this] { d->_o_configChanged(); });
2251 qRegisterMetaType<Okular::FontInfo>();
2260 for (; viewIt != viewEnd; ++viewIt) {
2262 v->d_func()->document =
nullptr;
2266 delete d->m_bookmarkManager;
2270 for (; it != itEnd; ++it) {
2271 d->unloadGenerator(it.value());
2273 d->m_loadedGenerators.clear();
2279 QString DocumentPrivate::docDataFileName(
const QUrl &url, qint64 document_size)
2286 qCDebug(OkularCoreDebug) <<
"creating docdata folder" << docdataDir;
2291 if (!
QFile::exists(newokularfile) && !QStandardPaths::isTestModeEnabled()) {
2293 static Kdelibs4Migration k4migration;
2294 QString oldfile = k4migration.locateLocal(
"data", QStringLiteral(
"okular/docdata/") + fn);
2296 oldfile = k4migration.locateLocal(
"data", QStringLiteral(
"kpdf/") + fn);
2305 return newokularfile;
2334 for (
const QString &supported : mimetypes) {
2336 if (mimeType == type && !exactMatches.
contains(md)) {
2340 if (
type.inherits(supported) && !offers.
contains(md)) {
2346 if (!exactMatches.
isEmpty()) {
2347 offers = exactMatches;
2355 int offercount = offers.
size();
2356 if (offercount > 1) {
2359 const QString property = QStringLiteral(
"X-KDE-Priority");
2360 return s1.
rawData()[property].toInt() > s2.rawData()[property].toInt();
2362 std::stable_sort(offers.
begin(), offers.
end(), cmp);
2364 if (SettingsCore::chooseGenerators()) {
2366 for (
int i = 0; i < offercount; ++i) {
2367 list << offers.
at(i).pluginId();
2369 ChooseEngineDialog choose(list, type, widget);
2375 hRank = choose.selectedGenerator();
2378 Q_ASSERT(hRank < offers.
size());
2379 return offers.
at(hRank);
2397 bool triedMimeFromFileContent =
false;
2404 d->m_docFileName = docFile;
2406 if (!d->updateMetadataXmlNameAndDocSize()) {
2413 qWarning() <<
"failed to read" << url << filedata;
2422 d->m_docSize = filedata.
size();
2423 triedMimeFromFileContent =
true;
2426 const bool fromFileDescriptor = fd >= 0;
2430 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2431 if (!offer.
isValid() && !triedMimeFromFileContent) {
2433 triedMimeFromFileContent =
true;
2434 if (newmime != mime) {
2436 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2444 if (!newmime.
isDefault() && newmime != mime) {
2446 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2451 d->m_openError =
i18n(
"Can not find a plugin which is able to handle the document being passed.");
2453 qCWarning(OkularCoreDebug).nospace() <<
"No plugin for mimetype '" << mime.
name() <<
"'.";
2458 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2459 if (openResult == OpenError) {
2461 triedOffers << offer;
2462 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2464 while (offer.isValid()) {
2465 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2467 if (openResult == OpenError) {
2468 triedOffers << offer;
2469 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2475 if (openResult == OpenError && !triedMimeFromFileContent) {
2477 triedMimeFromFileContent =
true;
2478 if (newmime != mime) {
2480 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2481 while (offer.isValid()) {
2482 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2484 if (openResult == OpenError) {
2485 triedOffers << offer;
2486 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2494 if (openResult == OpenSuccess) {
2502 if (openResult != OpenSuccess) {
2508 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(docFile).constData(),
nullptr, 1);
2510 d->loadSyncFile(docFile);
2513 d->m_generatorName = offer.
pluginId();
2514 d->m_pageController =
new PageController();
2515 connect(d->m_pageController, &PageController::rotationFinished,
this, [
this](
int p,
Okular::Page *op) { d->rotationFinished(p, op); });
2517 for (
Page *p : qAsConst(d->m_pagesVector)) {
2521 d->m_metadataLoadingCompleted =
false;
2522 d->m_docdataMigrationNeeded =
false;
2525 if (d->m_archiveData) {
2527 d->m_archiveData->metadataFile.fileName();
2528 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2529 d->loadDocumentInfo(LoadGeneralInfo);
2531 if (d->loadDocumentInfo(LoadPageInfo)) {
2532 d->m_docdataMigrationNeeded =
true;
2534 d->loadDocumentInfo(LoadGeneralInfo);
2537 d->m_metadataLoadingCompleted =
true;
2538 d->m_bookmarkManager->setUrl(d->m_url);
2545 if (loadedViewport.
isValid()) {
2547 if (loadedViewport.
pageNumber >= (
int)d->m_pagesVector.size()) {
2548 loadedViewport.
pageNumber = d->m_pagesVector.size() - 1;
2556 if (!d->m_saveBookmarksTimer) {
2557 d->m_saveBookmarksTimer =
new QTimer(
this);
2560 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2563 if (!d->m_memCheckTimer) {
2564 d->m_memCheckTimer =
new QTimer(
this);
2567 d->m_memCheckTimer->start(kMemCheckTime);
2573 d->m_nextDocumentDestination =
QString();
2578 const QStringList docScripts = d->m_generator->metaData(QStringLiteral(
"DocumentScripts"), QStringLiteral(
"JavaScript")).toStringList();
2580 d->m_scripter =
new Scripter(d);
2581 for (
const QString &docscript : docScripts) {
2582 d->m_scripter->execute(
JavaScript, docscript);
2589 bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2593 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2597 m_docSize = fileReadTest.size();
2600 if (m_url.isLocalFile()) {
2601 const QString filePath = docDataFileName(m_url, m_docSize);
2602 qCDebug(OkularCoreDebug) <<
"Metadata file is now:" << filePath;
2603 m_xmlFileName = filePath;
2605 qCDebug(OkularCoreDebug) <<
"Metadata file: disabled";
2614 if (d->m_generator) {
2626 if (!d->m_generator) {
2632 delete d->m_pageController;
2633 d->m_pageController =
nullptr;
2635 delete d->m_scripter;
2636 d->m_scripter =
nullptr;
2639 d->clearAndWaitForRequests();
2641 if (d->m_fontThread) {
2642 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2643 d->m_fontThread->stopExtraction();
2644 d->m_fontThread->wait();
2645 d->m_fontThread =
nullptr;
2652 if (d->m_generator && d->m_pagesVector.size() > 0) {
2653 d->saveDocumentInfo();
2661 for (
Page *p : qAsConst(d->m_pagesVector)) {
2665 const Action *a =
static_cast<const Action *
>(oRect->object());
2666 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2667 if (backendAction) {
2668 d->m_generator->freeOpaqueActionContents(*backendAction);
2676 for (
const Action *a : additionalActions) {
2677 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2678 if (backendAction) {
2679 d->m_generator->freeOpaqueActionContents(*backendAction);
2685 d->m_generator->closeDocument();
2688 if (d->m_synctex_scanner) {
2689 synctex_scanner_free(d->m_synctex_scanner);
2690 d->m_synctex_scanner =
nullptr;
2694 if (d->m_memCheckTimer) {
2695 d->m_memCheckTimer->stop();
2697 if (d->m_saveBookmarksTimer) {
2698 d->m_saveBookmarksTimer->stop();
2701 if (d->m_generator) {
2703 d->m_generator->d_func()->m_document =
nullptr;
2705 disconnect(d->m_generator,
nullptr,
this,
nullptr);
2708 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2710 d->m_generator =
nullptr;
2711 d->m_generatorName =
QString();
2713 d->m_walletGenerator =
nullptr;
2716 delete d->m_tempFile;
2717 d->m_tempFile =
nullptr;
2718 delete d->m_archiveData;
2719 d->m_archiveData =
nullptr;
2721 d->m_exportCached =
false;
2722 d->m_exportFormats.clear();
2724 d->m_fontsCached =
false;
2725 d->m_fontsCache.clear();
2734 for (; pIt != pEnd; ++pIt) {
2737 d->m_pagesVector.clear();
2740 qDeleteAll(d->m_allocatedPixmaps);
2741 d->m_allocatedPixmaps.clear();
2746 for (; rIt != rEnd; ++rIt) {
2749 d->m_searches.clear();
2754 for (; vIt != vEnd; ++vIt) {
2757 d->m_pageRects.clear();
2758 foreachObserver(notifyVisibleRectsChanged());
2762 d->m_viewportHistory.clear();
2764 d->m_viewportIterator = d->m_viewportHistory.begin();
2765 d->m_allocatedPixmapsTotalMemory = 0;
2766 d->m_allocatedTextPagesFifo.clear();
2768 d->m_pageSizes.clear();
2771 d->m_documentInfoAskedKeys.clear();
2775 d->m_undoStack->clear();
2776 d->m_docdataMigrationNeeded =
false;
2778 #if HAVE_MALLOC_TRIM
2788 Q_ASSERT(!d->m_observers.contains(pObserver));
2789 d->m_observers << pObserver;
2792 if (!d->m_pagesVector.isEmpty()) {
2801 if (d->m_observers.contains(pObserver)) {
2804 for (; it != end; ++it) {
2805 (*it)->deletePixmap(pObserver);
2809 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2810 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2811 while (aIt != aEnd) {
2812 AllocatedPixmap *p = *aIt;
2813 if (p->observer == pObserver) {
2814 aIt = d->m_allocatedPixmaps.erase(aIt);
2821 for (
PixmapRequest *executingRequest : qAsConst(d->m_executingPixmapRequests)) {
2822 if (executingRequest->observer() == pObserver) {
2823 d->cancelRenderingBecauseOf(executingRequest,
nullptr);
2828 d->m_observers.remove(pObserver);
2835 bool configchanged =
false;
2836 if (d->m_generator) {
2842 if (configchanged) {
2845 for (; it != end; ++it) {
2846 (*it)->deletePixmaps();
2850 qDeleteAll(d->m_allocatedPixmaps);
2851 d->m_allocatedPixmaps.clear();
2852 d->m_allocatedPixmapsTotalMemory = 0;
2859 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2860 d->cleanupPixmapMemory();
2866 return d->m_generator;
2871 if (d->m_generator) {
2873 return iface ? true :
false;
2881 if (d->m_generator->canSign()) {
2882 return d->m_generator->sign(data, newPath);
2890 return d->m_generator ? d->m_generator->certificateStore() :
nullptr;
2895 d->editorCommandOverride = editCmd;
2900 return d->editorCommandOverride;
2918 if (d->m_generator && !missingKeys.
isEmpty()) {
2919 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2930 const QString pagesSize = d->pagesSizeString();
2940 d->m_documentInfo.d->values.unite(info.d->values);
2941 d->m_documentInfo.d->titles.unite(info.d->titles);
2942 result.d->values.unite(info.d->values);
2943 result.d->titles.unite(info.d->titles);
2945 d->m_documentInfoAskedKeys += keys;
2952 return d->m_generator ? d->m_generator->generateDocumentSynopsis() :
nullptr;
2957 if (!d->m_generator || !d->m_generator->hasFeature(
Generator::FontInfo) || d->m_fontThread) {
2961 if (d->m_fontsCached) {
2965 for (
int i = 0; i < d->m_fontsCache.count(); ++i) {
2973 d->m_fontThread =
new FontExtractionThread(d->m_generator,
pages());
2974 connect(d->m_fontThread, &FontExtractionThread::gotFont,
this, [
this](
const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2975 connect(d->m_fontThread.data(), &FontExtractionThread::progress,
this, [
this](
int p) { d->slotFontReadingProgress(p); });
2977 d->m_fontThread->startExtraction(
true);
2982 if (!d->m_fontThread) {
2986 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2987 d->m_fontThread->stopExtraction();
2988 d->m_fontThread =
nullptr;
2989 d->m_fontsCache.clear();
2999 return d->m_generator ? d->m_generator->canSign() :
false;
3004 return d->m_generator ? d->m_generator->embeddedFiles() :
nullptr;
3009 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) :
nullptr;
3014 return (*d->m_viewportIterator);
3019 return d->m_pageRects;
3026 for (; vIt != vEnd; ++vIt) {
3032 if (o != excludeObserver) {
3033 o->notifyVisibleRectsChanged();
3040 return (*d->m_viewportIterator).pageNumber;
3045 return d->m_pagesVector.size();
3055 if (action ==
Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3062 #if !OKULAR_FORCE_DRM
3068 return d->m_generator ? d->m_generator->isAllowed(action) :
false;
3088 if (d->m_generator) {
3089 if (d->m_pageSizes.isEmpty()) {
3090 d->m_pageSizes = d->m_generator->pageSizes();
3092 return d->m_pageSizes;
3099 if (!d->m_generator) {
3103 d->cacheExportFormats();
3104 return !d->m_exportToText.isNull();
3109 if (!d->m_generator) {
3113 d->cacheExportFormats();
3114 if (d->m_exportToText.isNull()) {
3118 return d->m_generator->exportTo(fileName, d->m_exportToText);
3123 if (!d->m_generator) {
3127 d->cacheExportFormats();
3128 return d->m_exportFormats;
3133 return d->m_generator ? d->m_generator->exportTo(fileName, format) :
false;
3138 return d->m_viewportIterator == d->m_viewportHistory.begin();
3143 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3160 name = reference.
mid(4);
3162 int nameLength = name.
length();
3164 for (i = 0; i < nameLength; ++i) {
3165 if (!name[i].isDigit()) {
3169 lineString = name.
left(i);
3173 lineString = lineString.
trimmed();
3176 int line = lineString.
toInt(&ok);
3183 synctex_node_p node;
3186 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3193 const QSizeF dpi = d->m_generator->dpi();
3196 double px = (synctex_node_visible_h(node) * dpi.
width()) / 72.27;
3197 double py = (synctex_node_visible_v(node) * dpi.
height()) / 72.27;
3208 return d->m_generator ? d->m_generator->metaData(key, option) :
QVariant();
3213 return d->m_rotation;
3218 bool allPagesSameSize =
true;
3220 for (
int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3221 const Page *p = d->m_pagesVector.at(i);
3228 if (allPagesSameSize) {
3237 if (d->m_generator) {
3239 const Page *p = d->m_pagesVector.at(
page);
3272 if (executingRequest.
width() != otherRequest.
width()) {
3277 if (executingRequest.
height() != otherRequest.
height()) {
3282 if (executingRequest.
isTile() != otherRequest.
isTile()) {
3287 if (executingRequest.
isTile()) {
3300 if (!executingRequest->d->mResultImage.isNull()) {
3308 TilesManager *tm = executingRequest->d->tilesManager();
3310 tm->setPixmap(
nullptr, executingRequest->
normalizedRect(),
true );
3313 PagePrivate::PixmapObject
object = executingRequest->
page()->d->m_pixmaps.take(executingRequest->
observer());
3314 delete object.m_pixmap;
3316 if (executingRequest->d->mShouldAbortRender != 0) {
3320 executingRequest->d->mShouldAbortRender = 1;
3322 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->
page()) {
3323 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3340 if (!d->m_pageController) {
3342 qDeleteAll(requests);
3354 Q_ASSERT(request->
observer() == requesterObserver);
3359 d->m_pixmapRequestsMutex.lock();
3360 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3361 while (sIt != sEnd) {
3362 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.
contains((*sIt)->pageNumber()))) {
3365 sIt = d->m_pixmapRequestsStack.erase(sIt);
3374 qCDebug(OkularCoreDebug).nospace() <<
"request observer=" << request->
observer() <<
" " << request->
width() <<
"x" << request->
height() <<
"@" << request->
pageNumber();
3375 if (d->m_pagesVector.value(request->
pageNumber()) ==
nullptr) {
3381 request->d->mPage = d->m_pagesVector.value(request->
pageNumber());
3389 while (tIt != tEnd) {
3390 const Tile &tile = *tIt;
3392 if (tilesRect.
isNull()) {
3393 tilesRect = tile.
rect();
3395 tilesRect |= tile.
rect();
3406 request->d->mPriority = 0;
3412 for (
PixmapRequest *executingRequest : qAsConst(d->m_executingPixmapRequests)) {
3413 bool newRequestsContainExecutingRequestPage =
false;
3414 bool requestCancelled =
false;
3417 newRequestsContainExecutingRequestPage =
true;
3420 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3421 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3426 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->
observer() && !newRequestsContainExecutingRequestPage) {
3427 requestCancelled = d->cancelRenderingBecauseOf(executingRequest,
nullptr);
3430 if (requestCancelled) {
3431 observersPixmapCleared << executingRequest->
observer();
3441 d->m_pixmapRequestsStack.push_back(request);
3444 sIt = d->m_pixmapRequestsStack.begin();
3445 sEnd = d->m_pixmapRequestsStack.end();
3446 while (sIt != sEnd && (*sIt)->priority() > request->
priority()) {
3449 d->m_pixmapRequestsStack.insert(sIt, request);
3452 d->m_pixmapRequestsMutex.unlock();
3459 d->sendGeneratorPixmapRequest();
3468 Page *kp = d->m_pagesVector[pageNumber];
3469 if (!d->m_generator || !kp) {
3475 d->m_generator->generateTextPage(kp);
3478 void DocumentPrivate::notifyAnnotationChanges(
int page)
3483 void DocumentPrivate::notifyFormChanges(
int )
3493 annotation->d_ptr->baseTransform(t.
inverted());
3495 d->m_undoStack->push(uc);
3512 switch (annotation->
subType()) {
3527 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3528 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3529 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3537 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3538 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3539 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3542 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3544 d->m_undoStack->push(uc);
3545 d->m_prevPropsOfAnnotBeingModified.clear();
3551 QUndoCommand *uc =
new Okular::TranslateAnnotationCommand(d, annotation,
page, delta, complete);
3552 d->m_undoStack->push(uc);
3558 QUndoCommand *uc =
new Okular::AdjustAnnotationCommand(d, annotation,
page, delta1, delta2, complete);
3559 d->m_undoStack->push(uc);
3565 QUndoCommand *uc =
new EditAnnotationContentsCommand(d, annotation,
page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3566 d->m_undoStack->push(uc);
3579 switch (annotation->
subType()) {
3596 d->m_undoStack->push(uc);
3601 d->m_undoStack->beginMacro(
i18nc(
"remove a collection of annotations from the page",
"remove annotations"));
3604 d->m_undoStack->push(uc);
3606 d->m_undoStack->endMacro();
3609 bool DocumentPrivate::canAddAnnotationsNatively()
const
3620 bool DocumentPrivate::canModifyExternalAnnotations()
const
3631 bool DocumentPrivate::canRemoveExternalAnnotations()
const
3645 if (!d->m_generator || !kp) {
3651 kp->d->setTextSelections(rect, color);
3653 kp->d->deleteTextSelections();
3662 return d->m_undoStack->canUndo();
3667 return d->m_undoStack->canRedo();
3703 const int oldPageNumber = oldViewport.
pageNumber;
3711 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3714 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3715 d->m_viewportHistory.pop_front();
3719 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
viewport);
3722 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3724 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3728 if (o != excludeObserver) {
3729 o->notifyViewportChanged(smoothMove);
3732 if (currentPageChanged) {
3733 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3743 }
else if (
page > (
int)d->m_pagesVector.count()) {
3744 page = d->m_pagesVector.count() - 1;
3761 if (o != excludeObserver) {
3762 o->notifyZoom(factor);
3770 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3771 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3774 --d->m_viewportIterator;
3775 foreachObserver(notifyViewportChanged(
true));
3777 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3778 if (oldViewportPage != currentViewportPage)
3779 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3786 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3788 if (nextIterator != d->m_viewportHistory.end()) {
3789 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3792 ++d->m_viewportIterator;
3793 foreachObserver(notifyViewportChanged(
true));
3795 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3796 if (oldViewportPage != currentViewportPage)
3797 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3803 d->m_nextDocumentViewport =
viewport;
3808 d->m_nextDocumentDestination = namedDestination;
3813 d->m_searchCancelled =
false;
3823 if (searchIt == d->m_searches.end()) {
3824 RunningSearch *search =
new RunningSearch();
3825 search->continueOnPage = -1;
3826 searchIt = d->m_searches.insert(searchID, search);
3828 RunningSearch *s = *searchIt;
3831 bool newText = text != s->cachedString;
3832 s->cachedString = text;
3833 s->cachedType = type;
3834 s->cachedCaseSensitivity = caseSensitivity;
3835 s->cachedViewportMove = moveViewport;
3836 s->cachedColor = color;
3837 s->isCurrentlySearching =
true;
3843 *pagesToNotify += s->highlightedPages;
3844 for (
const int pageNumber : qAsConst(s->highlightedPages)) {
3845 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3847 s->highlightedPages.clear();
3857 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3864 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3865 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3866 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3872 if (lastPage && lastPage->number() == s->continueOnPage) {
3874 match = lastPage->findText(searchID, text, forward ?
FromTop :
FromBottom, caseSensitivity);
3876 match = lastPage->findText(searchID, text, forward ?
NextResult :
PreviousResult, caseSensitivity, &s->continueOnMatch);
3888 s->pagesDone = pagesDone;
3890 DoContinueDirectionMatchSearchStruct *searchStruct =
new DoContinueDirectionMatchSearchStruct();
3891 searchStruct->pagesToNotify = pagesToNotify;
3892 searchStruct->match = match;
3894 searchStruct->searchID = searchID;
3896 QTimer::singleShot(0,
this, [
this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3904 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3912 if (it == d->m_searches.constEnd()) {
3918 RunningSearch *p = *it;
3919 if (!p->isCurrentlySearching) {
3920 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3928 if (it == d->m_searches.constEnd()) {
3934 RunningSearch *p = *it;
3935 if (!p->isCurrentlySearching) {
3936 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3943 if (!d->m_generator) {
3949 if (searchIt == d->m_searches.end()) {
3954 RunningSearch *s = *searchIt;
3957 for (
const int pageNumber : qAsConst(s->highlightedPages)) {
3958 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3963 foreachObserver(notifySetup(d->m_pagesVector, 0));
3966 d->m_searches.erase(searchIt);
3972 d->m_searchCancelled =
true;
3977 d->m_undoStack->undo();
3982 d->m_undoStack->redo();
3987 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->
text(), prevCursorPos, prevAnchorPos);
3988 d->m_undoStack->push(uc);
3994 QUndoCommand *uc =
new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
3995 d->m_undoStack->push(uc);
4007 QUndoCommand *uc =
new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4008 d->m_undoStack->push(uc);
4013 QUndoCommand *uc =
new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4014 d->m_undoStack->push(uc);
4019 const int numOfPages =
pages();
4021 d->refreshPixmaps(i);
4023 for (
int i =
currentPage() + 1; i < numOfPages; i++) {
4024 d->refreshPixmaps(i);
4030 return d->m_bookmarkManager;
4036 uint docPages =
pages();
4039 for (uint i = 0; i < docPages; i++) {
4052 uint docPages =
pages();
4056 for (uint i = 0; i < docPages; ++i) {
4066 }
else if (startId >= 0 && endId >= 0) {
4071 if (endId - startId > 0) {
4072 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4080 if (startId >= 0 && endId >= 0) {
4085 if (endId - startId > 0) {
4086 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4094 struct ExecuteNextActionsHelper :
public QObject {
4107 ExecuteNextActionsHelper executeNextActions;
4126 if (go->
isExternal() && !d->openRelativeFile(filename)) {
4127 qCWarning(OkularCoreDebug).nospace() <<
"Action: Error opening '" << filename <<
"'.";
4132 if (!nextViewport.
isValid()) {
4138 d->m_nextDocumentDestination =
QString();
4147 d->openRelativeFile(fileName);
4153 QUrl url = d->giveAbsoluteUrl(fileName);
4166 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4172 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4183 Q_EMIT error(
i18n(
"No application found for opening file of mimetype %1.", mime.
name()), -1);
4194 if ((*d->m_viewportIterator).pageNumber > 0) {
4199 if ((*d->m_viewportIterator).pageNumber < (
int)d->m_pagesVector.count() - 1) {
4242 int lilyRow = 0, lilyCol = 0;
4246 }
else if (extractLilyPondSourceReference(browse->
url(), &lilySource, &lilyRow, &lilyCol)) {
4250 const QUrl url = browse->
url();
4254 d->openRelativeFile(url.
fileName());
4259 if (d->m_url.isValid()) {
4262 KRun *r =
new KRun(realUrl, d->m_widget);
4275 if (!d->m_scripter) {
4276 d->m_scripter =
new Scripter(d);
4287 if (!d->m_scripter) {
4288 d->m_scripter =
new Scripter(d);
4290 d->m_scripter->execute(linkrendition->
scriptType(), linkrendition->
script());
4296 d->m_generator->opaqueAction(
static_cast<const BackendOpaqueAction *
>(action));
4300 if (executeNextActions.b) {
4302 for (
const Action *a : nextActions) {
4311 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for formatting.";
4316 int foundPage = d->findFieldPageNumber(fft);
4318 if (foundPage == -1) {
4319 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4325 std::shared_ptr<Event>
event = Event::createFormatEvent(fft, d->m_pagesVector[foundPage]);
4329 d->executeScriptEvent(
event, linkscript);
4331 const QString formattedText =
event->value().toString();
4332 if (formattedText != unformattedText) {
4338 d->refreshPixmaps(foundPage);
4341 fft->
setText(unformattedText);
4348 d->refreshPixmaps(foundPage);
4356 QStringIterator oldIt(oldVal);
4357 QStringIterator newIt(newVal);
4359 while (oldIt.hasNext() && newIt.hasNext()) {
4360 QChar oldToken = oldIt.next();
4361 QChar newToken = newIt.next();
4363 if (oldToken != newToken) {
4369 while (newIt.hasNext()) {
4370 diff += newIt.next();
4378 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4382 int foundPage = d->findFieldPageNumber(fft);
4384 if (foundPage == -1) {
4385 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4389 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(fft, d->m_pagesVector[foundPage]);
4390 event->setChange(DocumentPrivate::diff(fft->
text(), newValue.
toString()));
4394 d->executeScriptEvent(
event, linkscript);
4396 if (
event->returnCode()) {
4406 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4410 int foundPage = d->findFieldPageNumber(fft);
4412 if (foundPage == -1) {
4413 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4417 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(fft, d->m_pagesVector[foundPage]);
4418 event->setWillCommit(
true);
4422 d->executeScriptEvent(
event, linkscript);
4424 if (
event->returnCode()) {
4439 int foundPage = d->findFieldPageNumber(field);
4441 if (foundPage == -1) {
4442 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4446 std::shared_ptr<Event>
event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4450 d->executeScriptEvent(
event, linkscript);
4460 int foundPage = d->findFieldPageNumber(fft);
4462 if (foundPage == -1) {
4463 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4467 std::shared_ptr<Event>
event = Event::createFormValidateEvent(fft, d->m_pagesVector[foundPage]);
4471 d->executeScriptEvent(
event, linkscript);
4472 returnCode =
event->returnCode();
4481 const QUrl url = d->giveAbsoluteUrl(ref->fileName());
4483 qCDebug(OkularCoreDebug) << url.
url() <<
"is not a local file.";
4489 qCDebug(OkularCoreDebug) <<
"No such file:" << absFileName;
4493 bool handled =
false;
4502 editors = buildEditorsMap();
4506 QString p = d->editorCommandOverride;
4512 p = SettingsCore::externalEditorCommand();
4551 if (!d->m_synctex_scanner) {
4555 const QSizeF dpi = d->m_generator->dpi();
4557 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.
width(), absY * 72. / dpi.
height()) > 0) {
4558 synctex_node_p node;
4560 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4561 int line = synctex_node_line(node);
4562 int col = synctex_node_column(node);
4567 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4577 if (d->m_generator) {
4599 return d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4605 case TemporaryFileOpenPrintError:
4606 return i18n(
"Could not open a temporary file");
4607 case FileConversionPrintError:
4608 return i18n(
"Print conversion failed");
4609 case PrintingProcessCrashPrintError:
4610 return i18n(
"Printing process crashed");
4611 case PrintingProcessStartPrintError:
4612 return i18n(
"Printing process could not start");
4613 case PrintToFilePrintError:
4614 return i18n(
"Printing to file failed");
4615 case InvalidPrinterStatePrintError:
4616 return i18n(
"Printer was in invalid state");
4617 case UnableToFindFilePrintError:
4618 return i18n(
"Unable to find file to print");
4619 case NoFileToPrintError:
4620 return i18n(
"There was no file to print");
4621 case NoBinaryToPrintError:
4622 return i18n(
"Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4623 case InvalidPageSizePrintError:
4624 return i18n(
"The page print size is invalid");
4627 case UnknownPrintError:
4636 if (d->m_generator) {
4637 PrintInterface *iface = qobject_cast<Okular::PrintInterface *>(d->m_generator);
4651 BackendConfigDialog *bcd =
dynamic_cast<BackendConfigDialog *
>(dialog);
4658 d->loadServiceList(offers);
4666 for (; it != itEnd; ++it) {
4667 sortedGenerators.
insert(it.key(), it.value());
4670 bool pagesAdded =
false;
4673 for (; sit != sitEnd; ++sit) {
4679 if (sit.value().generator == d->m_generator) {
4680 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4696 if (md.rawData()[QStringLiteral(
"X-KDE-okularHasInternalSettings")].toBool()) {
4705 if (!d->m_generator) {
4709 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4710 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4711 return genIt.value().metadata;
4716 return DocumentPrivate::configurableGenerators().size();
4726 result << md.mimeTypes();
4732 for (
const QString &mimeName : qAsConst(result)) {
4736 for (
const QMimeType &mimeType : uniqueMimetypes) {
4737 result.
append(mimeType.name());
4741 result << QStringLiteral(
"application/vnd.kde.okular-archive");
4745 std::sort(result.
begin(), result.
end());
4747 d->m_supportedMimeTypes = result;
4754 if (!d->m_generator) {
4763 if (!d->m_generator) {
4772 d->saveDocumentInfo();
4774 d->clearAndWaitForRequests();
4776 qCDebug(OkularCoreDebug) <<
"Swapping backing file to" << newFileName;
4779 if (result != Generator::SwapBackingFileError) {
4784 if (result == Generator::SwapBackingFileReloadInternalData) {
4790 if (newPagesVector.
count() != d->m_pagesVector.count()) {
4795 for (
int i = 0; i < d->m_undoStack->count(); ++i) {
4798 if (OkularUndoCommand *ouc =
dynamic_cast<OkularUndoCommand *
>(uc)) {
4799 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
4801 qWarning() <<
"Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
4805 qWarning() <<
"Document::swapBackingFile: Unhandled undo command" << uc;
4810 for (
int i = 0; i < d->m_pagesVector.count(); ++i) {
4814 Page *oldPage = d->m_pagesVector[i];
4815 Page *newPage = newPagesVector[i];
4816 newPage->d->adoptGeneratedContents(oldPage->d);
4818 pagePrivatesToDelete << oldPage->d;
4819 oldPage->d = newPage->d;
4820 oldPage->d->m_page = oldPage;
4821 oldPage->d->m_doc = d;
4822 newPage->d =
nullptr;
4824 annotationsToDelete << oldPage->m_annotations;
4825 rectsToDelete << oldPage->m_rects;
4826 oldPage->m_annotations = newPage->m_annotations;
4827 oldPage->m_rects = newPage->m_rects;
4829 qDeleteAll(newPagesVector);
4833 d->m_docFileName = newFileName;
4834 d->updateMetadataXmlNameAndDocSize();
4835 d->m_bookmarkManager->setUrl(d->m_url);
4837 d->m_documentInfoAskedKeys.clear();
4839 if (d->m_synctex_scanner) {
4840 synctex_scanner_free(d->m_synctex_scanner);
4841 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(newFileName).constData(),
nullptr, 1);
4843 d->loadSyncFile(newFileName);
4849 qDeleteAll(annotationsToDelete);
4850 qDeleteAll(rectsToDelete);
4851 qDeleteAll(pagePrivatesToDelete);
4861 qCDebug(OkularCoreDebug) <<
"Swapping backing archive to" << newFileName;
4863 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
4868 const QString tempFileName = newArchive->document.fileName();
4873 delete d->m_archiveData;
4874 d->m_archiveData = newArchive;
4883 d->m_undoStack->setClean();
4885 d->m_undoStack->resetClean();
4889 bool Document::isHistoryClean()
const
4891 return d->m_undoStack->isClean();
4896 if (!d->m_generator) {
4899 Q_ASSERT(!d->m_generatorName.isEmpty());
4902 Q_ASSERT(genIt != d->m_loadedGenerators.end());
4921 return d->canAddAnnotationsNatively();
4935 if (!d->m_generator || fileName.
isEmpty()) {
4938 Q_ASSERT(!d->m_generatorName.isEmpty());
4941 Q_ASSERT(genIt != d->m_loadedGenerators.end());
4959 if (viewDoc ==
this) {
4966 d->m_views.insert(view);
4967 view->d_func()->document = d;
4977 if (!viewDoc || viewDoc !=
this) {
4981 view->d_func()->document =
nullptr;
4982 d->m_views.remove(view);
4987 if (d->m_generator) {
4988 return d->m_generator->requestFontData(font);
4994 ArchiveData *DocumentPrivate::unpackDocumentArchive(
const QString &archivePath)
4998 if (!mime.
inherits(QStringLiteral(
"application/vnd.kde.okular-archive"))) {
5002 KZip okularArchive(archivePath);
5012 for (
const QString &entry : mainDirEntries) {
5014 qWarning() <<
"Warning: Found a directory inside" << archivePath <<
" - Okular does not create files like that so it is most probably forged.";
5020 if (!mainEntry || !mainEntry->
isFile()) {
5024 std::unique_ptr<QIODevice> mainEntryDevice(
static_cast<const KZipFileEntry *
>(mainEntry)->createDevice());
5026 if (!doc.
setContent(mainEntryDevice.get())) {
5029 mainEntryDevice.reset();
5044 documentFileName = fileEl.
text();
5046 metadataFileName = fileEl.
text();
5051 if (documentFileName.
isEmpty()) {
5056 if (!docEntry || !docEntry->
isFile()) {
5060 std::unique_ptr<ArchiveData> archiveData(
new ArchiveData());
5065 if (!archiveData->document.open()) {
5069 archiveData->originalFileName = documentFileName;
5072 std::unique_ptr<QIODevice> docEntryDevice(
static_cast<const KZipFileEntry *
>(docEntry)->createDevice());
5073 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5074 archiveData->document.close();
5078 if (metadataEntry && metadataEntry->
isFile()) {
5079 std::unique_ptr<QIODevice> metadataEntryDevice(
static_cast<const KZipFileEntry *
>(metadataEntry)->createDevice());
5081 if (archiveData->metadataFile.open()) {
5082 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5083 archiveData->metadataFile.close();
5087 return archiveData.release();
5092 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5093 if (!d->m_archiveData) {
5097 const QString tempFileName = d->m_archiveData->document.fileName();
5102 if (ret != OpenSuccess) {
5103 delete d->m_archiveData;
5104 d->m_archiveData =
nullptr;
5112 if (!d->m_generator) {
5118 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5123 QString docPath = d->m_docFileName;
5129 KZip okularArchive(fileName);
5138 const KUserGroup userGroup(QStringLiteral(
""));
5141 QDomDocument contentDoc(QStringLiteral(
"OkularArchive"));
5160 bool annotationsSavedNatively =
false;
5161 bool formsSavedNatively =
false;
5163 if (!modifiedFile.
open()) {
5169 modifiedFile.
close();
5173 docPath = modifiedFileName;
5174 annotationsSavedNatively = d->canAddAnnotationsNatively();
5177 qCWarning(OkularCoreDebug) <<
"saveChanges failed: " << errorText;
5178 qCDebug(OkularCoreDebug) <<
"Falling back to saving a copy of the original file";
5182 PageItems saveWhat = None;
5183 if (!annotationsSavedNatively) {
5184 saveWhat |= AnnotationPageItems;
5186 if (!formsSavedNatively) {
5187 saveWhat |= FormFieldPageItems;
5191 if (!d->savePageDocumentInfo(&metadataFile, saveWhat)) {
5196 const mode_t perm = 0100644;
5197 okularArchive.
writeFile(QStringLiteral(
"content.xml"), contentDocXml, perm, user.
loginName(), userGroup.
name());
5202 if (!okularArchive.
close()) {
5211 if (!d->m_archiveData) {
5218 return d->m_archiveData->document.copy(destFileName);
5223 double width, height;
5224 int landscape, portrait;
5231 for (uint i = 0; i <
pages(); i++) {
5236 std::swap(width, height);
5238 if (width > height) {
5249 d->m_annotationEditingEnabled = enable;
5250 foreachObserver(notifySetup(d->m_pagesVector, 0));
5255 if (d->m_generator) {
5256 d->m_generator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5257 }
else if (d->m_walletGenerator) {
5258 d->m_walletGenerator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5264 return d->m_docdataMigrationNeeded;
5269 if (d->m_docdataMigrationNeeded) {
5270 d->m_docdataMigrationNeeded =
false;
5271 foreachObserver(notifySetup(d->m_pagesVector, 0));
5277 return d->m_generator ? d->m_generator->layersModel() :
nullptr;
5282 return d->m_openError;
5287 QFile f(d->m_docFileName);
5289 Q_EMIT error(
i18n(
"Could not open '%1'. File does not exist", d->m_docFileName), -1);
5303 d->refreshPixmaps(pageNumber);
5306 void DocumentPrivate::executeScript(
const QString &
function)
5309 m_scripter =
new Scripter(
this);
5320 if (!m_generator || m_closingLoop) {
5321 m_pixmapRequestsMutex.lock();
5322 m_executingPixmapRequests.remove(req);
5323 m_pixmapRequestsMutex.unlock();
5325 if (m_closingLoop) {
5326 m_closingLoop->exit();
5332 if (!m_generator->canGeneratePixmap()) {
5333 qCDebug(OkularCoreDebug) <<
"requestDone with generator not in READY state.";
5339 std::list<AllocatedPixmap *>::iterator aIt = m_allocatedPixmaps.begin();
5340 std::list<AllocatedPixmap *>::iterator aEnd = m_allocatedPixmaps.end();
5341 for (; aIt != aEnd; ++aIt) {
5343 AllocatedPixmap *p = *aIt;
5344 m_allocatedPixmaps.erase(aIt);
5345 m_allocatedPixmapsTotalMemory -= p->memory;
5352 if (m_observers.contains(observer)) {
5354 qulonglong memoryBytes = 0;
5355 const TilesManager *tm = req->d->tilesManager();
5357 memoryBytes = tm->totalMemory();
5362 AllocatedPixmap *memoryPage =
new AllocatedPixmap(req->
observer(), req->
pageNumber(), memoryBytes);
5363 m_allocatedPixmaps.push_back(memoryPage);
5364 m_allocatedPixmapsTotalMemory += memoryBytes;
5371 qCWarning(OkularCoreDebug) <<
"Receiving a done request for the defunct observer" << observer;
5377 m_pixmapRequestsMutex.lock();
5378 m_executingPixmapRequests.remove(req);
5379 m_pixmapRequestsMutex.unlock();
5383 m_pixmapRequestsMutex.lock();
5384 bool hasPixmaps = !m_pixmapRequestsStack.empty();
5385 m_pixmapRequestsMutex.unlock();
5387 sendGeneratorPixmapRequest();
5391 void DocumentPrivate::setPageBoundingBox(
int page,
const NormalizedRect &boundingBox)
5393 Page *kp = m_pagesVector[page];
5394 if (!m_generator || !kp) {
5412 void DocumentPrivate::calculateMaxTextPages()
5414 int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0));
5415 switch (SettingsCore::memoryLevel()) {
5416 case SettingsCore::EnumMemoryLevel::Low:
5417 m_maxAllocatedTextPages = multipliers * 2;
5420 case SettingsCore::EnumMemoryLevel::Normal:
5421 m_maxAllocatedTextPages = multipliers * 50;
5424 case SettingsCore::EnumMemoryLevel::Aggressive:
5425 m_maxAllocatedTextPages = multipliers * 250;
5428 case SettingsCore::EnumMemoryLevel::Greedy:
5429 m_maxAllocatedTextPages = multipliers * 1250;
5434 void DocumentPrivate::textGenerationDone(
Page *page)
5436 if (!m_pageController) {
5441 if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) {
5442 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
5443 if (pageToKick != page->
number())
5445 m_pagesVector.at(pageToKick)->setTextPage(
nullptr);
5450 m_allocatedTextPagesFifo.append(page->
number());
5455 d->setRotationInternal(r,
true);
5458 void DocumentPrivate::setRotationInternal(
int r,
bool notify)
5461 if (!m_generator || (m_rotation == rotation)) {
5468 for (; pIt != pEnd; ++pIt) {
5469 (*pIt)->d->rotateAt(rotation);
5473 m_generator->rotationChanged(rotation, m_rotation);
5476 m_rotation = rotation;
5482 qCDebug(OkularCoreDebug) <<
"Rotated:" << r;
5491 if (d->m_pageSizes.isEmpty()) {
5492 d->m_pageSizes = d->m_generator->pageSizes();
5494 int sizeid = d->m_pageSizes.indexOf(size);
5502 for (; pIt != pEnd; ++pIt) {
5503 (*pIt)->d->changeSize(size);
5506 qDeleteAll(d->m_allocatedPixmaps);
5507 d->m_allocatedPixmaps.clear();
5508 d->m_allocatedPixmapsTotalMemory = 0;
5510 d->m_generator->pageSizeChanged(size, d->m_pageSize);
5512 d->m_pageSize = size;
5516 qCDebug(OkularCoreDebug) <<
"New PageSize id:" << sizeid;
5525 rePos.enabled =
false;
5526 rePos.normalizedX = 0.5;
5527 rePos.normalizedY = 0.0;
5538 rePos.enabled =
false;
5539 rePos.normalizedX = 0.5;
5540 rePos.normalizedY = 0.0;
5563 rePos.enabled =
true;
5568 rePos.enabled =
true;
5592 if (
rePos.enabled) {
5634 if (!other.
rePos.enabled) {
5638 if (
rePos.normalizedY != other.
rePos.normalizedY) {
5639 return rePos.normalizedY < other.
rePos.normalizedY;
5642 return rePos.normalizedX < other.
rePos.normalizedX;
5648 : d(new DocumentInfoPrivate())
5653 : d(new DocumentInfoPrivate())
5660 if (
this != &info) {
5661 d->values = info.d->values;
5662 d->titles = info.d->titles;
5667 DocumentInfo::~DocumentInfo()
5674 d->values[key] = value;
5675 d->titles[key] = title;
5685 return d->values.keys();
5695 return d->values[key];
5702 return QStringLiteral(
"title");
5705 return QStringLiteral(
"subject");