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>
51 #include <KApplicationTrader>
52 #include <KAuthorized>
53 #include <KConfigDialog>
56 #include <KIO/JobUiDelegateFactory>
57 #include <KIO/OpenUrlJob>
58 #include <KLocalizedString>
59 #include <KMacroExpander>
60 #include <KPluginMetaData>
64 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
65 #include <Kdelibs4Migration>
67 #include <kio_version.h>
72 #include "annotations.h"
73 #include "annotations_p.h"
74 #include "audioplayer.h"
75 #include "bookmarkmanager.h"
76 #include "chooseenginedialog_p.h"
79 #include "generator_p.h"
80 #include "interfaces/configinterface.h"
81 #include "interfaces/guiinterface.h"
82 #include "interfaces/printinterface.h"
83 #include "interfaces/saveinterface.h"
88 #include "pagecontroller_p.h"
89 #include "script/event_p.h"
91 #include "settings_core.h"
92 #include "sourcereference.h"
93 #include "sourcereference_p.h"
94 #include "texteditors_p.h"
96 #include "tilesmanager_p.h"
102 #include <config-okular.h>
110 struct AllocatedPixmap {
134 struct RunningSearch {
144 bool cachedViewportMove : 1;
145 bool isCurrentlySearching : 1;
150 #define foreachObserver(cmd) \
152 QSet<DocumentObserver *>::const_iterator it = d->m_observers.constBegin(), end = d->m_observers.constEnd(); \
153 for (; it != end; ++it) { \
158 #define foreachObserverD(cmd) \
160 QSet<DocumentObserver *>::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd(); \
161 for (; it != end; ++it) { \
166 #define OKULAR_HISTORY_MAXSTEPS 100
167 #define OKULAR_HISTORY_SAVEDSTEPS 10
170 constexpr
int kMemCheckTime = 2000;
173 constexpr
int kFreeMemCacheTimeout = kMemCheckTime - 100;
177 QString DocumentPrivate::pagesSizeString()
const
181 QSizeF size = m_parent->allPagesSize();
184 return localizedSize(size);
192 for (
int i = 0; i < m_pagesVector.count(); ++i) {
193 const Page *p = m_pagesVector.at(i);
195 pageSizeFrequencies[sizeString] = pageSizeFrequencies.
value(sizeString, 0) + 1;
199 int largestFrequencySeen = 0;
202 while (i != pageSizeFrequencies.
constEnd()) {
203 if (i.value() > largestFrequencySeen) {
204 largestFrequencySeen = i.value();
205 mostCommonPageSize = i.key();
209 QString finalText =
i18nc(
"@info %1 is a page size",
"Most pages are %1.", mostCommonPageSize);
220 QString DocumentPrivate::namePaperSize(
double inchesWidth,
double inchesHeight)
const
224 const QSize pointsSize(inchesWidth * 72.0, inchesHeight * 72.0);
230 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Portrait %1", paperName);
232 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Landscape %1", paperName);
236 QString DocumentPrivate::localizedSize(
const QSizeF size)
const
238 double inchesWidth = 0, inchesHeight = 0;
239 switch (m_generator->pagesSizeMetric()) {
241 inchesWidth = size.
width() / 72.0;
242 inchesHeight = size.
height() / 72.0;
246 const QSizeF dpi = m_generator->dpi();
255 return i18nc(
"%1 is width, %2 is height, %3 is paper size name",
"%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight));
257 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));
261 qulonglong DocumentPrivate::calculateMemoryToFree()
264 qulonglong clipValue = 0;
265 qulonglong memoryToFree = 0;
267 switch (SettingsCore::memoryLevel()) {
268 case SettingsCore::EnumMemoryLevel::Low:
269 memoryToFree = m_allocatedPixmapsTotalMemory;
272 case SettingsCore::EnumMemoryLevel::Normal: {
273 qulonglong thirdTotalMemory = getTotalMemory() / 3;
274 qulonglong freeMemory = getFreeMemory();
275 if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) {
276 memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory;
278 if (m_allocatedPixmapsTotalMemory > freeMemory) {
279 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
283 case SettingsCore::EnumMemoryLevel::Aggressive: {
284 qulonglong freeMemory = getFreeMemory();
285 if (m_allocatedPixmapsTotalMemory > freeMemory) {
286 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
289 case SettingsCore::EnumMemoryLevel::Greedy: {
291 qulonglong freeMemory = getFreeMemory(&freeSwap);
292 const qulonglong memoryLimit = qMin(qMax(freeMemory, getTotalMemory() / 2), freeMemory + freeSwap);
293 if (m_allocatedPixmapsTotalMemory > memoryLimit) {
294 clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2;
299 if (clipValue > memoryToFree) {
300 memoryToFree = clipValue;
306 void DocumentPrivate::cleanupPixmapMemory()
308 cleanupPixmapMemory(calculateMemoryToFree());
311 void DocumentPrivate::cleanupPixmapMemory(qulonglong memoryToFree)
313 if (memoryToFree < 1) {
317 const int currentViewportPage = (*m_viewportIterator).pageNumber;
322 for (; vIt != vEnd; ++vIt) {
323 visibleRects.
insert((*vIt)->pageNumber, (*vIt));
328 while (memoryToFree > 0) {
329 AllocatedPixmap *p = searchLowestPriorityPixmap(
true,
true);
334 qCDebug(OkularCoreDebug).nospace() <<
"Evicting cache pixmap observer=" << p->observer <<
" page=" << p->page;
338 m_allocatedPixmapsTotalMemory -= p->memory;
340 if (p->memory > memoryToFree) {
343 memoryToFree -= p->memory;
347 m_pagesVector.at(p->page)->deletePixmap(p->observer);
356 std::list<AllocatedPixmap *> pixmapsToKeep;
357 while (memoryToFree > 0) {
360 AllocatedPixmap *p = searchLowestPriorityPixmap(
false,
true, observer);
367 TilesManager *tilesManager = m_pagesVector.at(p->page)->d->tilesManager(observer);
368 if (tilesManager && tilesManager->totalMemory() > 0) {
369 qulonglong memoryDiff = p->memory;
371 if (visibleRects.
contains(p->page)) {
372 visibleRect = visibleRects[p->page]->rect;
376 tilesManager->cleanupPixmapMemory(memoryToFree, visibleRect, currentViewportPage);
378 p->memory = tilesManager->totalMemory();
379 memoryDiff -= p->memory;
380 memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0;
381 m_allocatedPixmapsTotalMemory -= memoryDiff;
384 pixmapsToKeep.push_back(p);
389 pixmapsToKeep.push_back(p);
393 if (clean_hits == 0) {
398 m_allocatedPixmaps.splice(m_allocatedPixmaps.end(), pixmapsToKeep);
407 AllocatedPixmap *DocumentPrivate::searchLowestPriorityPixmap(
bool unloadableOnly,
bool thenRemoveIt,
DocumentObserver *observer)
409 std::list<AllocatedPixmap *>::iterator pIt = m_allocatedPixmaps.begin();
410 std::list<AllocatedPixmap *>::iterator pEnd = m_allocatedPixmaps.end();
411 std::list<AllocatedPixmap *>::iterator farthestPixmap = pEnd;
412 const int currentViewportPage = (*m_viewportIterator).pageNumber;
415 int maxDistance = -1;
416 while (pIt != pEnd) {
417 const AllocatedPixmap *p = *pIt;
419 if (observer ==
nullptr || p->observer == observer) {
420 const int distance = qAbs(p->page - currentViewportPage);
421 if (maxDistance < distance && (!unloadableOnly || p->observer->canUnloadPixmap(p->page))) {
423 farthestPixmap = pIt;
430 if (farthestPixmap == pEnd) {
434 AllocatedPixmap *selectedPixmap = *farthestPixmap;
436 m_allocatedPixmaps.erase(farthestPixmap);
438 return selectedPixmap;
441 qulonglong DocumentPrivate::getTotalMemory()
443 static qulonglong cachedValue = 0;
448 #if defined(Q_OS_LINUX)
450 QFile memFile(QStringLiteral(
"/proc/meminfo"));
452 return (cachedValue = 134217728);
457 QString entry = readStream.readLine();
462 return (cachedValue = (Q_UINT64_C(1024) * entry.
section(
QLatin1Char(
' '), -2, -2).toULongLong()));
465 #elif defined(Q_OS_FREEBSD)
467 int mib[] = {CTL_HW, HW_PHYSMEM};
468 size_t len =
sizeof(physmem);
469 if (sysctl(mib, 2, &physmem, &len, NULL, 0) == 0)
470 return (cachedValue = physmem);
471 #elif defined(Q_OS_WIN)
474 GlobalMemoryStatusEx(&stat);
476 return (cachedValue =
stat.ullTotalPhys);
478 return (cachedValue = 134217728);
481 qulonglong DocumentPrivate::getFreeMemory(qulonglong *freeSwap)
484 static qulonglong cachedValue = 0;
485 static qulonglong cachedFreeSwap = 0;
487 if (!cacheTimer.hasExpired()) {
489 *freeSwap = cachedFreeSwap;
500 #if defined(Q_OS_LINUX)
502 QFile memFile(QStringLiteral(
"/proc/meminfo"));
509 qulonglong memoryFree = 0;
512 static const int nElems = 5;
513 QString names[nElems] = {QStringLiteral(
"MemFree:"), QStringLiteral(
"Buffers:"), QStringLiteral(
"Cached:"), QStringLiteral(
"SwapFree:"), QStringLiteral(
"SwapTotal:")};
514 qulonglong
values[nElems] = {0, 0, 0, 0, 0};
515 bool foundValues[nElems] = {
false,
false,
false,
false,
false};
517 entry = readStream.readLine();
521 for (
int i = 0; i < nElems; ++i) {
529 for (
int i = 0; found && i < nElems; ++i) {
530 found = found && foundValues[i];
537 if (values[4] > memoryFree) {
546 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
549 *freeSwap = (cachedFreeSwap = (Q_UINT64_C(1024) *
values[3]));
551 return (cachedValue = (Q_UINT64_C(1024) * memoryFree));
552 #elif defined(Q_OS_FREEBSD)
553 qulonglong cache, inact, free, psize;
554 size_t cachelen, inactlen, freelen, psizelen;
555 cachelen =
sizeof(cache);
556 inactlen =
sizeof(inact);
557 freelen =
sizeof(free);
558 psizelen =
sizeof(psize);
560 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 &&
561 sysctlbyname(
"vm.stats.vm.v_free_count", &free, &freelen, NULL, 0) == 0 && sysctlbyname(
"vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0) == 0) {
562 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
563 return (cachedValue = (cache + inact + free) * psize);
567 #elif defined(Q_OS_WIN)
570 GlobalMemoryStatusEx(&stat);
572 cacheTimer.setRemainingTime(kFreeMemCacheTimeout);
575 *freeSwap = (cachedFreeSwap =
stat.ullAvailPageFile);
576 return (cachedValue =
stat.ullAvailPhys);
583 bool DocumentPrivate::loadDocumentInfo(LoadDocumentInfoFlags loadWhat)
588 if (m_xmlFileName.isEmpty()) {
592 QFile infoFile(m_xmlFileName);
593 return loadDocumentInfo(infoFile, loadWhat);
596 bool DocumentPrivate::loadDocumentInfo(
QFile &infoFile, LoadDocumentInfoFlags loadWhat)
604 if (!doc.setContent(&infoFile)) {
605 qCDebug(OkularCoreDebug) <<
"Can't load XML pair! Check for broken xml.";
617 bool loadedAnything =
false;
625 if (catName ==
QLatin1String(
"pageList") && (loadWhat & LoadPageInfo)) {
629 if (pageElement.
hasAttribute(QStringLiteral(
"number"))) {
632 int pageNumber = pageElement.
attribute(QStringLiteral(
"number")).
toInt(&ok);
635 if (ok && pageNumber >= 0 && pageNumber < (
int)m_pagesVector.count()) {
636 if (m_pagesVector[pageNumber]->d->restoreLocalContents(pageElement)) {
637 loadedAnything =
true;
646 else if (catName ==
QLatin1String(
"generalInfo") && (loadWhat & LoadGeneralInfo)) {
654 m_viewportHistory.
clear();
659 if (historyElement.
hasAttribute(QStringLiteral(
"viewport"))) {
662 loadedAnything =
true;
667 if (m_viewportHistory.empty()) {
668 m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(),
DocumentViewport());
673 int newrotation = !str.
isEmpty() ? (str.
toInt(&ok) % 4) : 0;
674 if (ok && newrotation != 0) {
675 setRotationInternal(newrotation,
false);
676 loadedAnything =
true;
684 for (
View *view : qAsConst(m_views)) {
685 if (view->name() == viewName) {
686 loadViewsInfo(view, viewElement);
687 loadedAnything =
true;
702 return loadedAnything;
713 bool newzoom_ok =
true;
714 const double newzoom = !valueString.
isEmpty() ? valueString.
toDouble(&newzoom_ok) : 1.0;
719 bool newmode_ok =
true;
720 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
726 bool newmode_ok =
true;
727 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
733 bool newmode_ok =
true;
734 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
740 bool newmode_ok =
true;
741 const int newmode = !valueString.
isEmpty() ? valueString.
toInt(&newmode_ok) : 2;
759 if (ok && zoom != 0) {
790 QUrl DocumentPrivate::giveAbsoluteUrl(
const QString &fileName)
const
796 if (!m_url.isValid()) {
803 bool DocumentPrivate::openRelativeFile(
const QString &fileName)
805 const QUrl newUrl = giveAbsoluteUrl(fileName);
810 qCDebug(OkularCoreDebug).nospace() <<
"openRelativeFile: '" << newUrl <<
"'";
812 Q_EMIT m_parent->openUrl(newUrl);
813 return m_url == newUrl;
818 const auto result = KPluginFactory::instantiatePlugin<Okular::Generator>(service);
821 qCWarning(OkularCoreDebug).nospace() <<
"Failed to load plugin " << service.
fileName() <<
": " << result.errorText;
825 GeneratorInfo info(result.plugin, service);
826 m_loadedGenerators.insert(service.
pluginId(), info);
827 return result.plugin;
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();
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 QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2293 if (!
QFile::exists(newokularfile) && !QStandardPaths::isTestModeEnabled()) {
2296 QString oldfile = k4migration.
locateLocal(
"data", QStringLiteral(
"okular/docdata/") + fn);
2298 oldfile = k4migration.
locateLocal(
"data", QStringLiteral(
"kpdf/") + fn);
2308 return newokularfile;
2337 for (
const QString &supported : mimetypes) {
2339 if (mimeType == type && !exactMatches.
contains(md)) {
2343 if (
type.inherits(supported) && !offers.
contains(md)) {
2349 if (!exactMatches.
isEmpty()) {
2350 offers = exactMatches;
2358 int offercount = offers.
size();
2359 if (offercount > 1) {
2362 const QString property = QStringLiteral(
"X-KDE-Priority");
2363 return s1.
rawData()[property].toInt() > s2.rawData()[property].toInt();
2365 std::stable_sort(offers.
begin(), offers.
end(), cmp);
2367 if (SettingsCore::chooseGenerators()) {
2369 for (
int i = 0; i < offercount; ++i) {
2370 list << offers.
at(i).pluginId();
2372 ChooseEngineDialog choose(list, type, widget);
2378 hRank = choose.selectedGenerator();
2381 Q_ASSERT(hRank < offers.
size());
2382 return offers.
at(hRank);
2400 bool triedMimeFromFileContent =
false;
2407 d->m_docFileName = docFile;
2409 if (!d->updateMetadataXmlNameAndDocSize()) {
2416 qWarning() <<
"failed to read" << url << filedata;
2425 d->m_docSize = filedata.
size();
2426 triedMimeFromFileContent =
true;
2429 const bool fromFileDescriptor = fd >= 0;
2433 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2434 if (!offer.
isValid() && !triedMimeFromFileContent) {
2436 triedMimeFromFileContent =
true;
2437 if (newmime != mime) {
2439 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2447 if (!newmime.
isDefault() && newmime != mime) {
2449 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2454 d->m_openError =
i18n(
"Can not find a plugin which is able to handle the document being passed.");
2456 qCWarning(OkularCoreDebug).nospace() <<
"No plugin for mimetype '" << mime.
name() <<
"'.";
2461 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2462 if (openResult == OpenError) {
2464 triedOffers << offer;
2465 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2467 while (offer.isValid()) {
2468 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2470 if (openResult == OpenError) {
2471 triedOffers << offer;
2472 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2478 if (openResult == OpenError && !triedMimeFromFileContent) {
2480 triedMimeFromFileContent =
true;
2481 if (newmime != mime) {
2483 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2484 while (offer.isValid()) {
2485 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2487 if (openResult == OpenError) {
2488 triedOffers << offer;
2489 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2497 if (openResult == OpenSuccess) {
2505 if (openResult != OpenSuccess) {
2511 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(docFile).constData(),
nullptr, 1);
2513 d->loadSyncFile(docFile);
2516 d->m_generatorName = offer.
pluginId();
2517 d->m_pageController =
new PageController();
2518 connect(d->m_pageController, &PageController::rotationFinished,
this, [
this](
int p,
Okular::Page *op) { d->rotationFinished(p, op); });
2520 for (
Page *p : qAsConst(d->m_pagesVector)) {
2524 d->m_metadataLoadingCompleted =
false;
2525 d->m_docdataMigrationNeeded =
false;
2528 if (d->m_archiveData) {
2530 d->m_archiveData->metadataFile.fileName();
2531 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2532 d->loadDocumentInfo(LoadGeneralInfo);
2534 if (d->loadDocumentInfo(LoadPageInfo)) {
2535 d->m_docdataMigrationNeeded =
true;
2537 d->loadDocumentInfo(LoadGeneralInfo);
2540 d->m_metadataLoadingCompleted =
true;
2541 d->m_bookmarkManager->setUrl(d->m_url);
2548 if (loadedViewport.
isValid()) {
2550 if (loadedViewport.
pageNumber >= (
int)d->m_pagesVector.size()) {
2551 loadedViewport.
pageNumber = d->m_pagesVector.size() - 1;
2559 if (!d->m_saveBookmarksTimer) {
2560 d->m_saveBookmarksTimer =
new QTimer(
this);
2563 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2566 if (!d->m_memCheckTimer) {
2567 d->m_memCheckTimer =
new QTimer(
this);
2570 d->m_memCheckTimer->start(kMemCheckTime);
2576 d->m_nextDocumentDestination =
QString();
2581 const QStringList docScripts = d->m_generator->metaData(QStringLiteral(
"DocumentScripts"), QStringLiteral(
"JavaScript")).toStringList();
2583 d->m_scripter =
new Scripter(d);
2584 for (
const QString &docscript : docScripts) {
2585 d->m_scripter->execute(
JavaScript, docscript);
2592 bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2596 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2600 m_docSize = fileReadTest.size();
2603 if (m_url.isLocalFile()) {
2604 const QString filePath = docDataFileName(m_url, m_docSize);
2605 qCDebug(OkularCoreDebug) <<
"Metadata file is now:" << filePath;
2606 m_xmlFileName = filePath;
2608 qCDebug(OkularCoreDebug) <<
"Metadata file: disabled";
2617 if (d->m_generator) {
2629 if (!d->m_generator) {
2635 delete d->m_pageController;
2636 d->m_pageController =
nullptr;
2638 delete d->m_scripter;
2639 d->m_scripter =
nullptr;
2642 d->clearAndWaitForRequests();
2644 if (d->m_fontThread) {
2645 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2646 d->m_fontThread->stopExtraction();
2647 d->m_fontThread->wait();
2648 d->m_fontThread =
nullptr;
2655 if (d->m_generator && d->m_pagesVector.size() > 0) {
2656 d->saveDocumentInfo();
2664 for (
Page *p : qAsConst(d->m_pagesVector)) {
2668 const Action *a =
static_cast<const Action *
>(oRect->object());
2669 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2670 if (backendAction) {
2671 d->m_generator->freeOpaqueActionContents(*backendAction);
2679 for (
const Action *a : additionalActions) {
2680 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2681 if (backendAction) {
2682 d->m_generator->freeOpaqueActionContents(*backendAction);
2688 d->m_generator->closeDocument();
2691 if (d->m_synctex_scanner) {
2692 synctex_scanner_free(d->m_synctex_scanner);
2693 d->m_synctex_scanner =
nullptr;
2697 if (d->m_memCheckTimer) {
2698 d->m_memCheckTimer->stop();
2700 if (d->m_saveBookmarksTimer) {
2701 d->m_saveBookmarksTimer->stop();
2704 if (d->m_generator) {
2706 d->m_generator->d_func()->m_document =
nullptr;
2708 disconnect(d->m_generator,
nullptr,
this,
nullptr);
2711 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2713 d->m_generator =
nullptr;
2714 d->m_generatorName =
QString();
2716 d->m_walletGenerator =
nullptr;
2719 delete d->m_tempFile;
2720 d->m_tempFile =
nullptr;
2721 delete d->m_archiveData;
2722 d->m_archiveData =
nullptr;
2724 d->m_exportCached =
false;
2725 d->m_exportFormats.clear();
2727 d->m_fontsCached =
false;
2728 d->m_fontsCache.clear();
2737 for (; pIt != pEnd; ++pIt) {
2740 d->m_pagesVector.clear();
2743 qDeleteAll(d->m_allocatedPixmaps);
2744 d->m_allocatedPixmaps.clear();
2749 for (; rIt != rEnd; ++rIt) {
2752 d->m_searches.clear();
2757 for (; vIt != vEnd; ++vIt) {
2760 d->m_pageRects.clear();
2761 foreachObserver(notifyVisibleRectsChanged());
2765 d->m_viewportHistory.clear();
2767 d->m_viewportIterator = d->m_viewportHistory.begin();
2768 d->m_allocatedPixmapsTotalMemory = 0;
2769 d->m_allocatedTextPagesFifo.clear();
2771 d->m_pageSizes.clear();
2774 d->m_documentInfoAskedKeys.clear();
2778 d->m_undoStack->clear();
2779 d->m_docdataMigrationNeeded =
false;
2781 #if HAVE_MALLOC_TRIM
2791 Q_ASSERT(!d->m_observers.contains(pObserver));
2792 d->m_observers << pObserver;
2795 if (!d->m_pagesVector.isEmpty()) {
2804 if (d->m_observers.contains(pObserver)) {
2807 for (; it != end; ++it) {
2808 (*it)->deletePixmap(pObserver);
2812 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2813 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2814 while (aIt != aEnd) {
2815 AllocatedPixmap *p = *aIt;
2816 if (p->observer == pObserver) {
2817 aIt = d->m_allocatedPixmaps.erase(aIt);
2824 for (
PixmapRequest *executingRequest : qAsConst(d->m_executingPixmapRequests)) {
2825 if (executingRequest->observer() == pObserver) {
2826 d->cancelRenderingBecauseOf(executingRequest,
nullptr);
2831 d->m_observers.remove(pObserver);
2838 bool configchanged =
false;
2839 if (d->m_generator) {
2845 if (configchanged) {
2848 for (; it != end; ++it) {
2849 (*it)->deletePixmaps();
2853 qDeleteAll(d->m_allocatedPixmaps);
2854 d->m_allocatedPixmaps.clear();
2855 d->m_allocatedPixmapsTotalMemory = 0;
2862 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2863 d->cleanupPixmapMemory();
2869 return d->m_generator;
2874 if (d->m_generator) {
2876 return iface ? true :
false;
2884 if (d->m_generator->canSign()) {
2885 return d->m_generator->sign(data, newPath);
2893 return d->m_generator ? d->m_generator->certificateStore() :
nullptr;
2898 d->editorCommandOverride = editCmd;
2903 return d->editorCommandOverride;
2921 if (d->m_generator && !missingKeys.
isEmpty()) {
2922 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2933 const QString pagesSize = d->pagesSizeString();
2943 d->m_documentInfo.d->values.unite(info.d->values);
2944 d->m_documentInfo.d->titles.unite(info.d->titles);
2945 result.d->values.unite(info.d->values);
2946 result.d->titles.unite(info.d->titles);
2948 d->m_documentInfoAskedKeys += keys;
2955 return d->m_generator ? d->m_generator->generateDocumentSynopsis() :
nullptr;
2960 if (!d->m_generator || !d->m_generator->hasFeature(
Generator::FontInfo) || d->m_fontThread) {
2964 if (d->m_fontsCached) {
2968 for (
int i = 0; i < d->m_fontsCache.count(); ++i) {
2976 d->m_fontThread =
new FontExtractionThread(d->m_generator,
pages());
2977 connect(d->m_fontThread, &FontExtractionThread::gotFont,
this, [
this](
const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2978 connect(d->m_fontThread.data(), &FontExtractionThread::progress,
this, [
this](
int p) { d->slotFontReadingProgress(p); });
2980 d->m_fontThread->startExtraction(
true);
2985 if (!d->m_fontThread) {
2989 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2990 d->m_fontThread->stopExtraction();
2991 d->m_fontThread =
nullptr;
2992 d->m_fontsCache.clear();
3002 return d->m_generator ? d->m_generator->canSign() :
false;
3007 return d->m_generator ? d->m_generator->embeddedFiles() :
nullptr;
3012 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) :
nullptr;
3017 return (*d->m_viewportIterator);
3022 return d->m_pageRects;
3029 for (; vIt != vEnd; ++vIt) {
3035 if (o != excludeObserver) {
3036 o->notifyVisibleRectsChanged();
3043 return (*d->m_viewportIterator).pageNumber;
3048 return d->m_pagesVector.size();
3058 if (action ==
Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3065 #if !OKULAR_FORCE_DRM
3071 return d->m_generator ? d->m_generator->isAllowed(action) :
false;
3091 if (d->m_generator) {
3092 if (d->m_pageSizes.isEmpty()) {
3093 d->m_pageSizes = d->m_generator->pageSizes();
3095 return d->m_pageSizes;
3102 if (!d->m_generator) {
3106 d->cacheExportFormats();
3107 return !d->m_exportToText.isNull();
3112 if (!d->m_generator) {
3116 d->cacheExportFormats();
3117 if (d->m_exportToText.isNull()) {
3121 return d->m_generator->exportTo(fileName, d->m_exportToText);
3126 if (!d->m_generator) {
3130 d->cacheExportFormats();
3131 return d->m_exportFormats;
3136 return d->m_generator ? d->m_generator->exportTo(fileName, format) :
false;
3141 return d->m_viewportIterator == d->m_viewportHistory.begin();
3146 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3163 name = reference.
mid(4);
3165 int nameLength = name.
length();
3167 for (i = 0; i < nameLength; ++i) {
3168 if (!name[i].isDigit()) {
3172 lineString = name.
left(i);
3176 lineString = lineString.
trimmed();
3179 int line = lineString.
toInt(&ok);
3186 synctex_node_p node;
3189 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3196 const QSizeF dpi = d->m_generator->dpi();
3199 double px = (synctex_node_visible_h(node) * dpi.
width()) / 72.27;
3200 double py = (synctex_node_visible_v(node) * dpi.
height()) / 72.27;
3211 return d->m_generator ? d->m_generator->metaData(key, option) :
QVariant();
3216 return d->m_rotation;
3221 bool allPagesSameSize =
true;
3223 for (
int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3224 const Page *p = d->m_pagesVector.at(i);
3231 if (allPagesSameSize) {
3240 if (d->m_generator) {
3242 const Page *p = d->m_pagesVector.at(
page);
3275 if (executingRequest.
width() != otherRequest.
width()) {
3280 if (executingRequest.
height() != otherRequest.
height()) {
3285 if (executingRequest.
isTile() != otherRequest.
isTile()) {
3290 if (executingRequest.
isTile()) {
3303 if (!executingRequest->d->mResultImage.isNull()) {
3311 TilesManager *tm = executingRequest->d->tilesManager();
3313 tm->setPixmap(
nullptr, executingRequest->
normalizedRect(),
true );
3316 PagePrivate::PixmapObject
object = executingRequest->
page()->d->m_pixmaps.take(executingRequest->
observer());
3317 delete object.m_pixmap;
3319 if (executingRequest->d->mShouldAbortRender != 0) {
3323 executingRequest->d->mShouldAbortRender = 1;
3325 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->
page()) {
3326 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3343 if (!d->m_pageController) {
3345 qDeleteAll(requests);
3357 Q_ASSERT(request->
observer() == requesterObserver);
3362 d->m_pixmapRequestsMutex.lock();
3363 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3364 while (sIt != sEnd) {
3365 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.
contains((*sIt)->pageNumber()))) {
3368 sIt = d->m_pixmapRequestsStack.erase(sIt);
3377 qCDebug(OkularCoreDebug).nospace() <<
"request observer=" << request->
observer() <<
" " << request->
width() <<
"x" << request->
height() <<
"@" << request->
pageNumber();
3378 if (d->m_pagesVector.value(request->
pageNumber()) ==
nullptr) {
3384 request->d->mPage = d->m_pagesVector.value(request->
pageNumber());
3392 while (tIt != tEnd) {
3393 const Tile &tile = *tIt;
3395 if (tilesRect.
isNull()) {
3396 tilesRect = tile.
rect();
3398 tilesRect |= tile.
rect();
3409 request->d->mPriority = 0;
3415 for (
PixmapRequest *executingRequest : qAsConst(d->m_executingPixmapRequests)) {
3416 bool newRequestsContainExecutingRequestPage =
false;
3417 bool requestCancelled =
false;
3420 newRequestsContainExecutingRequestPage =
true;
3423 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3424 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3429 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->
observer() && !newRequestsContainExecutingRequestPage) {
3430 requestCancelled = d->cancelRenderingBecauseOf(executingRequest,
nullptr);
3433 if (requestCancelled) {
3434 observersPixmapCleared << executingRequest->
observer();
3444 d->m_pixmapRequestsStack.push_back(request);
3447 sIt = d->m_pixmapRequestsStack.begin();
3448 sEnd = d->m_pixmapRequestsStack.end();
3449 while (sIt != sEnd && (*sIt)->priority() > request->
priority()) {
3452 d->m_pixmapRequestsStack.insert(sIt, request);
3455 d->m_pixmapRequestsMutex.unlock();
3462 d->sendGeneratorPixmapRequest();
3471 Page *kp = d->m_pagesVector[pageNumber];
3472 if (!d->m_generator || !kp) {
3478 d->m_generator->generateTextPage(kp);
3481 void DocumentPrivate::notifyAnnotationChanges(
int page)
3486 void DocumentPrivate::notifyFormChanges(
int )
3496 annotation->d_ptr->baseTransform(t.
inverted());
3498 d->m_undoStack->push(uc);
3515 switch (annotation->
subType()) {
3530 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3531 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3532 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3540 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3541 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3542 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3545 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3547 d->m_undoStack->push(uc);
3548 d->m_prevPropsOfAnnotBeingModified.clear();
3554 QUndoCommand *uc =
new Okular::TranslateAnnotationCommand(d, annotation,
page, delta, complete);
3555 d->m_undoStack->push(uc);
3561 QUndoCommand *uc =
new Okular::AdjustAnnotationCommand(d, annotation,
page, delta1, delta2, complete);
3562 d->m_undoStack->push(uc);
3568 QUndoCommand *uc =
new EditAnnotationContentsCommand(d, annotation,
page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3569 d->m_undoStack->push(uc);
3582 switch (annotation->
subType()) {
3599 d->m_undoStack->push(uc);
3604 d->m_undoStack->beginMacro(
i18nc(
"remove a collection of annotations from the page",
"remove annotations"));
3607 d->m_undoStack->push(uc);
3609 d->m_undoStack->endMacro();
3612 bool DocumentPrivate::canAddAnnotationsNatively()
const
3623 bool DocumentPrivate::canModifyExternalAnnotations()
const
3634 bool DocumentPrivate::canRemoveExternalAnnotations()
const
3648 if (!d->m_generator || !kp) {
3654 kp->d->setTextSelections(rect, color);
3656 kp->d->deleteTextSelections();
3665 return d->m_undoStack->canUndo();
3670 return d->m_undoStack->canRedo();
3706 const int oldPageNumber = oldViewport.
pageNumber;
3714 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3717 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3718 d->m_viewportHistory.pop_front();
3722 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
viewport);
3725 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3727 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3731 if (o != excludeObserver) {
3732 o->notifyViewportChanged(smoothMove);
3735 if (currentPageChanged) {
3736 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3746 }
else if (
page > (
int)d->m_pagesVector.count()) {
3747 page = d->m_pagesVector.count() - 1;
3758 if (o != excludeObserver) {
3759 o->notifyZoom(factor);
3767 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3768 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3771 --d->m_viewportIterator;
3772 foreachObserver(notifyViewportChanged(
true));
3774 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3775 if (oldViewportPage != currentViewportPage)
3776 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3783 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3785 if (nextIterator != d->m_viewportHistory.end()) {
3786 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3789 ++d->m_viewportIterator;
3790 foreachObserver(notifyViewportChanged(
true));
3792 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3793 if (oldViewportPage != currentViewportPage)
3794 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3800 d->m_nextDocumentViewport =
viewport;
3805 d->m_nextDocumentDestination = namedDestination;
3810 d->m_searchCancelled =
false;
3820 if (searchIt == d->m_searches.end()) {
3821 RunningSearch *search =
new RunningSearch();
3822 search->continueOnPage = -1;
3823 searchIt = d->m_searches.insert(searchID, search);
3825 RunningSearch *s = *searchIt;
3828 bool newText = text != s->cachedString;
3829 s->cachedString = text;
3830 s->cachedType = type;
3831 s->cachedCaseSensitivity = caseSensitivity;
3832 s->cachedViewportMove = moveViewport;
3833 s->cachedColor = color;
3834 s->isCurrentlySearching =
true;
3840 *pagesToNotify += s->highlightedPages;
3841 for (
const int pageNumber : qAsConst(s->highlightedPages)) {
3842 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3844 s->highlightedPages.clear();
3854 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3861 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3862 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3863 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3869 if (lastPage && lastPage->number() == s->continueOnPage) {
3871 match = lastPage->findText(searchID, text, forward ?
FromTop :
FromBottom, caseSensitivity);
3873 match = lastPage->findText(searchID, text, forward ?
NextResult :
PreviousResult, caseSensitivity, &s->continueOnMatch);
3885 s->pagesDone = pagesDone;
3887 DoContinueDirectionMatchSearchStruct *searchStruct =
new DoContinueDirectionMatchSearchStruct();
3888 searchStruct->pagesToNotify = pagesToNotify;
3889 searchStruct->match = match;
3891 searchStruct->searchID = searchID;
3893 QTimer::singleShot(0,
this, [
this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3901 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3909 if (it == d->m_searches.constEnd()) {
3915 RunningSearch *p = *it;
3916 if (!p->isCurrentlySearching) {
3917 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3925 if (it == d->m_searches.constEnd()) {
3931 RunningSearch *p = *it;
3932 if (!p->isCurrentlySearching) {
3933 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3940 if (!d->m_generator) {
3946 if (searchIt == d->m_searches.end()) {
3951 RunningSearch *s = *searchIt;
3954 for (
const int pageNumber : qAsConst(s->highlightedPages)) {
3955 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3960 foreachObserver(notifySetup(d->m_pagesVector, 0));
3963 d->m_searches.erase(searchIt);
3969 d->m_searchCancelled =
true;
3974 d->m_undoStack->undo();
3979 d->m_undoStack->redo();
3984 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->
text(), prevCursorPos, prevAnchorPos);
3985 d->m_undoStack->push(uc);
3991 QUndoCommand *uc =
new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
3992 d->m_undoStack->push(uc);
4004 QUndoCommand *uc =
new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4005 d->m_undoStack->push(uc);
4010 QUndoCommand *uc =
new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4011 d->m_undoStack->push(uc);
4016 const int numOfPages =
pages();
4018 d->refreshPixmaps(i);
4020 for (
int i =
currentPage() + 1; i < numOfPages; i++) {
4021 d->refreshPixmaps(i);
4027 return d->m_bookmarkManager;
4033 uint docPages =
pages();
4036 for (uint i = 0; i < docPages; i++) {
4049 uint docPages =
pages();
4053 for (uint i = 0; i < docPages; ++i) {
4063 }
else if (startId >= 0 && endId >= 0) {
4068 if (endId - startId > 0) {
4069 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4077 if (startId >= 0 && endId >= 0) {
4082 if (endId - startId > 0) {
4083 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4094 explicit ExecuteNextActionsHelper(
Document *doc)
4101 ~ExecuteNextActionsHelper()
override
4103 m_doc->removeObserver(
this);
4108 if (setupFlags == DocumentChanged || setupFlags == UrlChanged) {
4113 bool shouldExecuteNextAction()
const
4130 const ExecuteNextActionsHelper executeNextActionsHelper(
this);
4148 if (go->
isExternal() && !d->openRelativeFile(filename)) {
4149 qCWarning(OkularCoreDebug).nospace() <<
"Action: Error opening '" << filename <<
"'.";
4154 if (!nextViewport.
isValid()) {
4160 d->m_nextDocumentDestination =
QString();
4169 d->openRelativeFile(fileName);
4175 QUrl url = d->giveAbsoluteUrl(fileName);
4188 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4194 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4199 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
4205 Q_EMIT error(i18n(
"No application found for opening file of mimetype %1.", mime.name()), -1);
4215 Q_EMIT error(
i18n(
"No application found for opening file of mimetype %1.", mime.
name()), -1);
4227 if ((*d->m_viewportIterator).pageNumber > 0) {
4232 if ((*d->m_viewportIterator).pageNumber < (
int)d->m_pagesVector.count() - 1) {
4275 int lilyRow = 0, lilyCol = 0;
4279 }
else if (extractLilyPondSourceReference(browse->
url(), &lilySource, &lilyRow, &lilyCol)) {
4283 const QUrl url = browse->
url();
4287 d->openRelativeFile(url.
fileName());
4293 if (d->m_url.isValid()) {
4300 KRun *r =
new KRun(realUrl, d->m_widget);
4313 if (!d->m_scripter) {
4314 d->m_scripter =
new Scripter(d);
4325 if (!d->m_scripter) {
4326 d->m_scripter =
new Scripter(d);
4328 d->m_scripter->execute(linkrendition->
scriptType(), linkrendition->
script());
4334 d->m_generator->opaqueAction(
static_cast<const BackendOpaqueAction *
>(action));
4338 if (executeNextActionsHelper.shouldExecuteNextAction()) {
4340 for (
const Action *a : nextActions) {
4349 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for formatting.";
4354 int foundPage = d->findFieldPageNumber(fft);
4356 if (foundPage == -1) {
4357 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4363 std::shared_ptr<Event>
event = Event::createFormatEvent(fft, d->m_pagesVector[foundPage]);
4367 d->executeScriptEvent(
event, linkscript);
4369 const QString formattedText =
event->value().toString();
4370 if (formattedText != unformattedText) {
4376 d->refreshPixmaps(foundPage);
4379 fft->
setText(unformattedText);
4386 d->refreshPixmaps(foundPage);
4399 auto oldUcs4 = oldVal.
toUcs4();
4400 auto newUcs4 = newVal.
toUcs4();
4402 for (
int i = 0; i < std::min(oldUcs4.size(), newUcs4.size()); i++) {
4403 if (oldUcs4.at(i) != newUcs4.at(i)) {
4407 if (oldUcs4.size() < newUcs4.size()) {
4408 return QString::fromUcs4(newUcs4.mid(oldUcs4.size()).constData(), newUcs4.size() - oldUcs4.size());
4416 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4420 int foundPage = d->findFieldPageNumber(fft);
4422 if (foundPage == -1) {
4423 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4427 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(fft, d->m_pagesVector[foundPage]);
4428 event->setChange(DocumentPrivate::diff(fft->
text(), newValue.
toString()));
4432 d->executeScriptEvent(
event, linkscript);
4434 if (
event->returnCode()) {
4444 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4448 int foundPage = d->findFieldPageNumber(fft);
4450 if (foundPage == -1) {
4451 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4455 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(fft, d->m_pagesVector[foundPage]);
4456 event->setWillCommit(
true);
4460 d->executeScriptEvent(
event, linkscript);
4462 if (
event->returnCode()) {
4477 int foundPage = d->findFieldPageNumber(field);
4479 if (foundPage == -1) {
4480 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4484 std::shared_ptr<Event>
event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4488 d->executeScriptEvent(
event, linkscript);
4498 int foundPage = d->findFieldPageNumber(fft);
4500 if (foundPage == -1) {
4501 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4505 std::shared_ptr<Event>
event = Event::createFormValidateEvent(fft, d->m_pagesVector[foundPage]);
4509 d->executeScriptEvent(
event, linkscript);
4510 returnCode =
event->returnCode();
4520 int foundPage = d->findFieldPageNumber(ff);
4522 if (foundPage == -1) {
4523 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4527 std::shared_ptr<Event>
event = Event::createFieldMouseUpEvent(ff, d->m_pagesVector[foundPage]);
4531 d->executeScriptEvent(
event, linkscript);
4540 const QUrl url = d->giveAbsoluteUrl(
ref->fileName());
4542 qCDebug(OkularCoreDebug) << url.
url() <<
"is not a local file.";
4548 qCDebug(OkularCoreDebug) <<
"No such file:" << absFileName;
4552 bool handled =
false;
4561 editors = buildEditorsMap();
4565 QString p = d->editorCommandOverride;
4571 p = SettingsCore::externalEditorCommand();
4610 if (!d->m_synctex_scanner) {
4614 const QSizeF dpi = d->m_generator->dpi();
4616 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.
width(), absY * 72. / dpi.
height()) > 0) {
4617 synctex_node_p node;
4619 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4620 int line = synctex_node_line(node);
4621 int col = synctex_node_column(node);
4626 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4636 if (d->m_generator) {
4658 return d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4664 case TemporaryFileOpenPrintError:
4665 return i18n(
"Could not open a temporary file");
4666 case FileConversionPrintError:
4667 return i18n(
"Print conversion failed");
4668 case PrintingProcessCrashPrintError:
4669 return i18n(
"Printing process crashed");
4670 case PrintingProcessStartPrintError:
4671 return i18n(
"Printing process could not start");
4672 case PrintToFilePrintError:
4673 return i18n(
"Printing to file failed");
4674 case InvalidPrinterStatePrintError:
4675 return i18n(
"Printer was in invalid state");
4676 case UnableToFindFilePrintError:
4677 return i18n(
"Unable to find file to print");
4678 case NoFileToPrintError:
4679 return i18n(
"There was no file to print");
4680 case NoBinaryToPrintError:
4681 return i18n(
"Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4682 case InvalidPageSizePrintError:
4683 return i18n(
"The page print size is invalid");
4686 case UnknownPrintError:
4695 if (d->m_generator) {
4696 PrintInterface *iface = qobject_cast<Okular::PrintInterface *>(d->m_generator);
4710 BackendConfigDialog *bcd =
dynamic_cast<BackendConfigDialog *
>(dialog);
4717 d->loadServiceList(offers);
4725 for (; it != itEnd; ++it) {
4726 sortedGenerators.
insert(it.key(), it.value());
4729 bool pagesAdded =
false;
4732 for (; sit != sitEnd; ++sit) {
4738 if (sit.value().generator == d->m_generator) {
4739 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4755 if (md.rawData()[QStringLiteral(
"X-KDE-okularHasInternalSettings")].toBool()) {
4764 if (!d->m_generator) {
4768 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4769 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4770 return genIt.value().metadata;
4775 return DocumentPrivate::configurableGenerators().size();
4785 result << md.mimeTypes();
4791 for (
const QString &mimeName : qAsConst(result)) {
4795 for (
const QMimeType &mimeType : uniqueMimetypes) {
4796 result.
append(mimeType.name());
4800 result << QStringLiteral(
"application/vnd.kde.okular-archive");
4804 std::sort(result.
begin(), result.
end());
4806 d->m_supportedMimeTypes = result;
4813 if (!d->m_generator) {
4822 if (!d->m_generator) {
4831 d->saveDocumentInfo();
4833 d->clearAndWaitForRequests();
4835 qCDebug(OkularCoreDebug) <<
"Swapping backing file to" << newFileName;
4838 if (result != Generator::SwapBackingFileError) {
4843 if (result == Generator::SwapBackingFileReloadInternalData) {
4849 if (newPagesVector.
count() != d->m_pagesVector.count()) {
4854 for (
int i = 0; i < d->m_undoStack->count(); ++i) {
4857 if (OkularUndoCommand *ouc =
dynamic_cast<OkularUndoCommand *
>(uc)) {
4858 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
4860 qWarning() <<
"Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
4864 qWarning() <<
"Document::swapBackingFile: Unhandled undo command" << uc;
4869 for (
int i = 0; i < d->m_pagesVector.count(); ++i) {
4873 Page *oldPage = d->m_pagesVector[i];
4874 Page *newPage = newPagesVector[i];
4875 newPage->d->adoptGeneratedContents(oldPage->d);
4877 pagePrivatesToDelete << oldPage->d;
4878 oldPage->d = newPage->d;
4879 oldPage->d->m_page = oldPage;
4880 oldPage->d->m_doc = d;
4881 newPage->d =
nullptr;
4883 annotationsToDelete << oldPage->m_annotations;
4884 rectsToDelete << oldPage->m_rects;
4885 oldPage->m_annotations = newPage->m_annotations;
4886 oldPage->m_rects = newPage->m_rects;
4888 qDeleteAll(newPagesVector);
4892 d->m_docFileName = newFileName;
4893 d->updateMetadataXmlNameAndDocSize();
4894 d->m_bookmarkManager->setUrl(d->m_url);
4896 d->m_documentInfoAskedKeys.clear();
4898 if (d->m_synctex_scanner) {
4899 synctex_scanner_free(d->m_synctex_scanner);
4900 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(newFileName).constData(),
nullptr, 1);
4902 d->loadSyncFile(newFileName);
4908 qDeleteAll(annotationsToDelete);
4909 qDeleteAll(rectsToDelete);
4910 qDeleteAll(pagePrivatesToDelete);
4920 qCDebug(OkularCoreDebug) <<
"Swapping backing archive to" << newFileName;
4922 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
4927 const QString tempFileName = newArchive->document.fileName();
4932 delete d->m_archiveData;
4933 d->m_archiveData = newArchive;
4942 d->m_undoStack->setClean();
4944 d->m_undoStack->resetClean();
4948 bool Document::isHistoryClean()
const
4950 return d->m_undoStack->isClean();
4955 if (!d->m_generator) {
4958 Q_ASSERT(!d->m_generatorName.isEmpty());
4961 Q_ASSERT(genIt != d->m_loadedGenerators.end());
4980 return d->canAddAnnotationsNatively();
4994 if (!d->m_generator || fileName.
isEmpty()) {
4997 Q_ASSERT(!d->m_generatorName.isEmpty());
5000 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5018 if (viewDoc ==
this) {
5025 d->m_views.insert(view);
5026 view->d_func()->document = d;
5036 if (!viewDoc || viewDoc !=
this) {
5040 view->d_func()->document =
nullptr;
5041 d->m_views.remove(view);
5046 if (d->m_generator) {
5047 return d->m_generator->requestFontData(font);
5053 ArchiveData *DocumentPrivate::unpackDocumentArchive(
const QString &archivePath)
5057 if (!mime.
inherits(QStringLiteral(
"application/vnd.kde.okular-archive"))) {
5061 KZip okularArchive(archivePath);
5071 for (
const QString &entry : mainDirEntries) {
5073 qWarning() <<
"Warning: Found a directory inside" << archivePath <<
" - Okular does not create files like that so it is most probably forged.";
5079 if (!mainEntry || !mainEntry->
isFile()) {
5083 std::unique_ptr<QIODevice> mainEntryDevice(
static_cast<const KZipFileEntry *
>(mainEntry)->createDevice());
5085 if (!doc.
setContent(mainEntryDevice.get())) {
5088 mainEntryDevice.reset();
5103 documentFileName = fileEl.
text();
5105 metadataFileName = fileEl.
text();
5110 if (documentFileName.
isEmpty()) {
5115 if (!docEntry || !docEntry->
isFile()) {
5119 std::unique_ptr<ArchiveData> archiveData(
new ArchiveData());
5124 if (!archiveData->document.open()) {
5128 archiveData->originalFileName = documentFileName;
5131 std::unique_ptr<QIODevice> docEntryDevice(
static_cast<const KZipFileEntry *
>(docEntry)->createDevice());
5132 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5133 archiveData->document.close();
5137 if (metadataEntry && metadataEntry->
isFile()) {
5138 std::unique_ptr<QIODevice> metadataEntryDevice(
static_cast<const KZipFileEntry *
>(metadataEntry)->createDevice());
5140 if (archiveData->metadataFile.open()) {
5141 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5142 archiveData->metadataFile.close();
5146 return archiveData.release();
5151 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5152 if (!d->m_archiveData) {
5156 const QString tempFileName = d->m_archiveData->document.fileName();
5161 if (ret != OpenSuccess) {
5162 delete d->m_archiveData;
5163 d->m_archiveData =
nullptr;
5171 if (!d->m_generator) {
5177 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5182 QString docPath = d->m_docFileName;
5188 KZip okularArchive(fileName);
5197 const KUserGroup userGroup(QStringLiteral(
""));
5200 QDomDocument contentDoc(QStringLiteral(
"OkularArchive"));
5219 bool annotationsSavedNatively =
false;
5220 bool formsSavedNatively =
false;
5222 if (!modifiedFile.
open()) {
5228 modifiedFile.
close();
5232 docPath = modifiedFileName;
5233 annotationsSavedNatively = d->canAddAnnotationsNatively();
5236 qCWarning(OkularCoreDebug) <<
"saveChanges failed: " << errorText;
5237 qCDebug(OkularCoreDebug) <<
"Falling back to saving a copy of the original file";
5241 PageItems saveWhat = None;
5242 if (!annotationsSavedNatively) {
5243 saveWhat |= AnnotationPageItems;
5245 if (!formsSavedNatively) {
5246 saveWhat |= FormFieldPageItems;
5250 if (!d->savePageDocumentInfo(&metadataFile, saveWhat)) {
5255 const mode_t perm = 0100644;
5256 okularArchive.
writeFile(QStringLiteral(
"content.xml"), contentDocXml, perm, user.
loginName(), userGroup.
name());
5261 if (!okularArchive.
close()) {
5270 if (!d->m_archiveData) {
5277 return d->m_archiveData->document.copy(destFileName);
5282 double width, height;
5283 int landscape, portrait;
5290 for (uint i = 0; i <
pages(); i++) {
5295 std::swap(width, height);
5297 if (width > height) {
5308 d->m_annotationEditingEnabled = enable;
5309 foreachObserver(notifySetup(d->m_pagesVector, 0));
5314 if (d->m_generator) {
5315 d->m_generator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5316 }
else if (d->m_walletGenerator) {
5317 d->m_walletGenerator->walletDataForFile(fileName, walletName, walletFolder, walletKey);
5323 return d->m_docdataMigrationNeeded;
5328 if (d->m_docdataMigrationNeeded) {
5329 d->m_docdataMigrationNeeded =
false;
5330 foreachObserver(notifySetup(d->m_pagesVector, 0));
5336 return d->m_generator ? d->m_generator->layersModel() :
nullptr;
5341 return d->m_openError;
5346 QFile f(d->m_docFileName);
5348 Q_EMIT error(
i18n(
"Could not open '%1'. File does not exist", d->m_docFileName), -1);
5362 d->refreshPixmaps(pageNumber);
5365 void DocumentPrivate::executeScript(
const QString &
function)
5368 m_scripter =
new Scripter(
this);
5379 if (!m_generator || m_closingLoop) {
5380 m_pixmapRequestsMutex.lock();
5381 m_executingPixmapRequests.remove(req);
5382 m_pixmapRequestsMutex.unlock();
5384 if (m_closingLoop) {
5385 m_closingLoop->exit();
5391 if (!m_generator->canGeneratePixmap()) {
5392 qCDebug(OkularCoreDebug) <<
"requestDone with generator not in READY state.";
5398 std::list<AllocatedPixmap *>::iterator aIt = m_allocatedPixmaps.begin();
5399 std::list<AllocatedPixmap *>::iterator aEnd = m_allocatedPixmaps.end();
5400 for (; aIt != aEnd; ++aIt) {
5402 AllocatedPixmap *p = *aIt;
5403 m_allocatedPixmaps.erase(aIt);
5404 m_allocatedPixmapsTotalMemory -= p->memory;
5411 if (m_observers.contains(observer)) {
5413 qulonglong memoryBytes = 0;
5414 const TilesManager *tm = req->d->tilesManager();
5416 memoryBytes = tm->totalMemory();
5421 AllocatedPixmap *memoryPage =
new AllocatedPixmap(req->
observer(), req->
pageNumber(), memoryBytes);
5422 m_allocatedPixmaps.push_back(memoryPage);
5423 m_allocatedPixmapsTotalMemory += memoryBytes;
5430 qCWarning(OkularCoreDebug) <<
"Receiving a done request for the defunct observer" << observer;
5436 m_pixmapRequestsMutex.lock();
5437 m_executingPixmapRequests.remove(req);
5438 m_pixmapRequestsMutex.unlock();
5442 m_pixmapRequestsMutex.lock();
5443 bool hasPixmaps = !m_pixmapRequestsStack.empty();
5444 m_pixmapRequestsMutex.unlock();
5446 sendGeneratorPixmapRequest();
5450 void DocumentPrivate::setPageBoundingBox(
int page,
const NormalizedRect &boundingBox)
5452 Page *kp = m_pagesVector[page];
5453 if (!m_generator || !kp) {
5471 void DocumentPrivate::calculateMaxTextPages()
5473 int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0));
5474 switch (SettingsCore::memoryLevel()) {
5475 case SettingsCore::EnumMemoryLevel::Low:
5476 m_maxAllocatedTextPages = multipliers * 2;
5479 case SettingsCore::EnumMemoryLevel::Normal:
5480 m_maxAllocatedTextPages = multipliers * 50;
5483 case SettingsCore::EnumMemoryLevel::Aggressive:
5484 m_maxAllocatedTextPages = multipliers * 250;
5487 case SettingsCore::EnumMemoryLevel::Greedy:
5488 m_maxAllocatedTextPages = multipliers * 1250;
5493 void DocumentPrivate::textGenerationDone(
Page *page)
5495 if (!m_pageController) {
5500 if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) {
5501 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
5502 if (pageToKick != page->
number())
5504 m_pagesVector.at(pageToKick)->setTextPage(
nullptr);
5509 m_allocatedTextPagesFifo.append(page->
number());
5514 d->setRotationInternal(r,
true);
5517 void DocumentPrivate::setRotationInternal(
int r,
bool notify)
5520 if (!m_generator || (m_rotation == rotation)) {
5527 for (; pIt != pEnd; ++pIt) {
5528 (*pIt)->d->rotateAt(rotation);
5532 m_generator->rotationChanged(rotation, m_rotation);
5535 m_rotation = rotation;
5541 qCDebug(OkularCoreDebug) <<
"Rotated:" << r;
5550 if (d->m_pageSizes.isEmpty()) {
5551 d->m_pageSizes = d->m_generator->pageSizes();
5553 int sizeid = d->m_pageSizes.indexOf(size);
5561 for (; pIt != pEnd; ++pIt) {
5562 (*pIt)->d->changeSize(size);
5565 qDeleteAll(d->m_allocatedPixmaps);
5566 d->m_allocatedPixmaps.clear();
5567 d->m_allocatedPixmapsTotalMemory = 0;
5569 d->m_generator->pageSizeChanged(size, d->m_pageSize);
5571 d->m_pageSize = size;
5575 qCDebug(OkularCoreDebug) <<
"New PageSize id:" << sizeid;
5584 rePos.enabled =
false;
5585 rePos.normalizedX = 0.5;
5586 rePos.normalizedY = 0.0;
5597 rePos.enabled =
false;
5598 rePos.normalizedX = 0.5;
5599 rePos.normalizedY = 0.0;
5622 rePos.enabled =
true;
5627 rePos.enabled =
true;
5651 if (
rePos.enabled) {
5693 if (!other.
rePos.enabled) {
5697 if (
rePos.normalizedY != other.
rePos.normalizedY) {
5698 return rePos.normalizedY < other.
rePos.normalizedY;