12#include "document_p.h"
13#include "documentcommands_p.h"
18#define _WIN32_WINNT 0x0500
20#elif defined(Q_OS_FREEBSD)
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>
47#include <QUndoCommand>
49#include <QtAlgorithms>
51#include <KApplicationTrader>
53#include <KConfigDialog>
56#include <KIO/JobUiDelegate>
57#include <KIO/JobUiDelegateFactory>
58#include <KIO/OpenUrlJob>
59#include <KLocalizedString>
60#include <KMacroExpander>
61#include <KPluginMetaData>
64#include <kio_version.h>
69#include "annotations.h"
70#include "annotations_p.h"
71#include "audioplayer.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>
107struct AllocatedPixmap {
131struct 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
167constexpr int kMemCheckTime = 2000;
170constexpr int kFreeMemCacheTimeout = kMemCheckTime - 100;
174QString DocumentPrivate::pagesSizeString()
const
178 QSizeF size = m_parent->allPagesSize();
181 return localizedSize(size);
188 for (
int i = 0; i < m_pagesVector.count(); ++i) {
189 const Page *p = m_pagesVector.at(i);
190 QString sizeString = localizedSize(
QSizeF(p->width(), p->height()));
191 pageSizeFrequencies[sizeString] = pageSizeFrequencies.
value(sizeString, 0) + 1;
195 int largestFrequencySeen = 0;
198 while (i != pageSizeFrequencies.
constEnd()) {
199 if (i.value() > largestFrequencySeen) {
200 largestFrequencySeen = i.value();
201 mostCommonPageSize = i.key();
205 QString finalText =
i18nc(
"@info %1 is a page size",
"Most pages are %1.", mostCommonPageSize);
216QString DocumentPrivate::namePaperSize(
double inchesWidth,
double inchesHeight)
const
220 const QSize pointsSize(inchesWidth * 72.0, inchesHeight * 72.0);
226 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Portrait %1", paperName);
228 return i18nc(
"paper type and orientation (eg: Portrait A4)",
"Landscape %1", paperName);
232QString DocumentPrivate::localizedSize(
const QSizeF size)
const
234 double inchesWidth = 0, inchesHeight = 0;
235 switch (m_generator->pagesSizeMetric()) {
237 inchesWidth = size.
width() / 72.0;
238 inchesHeight = size.
height() / 72.0;
242 const QSizeF dpi = m_generator->dpi();
251 return i18nc(
"%1 is width, %2 is height, %3 is paper size name",
"%1 × %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight));
253 return i18nc(
"%1 is width, %2 is height, %3 is paper size name",
"%1 × %2 mm (%3)",
QString::number(inchesWidth * 25.4,
'd', 0),
QString::number(inchesHeight * 25.4,
'd', 0), namePaperSize(inchesWidth, inchesHeight));
257qulonglong DocumentPrivate::calculateMemoryToFree()
260 qulonglong clipValue = 0;
261 qulonglong memoryToFree = 0;
263 switch (SettingsCore::memoryLevel()) {
264 case SettingsCore::EnumMemoryLevel::Low:
265 memoryToFree = m_allocatedPixmapsTotalMemory;
268 case SettingsCore::EnumMemoryLevel::Normal: {
269 qulonglong thirdTotalMemory = getTotalMemory() / 3;
270 qulonglong freeMemory = getFreeMemory();
271 if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) {
272 memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory;
274 if (m_allocatedPixmapsTotalMemory > freeMemory) {
275 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
279 case SettingsCore::EnumMemoryLevel::Aggressive: {
280 qulonglong freeMemory = getFreeMemory();
281 if (m_allocatedPixmapsTotalMemory > freeMemory) {
282 clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
285 case SettingsCore::EnumMemoryLevel::Greedy: {
287 qulonglong freeMemory = getFreeMemory(&freeSwap);
288 const qulonglong memoryLimit = qMin(qMax(freeMemory, getTotalMemory() / 2), freeMemory + freeSwap);
289 if (m_allocatedPixmapsTotalMemory > memoryLimit) {
290 clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2;
295 if (clipValue > memoryToFree) {
296 memoryToFree = clipValue;
302void DocumentPrivate::cleanupPixmapMemory()
304 cleanupPixmapMemory(calculateMemoryToFree());
307void DocumentPrivate::cleanupPixmapMemory(qulonglong memoryToFree)
309 if (memoryToFree < 1) {
313 const int currentViewportPage = (*m_viewportIterator).pageNumber;
318 for (; vIt != vEnd; ++vIt) {
319 visibleRects.
insert((*vIt)->pageNumber, (*vIt));
324 while (memoryToFree > 0) {
325 AllocatedPixmap *p = searchLowestPriorityPixmap(
true,
true);
330 qCDebug(OkularCoreDebug).nospace() <<
"Evicting cache pixmap observer=" << p->observer <<
" page=" << p->page;
334 m_allocatedPixmapsTotalMemory -= p->memory;
336 if (p->memory > memoryToFree) {
339 memoryToFree -= p->memory;
343 m_pagesVector.at(p->page)->deletePixmap(p->observer);
352 std::list<AllocatedPixmap *> pixmapsToKeep;
353 while (memoryToFree > 0) {
356 AllocatedPixmap *p = searchLowestPriorityPixmap(
false,
true, observer);
363 TilesManager *tilesManager = m_pagesVector.at(p->page)->d->tilesManager(observer);
364 if (tilesManager && tilesManager->totalMemory() > 0) {
365 qulonglong memoryDiff = p->memory;
367 if (visibleRects.
contains(p->page)) {
368 visibleRect = visibleRects[p->page]->rect;
372 tilesManager->cleanupPixmapMemory(memoryToFree, visibleRect, currentViewportPage);
374 p->memory = tilesManager->totalMemory();
375 memoryDiff -= p->memory;
376 memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0;
377 m_allocatedPixmapsTotalMemory -= memoryDiff;
380 pixmapsToKeep.push_back(p);
385 pixmapsToKeep.push_back(p);
389 if (clean_hits == 0) {
394 m_allocatedPixmaps.splice(m_allocatedPixmaps.end(), pixmapsToKeep);
395 Q_UNUSED(pagesFreed);
404AllocatedPixmap *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;
438qulonglong 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);
478qulonglong 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];
533 memoryFree = values[0] + values[1] + values[2] + values[3];
534 if (values[4] > memoryFree) {
537 memoryFree -= values[4];
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);
580bool DocumentPrivate::loadDocumentInfo(LoadDocumentInfoFlags loadWhat)
585 if (m_xmlFileName.isEmpty()) {
589 QFile infoFile(m_xmlFileName);
590 return loadDocumentInfo(infoFile, loadWhat);
593bool DocumentPrivate::loadDocumentInfo(
QFile &infoFile, LoadDocumentInfoFlags loadWhat)
597 if (loadWhat & LoadGeneralInfo) {
603 for (
View *view : std::as_const(m_views)) {
604 setDefaultViewMode(view, defaultViewMode);
612 if (!doc.setContent(&infoFile)) {
613 qCDebug(OkularCoreDebug) <<
"Can't load XML pair! Check for broken xml.";
625 bool loadedAnything =
false;
633 if (catName ==
QLatin1String(
"pageList") && (loadWhat & LoadPageInfo)) {
637 if (pageElement.
hasAttribute(QStringLiteral(
"number"))) {
640 int pageNumber = pageElement.
attribute(QStringLiteral(
"number")).
toInt(&ok);
643 if (ok && pageNumber >= 0 && pageNumber < (
int)m_pagesVector.count()) {
644 if (m_pagesVector[pageNumber]->d->restoreLocalContents(pageElement)) {
645 loadedAnything =
true;
654 else if (catName ==
QLatin1String(
"generalInfo") && (loadWhat & LoadGeneralInfo)) {
662 m_viewportHistory.clear();
667 if (historyElement.
hasAttribute(QStringLiteral(
"viewport"))) {
670 loadedAnything =
true;
675 if (m_viewportHistory.empty()) {
676 m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(),
DocumentViewport());
681 int newrotation = !str.
isEmpty() ? (str.
toInt(&ok) % 4) : 0;
682 if (ok && newrotation != 0) {
683 setRotationInternal(newrotation,
false);
684 loadedAnything =
true;
692 for (
View *view : std::as_const(m_views)) {
693 if (view->name() == viewName) {
694 loadViewsInfo(view, viewElement);
695 loadedAnything =
true;
710 return loadedAnything;
721 bool newzoom_ok =
true;
722 const double newzoom = !valueString.
isEmpty() ? valueString.
toDouble(&newzoom_ok) : 1.0;
723 if (newzoom_ok && newzoom != 0 && view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable))) {
724 view->setCapability(View::Zoom, newzoom);
727 bool newmode_ok =
true;
728 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
729 if (newmode_ok && view->supportsCapability(View::ZoomModality) && (view->capabilityFlags(View::ZoomModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
730 view->setCapability(View::ZoomModality, newmode);
734 bool newmode_ok =
true;
735 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
736 if (newmode_ok && view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
737 view->setCapability(View::ViewModeModality, newmode);
741 bool newmode_ok =
true;
742 const int newmode = !modeString.
isEmpty() ? modeString.
toInt(&newmode_ok) : 2;
743 if (newmode_ok && view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
744 view->setCapability(View::Continuous, newmode);
748 bool newmode_ok =
true;
749 const int newmode = !valueString.
isEmpty() ? valueString.
toInt(&newmode_ok) : 2;
750 if (newmode_ok && view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
751 view->setCapability(View::TrimMargins, newmode);
761 if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
762 view->setCapability(View::ViewModeModality, (
int)defaultViewMode);
765 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
766 view->setCapability(View::Continuous, (
int)m_generator->defaultPageContinuous());
772 if (view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable)) && view->supportsCapability(View::ZoomModality) &&
773 (view->capabilityFlags(View::ZoomModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
777 const double zoom = view->capability(View::Zoom).toDouble(&ok);
778 if (ok && zoom != 0) {
781 const int mode = view->capability(View::ZoomModality).toInt(&ok);
786 if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
789 const bool mode = view->capability(View::Continuous).toBool();
792 if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
796 const int mode = view->capability(View::ViewModeModality).toInt(&ok);
801 if (view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
804 const bool value = view->capability(View::TrimMargins).toBool();
809QUrl DocumentPrivate::giveAbsoluteUrl(
const QString &fileName)
const
815 if (!m_url.isValid()) {
822bool DocumentPrivate::openRelativeFile(
const QString &fileName)
824 const QUrl newUrl = giveAbsoluteUrl(fileName);
829 qCDebug(OkularCoreDebug).nospace() <<
"openRelativeFile: '" << newUrl <<
"'";
831 Q_EMIT m_parent->openUrl(newUrl);
832 return m_url == newUrl;
837 const auto result = KPluginFactory::instantiatePlugin<Okular::Generator>(service);
840 qCWarning(OkularCoreDebug).nospace() <<
"Failed to load plugin " << service.
fileName() <<
": " << result.errorText;
844 GeneratorInfo info(result.plugin, service);
845 m_loadedGenerators.insert(service.
pluginId(), info);
846 return result.plugin;
849void DocumentPrivate::loadAllGeneratorLibraries()
851 if (m_generatorsLoaded) {
855 loadServiceList(availableGenerators());
857 m_generatorsLoaded =
true;
862 int count = offers.
count();
867 for (
int i = 0; i < count; ++i) {
871 if (!m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd()) {
880void DocumentPrivate::unloadGenerator(
const GeneratorInfo &info)
882 delete info.generator;
885void DocumentPrivate::cacheExportFormats()
887 if (m_exportCached) {
892 for (
int i = 0; i < formats.
count(); ++i) {
894 m_exportToText = formats.
at(i);
896 m_exportFormats.append(formats.
at(i));
900 m_exportCached =
true;
905 if (info.configChecked) {
909 info.config = qobject_cast<Okular::ConfigInterface *>(info.generator);
910 info.configChecked =
true;
914SaveInterface *DocumentPrivate::generatorSave(GeneratorInfo &info)
916 if (info.saveChecked) {
920 info.
save = qobject_cast<Okular::SaveInterface *>(info.generator);
921 info.saveChecked =
true;
929 m_walletGenerator =
nullptr;
930 if (genIt != m_loadedGenerators.constEnd()) {
931 m_generator = genIt.value().generator;
933 m_generator = loadGeneratorLibrary(offer);
935 return Document::OpenError;
937 genIt = m_loadedGenerators.constFind(propName);
938 Q_ASSERT(genIt != m_loadedGenerators.constEnd());
940 Q_ASSERT_X(m_generator,
"Document::load()",
"null generator?!");
942 m_generator->d_func()->m_document =
this;
954 qCDebug(OkularCoreDebug) <<
"Output DPI:" << dpi;
955 m_generator->setDPI(dpi);
959 openResult = m_generator->loadDocumentWithPassword(docFile, m_pagesVector, password);
960 }
else if (!filedata.
isEmpty()) {
962 openResult = m_generator->loadDocumentFromDataWithPassword(filedata, m_pagesVector, password);
965 if (!m_tempFile->open()) {
967 m_tempFile =
nullptr;
969 m_tempFile->write(filedata);
970 QString tmpFileName = m_tempFile->fileName();
972 openResult = m_generator->loadDocumentWithPassword(tmpFileName, m_pagesVector, password);
978 if (openResult != Document::OpenSuccess || m_pagesVector.size() <= 0) {
979 m_generator->d_func()->m_document =
nullptr;
987 m_walletGenerator = m_generator;
988 m_generator =
nullptr;
990 qDeleteAll(m_pagesVector);
991 m_pagesVector.clear();
993 m_tempFile =
nullptr;
996 if (openResult == Document::OpenSuccess) {
997 openResult = Document::OpenError;
1012bool DocumentPrivate::savePageDocumentInfo(
QTemporaryFile *infoFile,
int what)
const
1014 if (infoFile->
open()) {
1017 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral(
"xml"), QStringLiteral(
"version=\"1.0\" encoding=\"utf-8\""));
1019 QDomElement root = doc.createElement(QStringLiteral(
"documentInfo"));
1023 QDomElement pageList = doc.createElement(QStringLiteral(
"pageList"));
1027 for (; pIt != pEnd; ++pIt) {
1028 (*pIt)->d->saveLocalContents(pageList, doc, PageItems(what));
1045 if (!m_nextDocumentDestination.isEmpty() && m_generator) {
1054void DocumentPrivate::performAddPageAnnotation(
int page,
Annotation *annotation)
1060 Page *kp = m_pagesVector[page];
1061 if (!m_generator || !kp) {
1066 if (annotation->d_ptr->m_page) {
1071 kp->addAnnotation(annotation);
1079 notifyAnnotationChanges(page);
1083 refreshPixmaps(page);
1087void DocumentPrivate::performRemovePageAnnotation(
int page,
Annotation *annotation)
1091 bool isExternallyDrawn;
1094 Page *kp = m_pagesVector[page];
1095 if (!m_generator || !kp) {
1100 isExternallyDrawn =
true;
1102 isExternallyDrawn =
false;
1106 if (m_parent->canRemovePageAnnotation(annotation)) {
1112 kp->removeAnnotation(annotation);
1115 notifyAnnotationChanges(page);
1117 if (isExternallyDrawn) {
1119 refreshPixmaps(page);
1124void DocumentPrivate::performModifyPageAnnotation(
int page,
Annotation *annotation,
bool appearanceChanged)
1130 const Page *kp = m_pagesVector[page];
1131 if (!m_generator || !kp) {
1141 notifyAnnotationChanges(page);
1146 if (m_annotationBeingModified) {
1149 m_annotationBeingModified =
true;
1152 m_annotationBeingModified =
false;
1156 qCDebug(OkularCoreDebug) <<
"Refreshing Pixmaps";
1157 refreshPixmaps(page);
1161void DocumentPrivate::performSetAnnotationContents(
const QString &newContents,
Annotation *annot,
int pageNumber)
1163 bool appearanceChanged =
false;
1169 const Okular::TextAnnotation *txtann =
static_cast<Okular::TextAnnotation *
>(annot);
1170 if (txtann->textType() == Okular::TextAnnotation::InPlace) {
1171 appearanceChanged =
true;
1177 const Okular::LineAnnotation *lineann =
static_cast<Okular::LineAnnotation *
>(annot);
1178 if (lineann->showCaption()) {
1179 appearanceChanged =
true;
1191 performModifyPageAnnotation(pageNumber, annot, appearanceChanged);
1194void DocumentPrivate::recalculateForms()
1196 const QVariant fco = m_parent->metaData(QStringLiteral(
"FormCalculateOrder"));
1198 for (
int formId : formCalculateOrder) {
1199 for (uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++) {
1200 const Page *p = m_parent->page(pageIdx);
1202 bool pageNeedsRefresh =
false;
1205 if (form->id() == formId) {
1209 std::shared_ptr<Event>
event;
1213 event = Event::createFormCalculateEvent(fft, m_pagesVector[pageIdx]);
1215 m_scripter =
new Scripter(
this);
1219 oldVal = fft->
text();
1222 m_parent->processAction(action);
1225 m_scripter->setEvent(
nullptr);
1226 const QString newVal =
event->value().toString();
1227 if (newVal != oldVal) {
1232 m_parent->processFormatAction(action, form);
1234 Q_EMIT m_parent->refreshFormWidget(fft);
1235 pageNeedsRefresh =
true;
1240 qWarning() <<
"Form that is part of calculate order doesn't have a calculate action";
1244 if (pageNeedsRefresh) {
1245 refreshPixmaps(p->number());
1252void DocumentPrivate::saveDocumentInfo()
const
1254 if (m_xmlFileName.isEmpty()) {
1258 QFile infoFile(m_xmlFileName);
1259 qCDebug(OkularCoreDebug) <<
"About to save document info to" << m_xmlFileName;
1261 qCWarning(OkularCoreDebug) <<
"Failed to open docdata file" << m_xmlFileName;
1266 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral(
"xml"), QStringLiteral(
"version=\"1.0\" encoding=\"utf-8\""));
1268 QDomElement root = doc.createElement(QStringLiteral(
"documentInfo"));
1270 doc.appendChild(root);
1274 if (m_docdataMigrationNeeded) {
1275 QDomElement pageList = doc.createElement(QStringLiteral(
"pageList"));
1283 const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems;
1286 for (; pIt != pEnd; ++pIt) {
1287 (*pIt)->d->saveLocalContents(pageList, doc, saveWhat);
1292 QDomElement generalInfo = doc.createElement(QStringLiteral(
"generalInfo"));
1296 QDomElement rotationNode = doc.createElement(QStringLiteral(
"rotation"));
1301 const auto currentViewportIterator = std::list<DocumentViewport>::const_iterator(m_viewportIterator);
1302 std::list<DocumentViewport>::const_iterator backIterator = currentViewportIterator;
1303 if (backIterator != m_viewportHistory.end()) {
1305 int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
1306 while (backSteps-- && backIterator != m_viewportHistory.begin()) {
1311 QDomElement historyNode = doc.createElement(QStringLiteral(
"history"));
1315 std::list<DocumentViewport>::const_iterator endIt = currentViewportIterator;
1317 while (backIterator != endIt) {
1318 QString name = (backIterator == currentViewportIterator) ? QStringLiteral(
"current") : QStringLiteral(
"oldPage");
1319 QDomElement historyEntry = doc.createElement(name);
1320 historyEntry.
setAttribute(QStringLiteral(
"viewport"), (*backIterator).toString());
1326 QDomElement viewsNode = doc.createElement(QStringLiteral(
"views"));
1328 for (
View *view : std::as_const(m_views)) {
1329 QDomElement viewEntry = doc.createElement(QStringLiteral(
"view"));
1330 viewEntry.
setAttribute(QStringLiteral(
"name"), view->name());
1332 saveViewsInfo(view, viewEntry);
1343void DocumentPrivate::slotTimedMemoryCheck()
1346 if (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024 * 1024) {
1347 cleanupPixmapMemory();
1351void DocumentPrivate::sendGeneratorPixmapRequest()
1357 const qulonglong memoryToFree = calculateMemoryToFree();
1358 const int currentViewportPage = (*m_viewportIterator).pageNumber;
1359 int maxDistance = INT_MAX;
1361 const AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap(
true);
1362 if (pixmapToReplace) {
1363 maxDistance = qAbs(pixmapToReplace->page - currentViewportPage);
1369 m_pixmapRequestsMutex.lock();
1370 while (!m_pixmapRequestsStack.empty() && !request) {
1373 m_pixmapRequestsStack.pop_back();
1378 TilesManager *tilesManager = r->d->tilesManager();
1380 const QScreen *screen =
nullptr;
1397 m_pixmapRequestsStack.pop_back();
1402 m_pixmapRequestsStack.pop_back();
1404 }
else if (!r->d->mForce && r->
preload() && qAbs(r->
pageNumber() - currentViewportPage) >= maxDistance) {
1405 m_pixmapRequestsStack.pop_back();
1411 m_pixmapRequestsStack.pop_back();
1417 qCDebug(OkularCoreDebug).nospace() <<
"Start using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1423 tilesManager->setPixmap(pixmap,
NormalizedRect(0, 0, 1, 1),
true );
1431 r->
page()->d->setTilesManager(r->
observer(), tilesManager);
1440 while (tIt != tEnd) {
1442 if (tilesRect.
isNull()) {
1443 tilesRect = tile.
rect();
1445 tilesRect |= tile.
rect();
1458 m_pixmapRequestsStack.pop_back();
1463 else if (tilesManager && (
long)r->
width() * (
long)r->
height() < 3L * screenSize) {
1464 qCDebug(OkularCoreDebug).nospace() <<
"Stop using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1471 }
else if ((
long)requestRect.
width() * (
long)requestRect.
height() > 100L * screenSize && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy)) {
1472 m_pixmapRequestsStack.pop_back();
1473 if (!m_warnedOutOfMemory) {
1474 qCWarning(OkularCoreDebug).nospace() <<
"Running out of memory on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1475 qCWarning(OkularCoreDebug) <<
"this message will be reported only once.";
1476 m_warnedOutOfMemory =
true;
1486 m_pixmapRequestsMutex.unlock();
1491 qulonglong pixmapBytes = 0;
1492 TilesManager *tm = request->d->tilesManager();
1494 pixmapBytes = tm->totalMemory();
1496 pixmapBytes = 4 * request->
width() * request->
height();
1499 if (pixmapBytes > (1024 * 1024)) {
1500 cleanupPixmapMemory(memoryToFree );
1504 if (m_generator->canGeneratePixmap()) {
1505 QRect requestRect = !request->
isTile() ?
QRect(0, 0, request->
width(), request->
height()) : request->normalizedRect().geometry(request->width(), request->height());
1506 qCDebug(OkularCoreDebug).nospace() <<
"sending request observer=" << request->
observer() <<
" " << requestRect.
width() <<
"x" << requestRect.
height() <<
"@" << request->
pageNumber() <<
" async == " << request->
asynchronous()
1507 <<
" isTile == " << request->
isTile();
1508 m_pixmapRequestsStack.remove(request);
1514 if ((
int)m_rotation % 2) {
1530 m_executingPixmapRequests.push_back(request);
1531 m_pixmapRequestsMutex.unlock();
1532 m_generator->generatePixmap(request);
1534 m_pixmapRequestsMutex.unlock();
1540void DocumentPrivate::rotationFinished(
int page,
Okular::Page *okularPage)
1542 const Okular::Page *wantedPage = m_pagesVector.value(page,
nullptr);
1543 if (!wantedPage || wantedPage != okularPage) {
1552void DocumentPrivate::slotFontReadingProgress(
int page)
1554 Q_EMIT m_parent->fontReadingProgress(page);
1556 if (page >= (
int)m_parent->pages() - 1) {
1557 Q_EMIT m_parent->fontReadingEnded();
1558 m_fontThread =
nullptr;
1559 m_fontsCached =
true;
1566 if (m_fontsCache.indexOf(font) == -1) {
1567 m_fontsCache.append(font);
1569 Q_EMIT m_parent->gotFont(font);
1573void DocumentPrivate::slotGeneratorConfigChanged()
1580 bool configchanged =
false;
1582 for (; it != itEnd; ++it) {
1586 if (it_changed && (m_generator == it.value().generator)) {
1587 configchanged =
true;
1591 if (configchanged) {
1594 for (; it !=
end; ++it) {
1595 (*it)->deletePixmaps();
1599 qDeleteAll(m_allocatedPixmaps);
1600 m_allocatedPixmaps.clear();
1601 m_allocatedPixmapsTotalMemory = 0;
1608 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.empty() && !m_pagesVector.isEmpty()) {
1609 cleanupPixmapMemory();
1613void DocumentPrivate::refreshPixmaps(
int pageNumber)
1615 Page *page = m_pagesVector.value(pageNumber,
nullptr);
1622 for (; it != itEnd; ++it) {
1623 const QSize size = (*it).m_pixmap->size();
1625 p->d->mForce =
true;
1626 pixmapsToRequest << p;
1640 TilesManager *tilesManager = page->d->tilesManager(observer);
1642 tilesManager->markDirty();
1644 PixmapRequest *p =
new PixmapRequest(observer, pageNumber, tilesManager->width(), tilesManager->height(), 1 , 1, PixmapRequest::Asynchronous);
1649 for (; vIt != vEnd; ++vIt) {
1650 if ((*vIt)->pageNumber == pageNumber) {
1651 visibleRect = (*vIt)->rect;
1656 if (!visibleRect.
isNull()) {
1659 p->d->mForce =
true;
1670void DocumentPrivate::_o_configChanged()
1673 calculateMaxTextPages();
1674 while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) {
1675 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
1676 m_pagesVector.at(pageToKick)->setTextPage(
nullptr);
1680void DocumentPrivate::doContinueDirectionMatchSearch(
void *doContinueDirectionMatchSearchStruct)
1682 DoContinueDirectionMatchSearchStruct *searchStruct =
static_cast<DoContinueDirectionMatchSearchStruct *
>(doContinueDirectionMatchSearchStruct);
1683 RunningSearch *search = m_searches.value(searchStruct->searchID);
1685 if ((m_searchCancelled && !searchStruct->match) || !search) {
1690 search->isCurrentlySearching =
false;
1693 Q_EMIT m_parent->searchFinished(searchStruct->searchID, Document::SearchCancelled);
1694 delete searchStruct->pagesToNotify;
1695 delete searchStruct;
1699 const bool forward = search->cachedType == Document::NextMatch;
1700 bool doContinue =
false;
1702 if (!searchStruct->match) {
1703 const int pageCount = m_pagesVector.count();
1704 if (search->pagesDone < pageCount) {
1706 if (searchStruct->currentPage >= pageCount) {
1707 searchStruct->currentPage = 0;
1708 Q_EMIT m_parent->notice(
i18n(
"Continuing search from beginning"), 3000);
1709 }
else if (searchStruct->currentPage < 0) {
1710 searchStruct->currentPage = pageCount - 1;
1711 Q_EMIT m_parent->notice(
i18n(
"Continuing search from bottom"), 3000);
1718 const Page *page = m_pagesVector[searchStruct->currentPage];
1720 if (!page->hasTextPage()) {
1721 m_parent->requestTextPage(page->number());
1725 searchStruct->match = page->findText(searchStruct->searchID, search->cachedString, forward ?
FromTop :
FromBottom, search->cachedCaseSensitivity);
1726 if (!searchStruct->match) {
1728 searchStruct->currentPage++;
1730 searchStruct->currentPage--;
1732 search->pagesDone++;
1734 search->pagesDone = 1;
1738 QTimer::singleShot(0, m_parent, [
this, searchStruct] { doContinueDirectionMatchSearch(searchStruct); });
1740 doProcessSearchMatch(searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor);
1741 delete searchStruct;
1745void DocumentPrivate::doProcessSearchMatch(
RegularAreaRect *match, RunningSearch *search,
QSet<int> *pagesToNotify,
int currentPage,
int searchID,
bool moveViewport,
const QColor &color)
1750 bool foundAMatch =
false;
1752 search->isCurrentlySearching =
false;
1758 search->continueOnPage = currentPage;
1759 search->continueOnMatch = *
match;
1760 search->highlightedPages.
insert(currentPage);
1762 m_pagesVector[currentPage]->d->setHighlight(searchID, match, color);
1765 pagesToNotify->
insert(currentPage);
1770 const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible(matchRectWithBuffer, currentPage);
1773 if (moveViewport && !matchRectFullyVisible) {
1775 searchViewport.rePos.enabled =
true;
1776 searchViewport.rePos.normalizedX = (
match->first().left +
match->first().right) / 2.0;
1777 searchViewport.rePos.normalizedY = (
match->first().top +
match->first().bottom) / 2.0;
1778 m_parent->setViewport(searchViewport,
nullptr,
true);
1784 for (
int pageNumber : std::as_const(*pagesToNotify)) {
1791 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1793 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1796 delete pagesToNotify;
1799void DocumentPrivate::doContinueAllDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID)
1803 RunningSearch *search = m_searches.value(searchID);
1805 if (m_searchCancelled || !search) {
1811 search->isCurrentlySearching =
false;
1814 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1815 for (
const MatchesVector &mv : std::as_const(*pageMatches)) {
1819 delete pagesToNotify;
1823 if (currentPage < m_pagesVector.count()) {
1825 Page *page = m_pagesVector.at(currentPage);
1826 int pageNumber = page->number();
1829 if (!page->hasTextPage()) {
1830 m_parent->requestTextPage(pageNumber);
1837 lastMatch = page->findText(searchID, search->cachedString,
NextResult, search->cachedCaseSensitivity, lastMatch);
1839 lastMatch = page->findText(searchID, search->cachedString,
FromTop, search->cachedCaseSensitivity);
1847 (*pageMatches)[page].append(lastMatch);
1851 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID] { doContinueAllDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID); });
1856 search->isCurrentlySearching =
false;
1857 bool foundAMatch = pageMatches->
count() != 0;
1861 for (; it != itEnd; ++it) {
1863 it.
key()->d->setHighlight(searchID, match, search->cachedColor);
1866 search->highlightedPages.
insert(it.
key()->number());
1867 pagesToNotify->
insert(it.
key()->number());
1875 for (
int pageNumber : std::as_const(*pagesToNotify)) {
1882 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1884 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1888 delete pagesToNotify;
1892void DocumentPrivate::doContinueGooglesDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID,
const QStringList &words)
1894 typedef QPair<RegularAreaRect *, QColor> MatchColor;
1897 RunningSearch *search = m_searches.value(searchID);
1899 if (m_searchCancelled || !search) {
1905 search->isCurrentlySearching =
false;
1908 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1910 for (
const MatchesVector &mv : std::as_const(*pageMatches)) {
1911 for (
const MatchColor &mc : mv) {
1916 delete pagesToNotify;
1920 const int wordCount = words.
count();
1921 const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
1922 int baseHue, baseSat, baseVal;
1923 search->cachedColor.getHsv(&baseHue, &baseSat, &baseVal);
1925 if (currentPage < m_pagesVector.count()) {
1927 Page *page = m_pagesVector.at(currentPage);
1928 int pageNumber = page->number();
1931 if (!page->hasTextPage()) {
1932 m_parent->requestTextPage(pageNumber);
1936 bool allMatched = wordCount > 0, anyMatched =
false;
1937 for (
int w = 0; w < wordCount; w++) {
1938 const QString &word = words[w];
1939 int newHue = baseHue - w * hueStep;
1946 bool wordMatched =
false;
1949 lastMatch = page->findText(searchID, word,
NextResult, search->cachedCaseSensitivity, lastMatch);
1951 lastMatch = page->findText(searchID, word,
FromTop, search->cachedCaseSensitivity);
1959 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
1962 allMatched = allMatched && wordMatched;
1963 anyMatched = anyMatched || wordMatched;
1967 const bool matchAll = search->cachedType == Document::GoogleAll;
1968 if (!allMatched && matchAll) {
1970 for (
const MatchColor &mc : matches) {
1973 pageMatches->
remove(page);
1976 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID, words] { doContinueGooglesDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID, words); });
1981 search->isCurrentlySearching =
false;
1982 bool foundAMatch = pageMatches->
count() != 0;
1986 for (; it != itEnd; ++it) {
1987 for (
const MatchColor &mc : it.
value()) {
1988 it.
key()->d->setHighlight(searchID, mc.first, mc.second);
1991 search->highlightedPages.
insert(it.
key()->number());
1992 pagesToNotify->
insert(it.
key()->number());
2001 for (
int pageNumber : std::as_const(*pagesToNotify)) {
2008 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
2010 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
2014 delete pagesToNotify;
2022 bool giveDefault = option.
toBool();
2024 if ((SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper) && SettingsCore::changeColors()) {
2025 color = SettingsCore::paperColor();
2026 }
else if (giveDefault) {
2033 switch (SettingsCore::textAntialias()) {
2034 case SettingsCore::EnumTextAntialias::Enabled:
2037 case SettingsCore::EnumTextAntialias::Disabled:
2044 switch (SettingsCore::graphicsAntialias()) {
2045 case SettingsCore::EnumGraphicsAntialias::Enabled:
2048 case SettingsCore::EnumGraphicsAntialias::Disabled:
2055 switch (SettingsCore::textHinting()) {
2056 case SettingsCore::EnumTextHinting::Enabled:
2059 case SettingsCore::EnumTextHinting::Disabled:
2068bool DocumentPrivate::isNormalizedRectangleFullyVisible(
const Okular::NormalizedRect &rectOfInterest,
int rectPage)
2070 bool rectFullyVisible =
false;
2075 for (; (vIt != vEnd) && !rectFullyVisible; ++vIt) {
2076 if ((*vIt)->pageNumber == rectPage && (*vIt)->rect.contains(rectOfInterest.
left, rectOfInterest.
top) && (*vIt)->rect.contains(rectOfInterest.
right, rectOfInterest.
bottom)) {
2077 rectFullyVisible =
true;
2080 return rectFullyVisible;
2083struct pdfsyncpoint {
2092void DocumentPrivate::loadSyncFile(
const QString &filePath)
2101 const QString coreName = ts.readLine();
2103 const QString versionstr = ts.readLine();
2108 if (!
match.hasMatch()) {
2114 int currentpage = -1;
2118 fileStack.
push(coreName + texStr);
2120 const QSizeF dpi = m_generator->dpi();
2123 while (!ts.atEnd()) {
2124 line = ts.readLine();
2126 const int tokenSize = tokens.
count();
2127 if (tokenSize < 1) {
2131 int id = tokens.
at(1).toInt();
2137 pt.row = tokens.
at(2).toInt();
2140 pt.file = fileStack.
top();
2144 currentpage = tokens.
at(1).toInt() - 1;
2147 qCDebug(OkularCoreDebug) <<
"PdfSync: 'p*' line ignored";
2149 int id = tokens.
at(1).toInt();
2151 if (it != points.
end()) {
2152 it->x = tokens.
at(2).toInt();
2153 it->y = tokens.
at(3).toInt();
2154 it->page = currentpage;
2163 fileStack.
push(newfile);
2168 qCDebug(OkularCoreDebug) <<
"PdfSync: going one level down too much";
2171 qCDebug(OkularCoreDebug).nospace() <<
"PdfSync: unknown line format: '" << line <<
"'";
2176 for (
const pdfsyncpoint &pt : std::as_const(points)) {
2178 if (pt.page < 0 || pt.page >= m_pagesVector.size()) {
2183 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()));
2188 for (
int i = 0; i < refRects.size(); ++i) {
2189 if (!refRects.at(i).isEmpty()) {
2190 m_pagesVector[i]->setSourceReferences(refRects.at(i));
2195void DocumentPrivate::clearAndWaitForRequests()
2197 m_pixmapRequestsMutex.lock();
2198 std::list<PixmapRequest *>::const_iterator sIt = m_pixmapRequestsStack.begin();
2199 std::list<PixmapRequest *>::const_iterator sEnd = m_pixmapRequestsStack.end();
2200 for (; sIt != sEnd; ++sIt) {
2203 m_pixmapRequestsStack.clear();
2204 m_pixmapRequestsMutex.unlock();
2207 bool startEventLoop =
false;
2209 m_pixmapRequestsMutex.lock();
2210 startEventLoop = !m_executingPixmapRequests.empty();
2213 for (
PixmapRequest *executingRequest : std::as_const(m_executingPixmapRequests)) {
2214 executingRequest->d->mShouldAbortRender = 1;
2217 if (m_generator->d_ptr->mTextPageGenerationThread) {
2218 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
2222 m_pixmapRequestsMutex.unlock();
2223 if (startEventLoop) {
2224 m_closingLoop = &loop;
2226 m_closingLoop =
nullptr;
2228 }
while (startEventLoop);
2235 for (uint pageIdx = 0, nPages = m_parent->pages(); pageIdx < nPages; pageIdx++) {
2236 const Page *p = m_parent->
page(pageIdx);
2237 if (p && p->formFields().contains(field)) {
2238 foundPage =
static_cast<int>(pageIdx);
2245void DocumentPrivate::executeScriptEvent(
const std::shared_ptr<Event> &event,
const Okular::ScriptAction *linkscript)
2248 m_scripter =
new Scripter(
this);
2254 m_scripter->setEvent(
nullptr);
2259 , d(new DocumentPrivate(this))
2261 d->m_widget = widget;
2263 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
DocumentViewport());
2266 connect(SettingsCore::self(), &SettingsCore::configChanged,
this, [
this] { d->_o_configChanged(); });
2271 qRegisterMetaType<Okular::FontInfo>();
2280 for (; viewIt != viewEnd; ++viewIt) {
2282 v->d_func()->document =
nullptr;
2286 delete d->m_bookmarkManager;
2290 for (; it != itEnd; ++it) {
2291 d->unloadGenerator(it.value());
2293 d->m_loadedGenerators.clear();
2299QString DocumentPrivate::docDataFileName(
const QUrl &url, qint64 document_size)
2306 qCDebug(OkularCoreDebug) <<
"creating docdata folder" << docdataDir;
2311 return newokularfile;
2340 for (
const QString &supported : mimetypes) {
2342 if (mimeType == type && !exactMatches.
contains(md)) {
2346 if (
type.inherits(supported) && !offers.
contains(md)) {
2352 if (!exactMatches.
isEmpty()) {
2353 offers = exactMatches;
2361 int offercount = offers.
size();
2362 if (offercount > 1) {
2365 const QString property = QStringLiteral(
"X-KDE-Priority");
2366 return s1.
rawData().
value(property).
toInt() > s2.rawData().value(property).toInt();
2368 std::stable_sort(offers.
begin(), offers.
end(), cmp);
2370 if (SettingsCore::chooseGenerators()) {
2372 for (
int i = 0; i < offercount; ++i) {
2373 list << offers.
at(i).pluginId();
2375 ChooseEngineDialog choose(list, type, widget);
2381 hRank = choose.selectedGenerator();
2384 Q_ASSERT(hRank < offers.
size());
2385 return offers.
at(hRank);
2403 bool triedMimeFromFileContent =
false;
2410 d->m_docFileName = docFile;
2412 if (!d->updateMetadataXmlNameAndDocSize()) {
2419 qWarning() <<
"failed to read" << url << filedata;
2428 d->m_docSize = filedata.
size();
2429 triedMimeFromFileContent =
true;
2432 const bool fromFileDescriptor = fd >= 0;
2436 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2437 if (!offer.
isValid() && !triedMimeFromFileContent) {
2439 triedMimeFromFileContent =
true;
2440 if (newmime != mime) {
2442 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2450 if (!newmime.
isDefault() && newmime != mime) {
2452 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2457 d->m_openError =
i18n(
"Can not find a plugin which is able to handle the document being passed.");
2459 qCWarning(OkularCoreDebug).nospace() <<
"No plugin for mimetype '" << mime.
name() <<
"'.";
2464 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2465 if (openResult == OpenError) {
2467 triedOffers << offer;
2468 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2470 while (offer.isValid()) {
2471 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2473 if (openResult == OpenError) {
2474 triedOffers << offer;
2475 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2481 if (openResult == OpenError && !triedMimeFromFileContent) {
2483 triedMimeFromFileContent =
true;
2484 if (newmime != mime) {
2486 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2487 while (offer.isValid()) {
2488 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2490 if (openResult == OpenError) {
2491 triedOffers << offer;
2492 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2500 if (openResult == OpenSuccess) {
2508 if (openResult != OpenSuccess) {
2514 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(docFile).constData(),
nullptr, 1);
2516 d->loadSyncFile(docFile);
2519 d->m_generatorName = offer.
pluginId();
2520 d->m_pageController =
new PageController();
2521 connect(d->m_pageController, &PageController::rotationFinished,
this, [
this](
int p,
Okular::Page *op) { d->rotationFinished(p, op); });
2523 for (
Page *p : std::as_const(d->m_pagesVector)) {
2527 d->m_docdataMigrationNeeded =
false;
2530 if (d->m_archiveData) {
2532 d->m_archiveData->metadataFile.fileName();
2533 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2534 d->loadDocumentInfo(LoadGeneralInfo);
2536 if (d->loadDocumentInfo(LoadPageInfo)) {
2537 d->m_docdataMigrationNeeded =
true;
2539 d->loadDocumentInfo(LoadGeneralInfo);
2542 d->m_bookmarkManager->setUrl(d->m_url);
2549 if (loadedViewport.
isValid()) {
2551 if (loadedViewport.
pageNumber >= (
int)d->m_pagesVector.size()) {
2552 loadedViewport.
pageNumber = d->m_pagesVector.size() - 1;
2560 if (!d->m_saveBookmarksTimer) {
2561 d->m_saveBookmarksTimer =
new QTimer(
this);
2564 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2567 if (!d->m_memCheckTimer) {
2568 d->m_memCheckTimer =
new QTimer(
this);
2571 d->m_memCheckTimer->start(kMemCheckTime);
2577 d->m_nextDocumentDestination =
QString();
2582 const QStringList docScripts = d->m_generator->metaData(QStringLiteral(
"DocumentScripts"), QStringLiteral(
"JavaScript")).toStringList();
2584 d->m_scripter =
new Scripter(d);
2585 for (
const QString &docscript : docScripts) {
2587 std::shared_ptr<Event>
event = Event::createDocEvent(Event::DocOpen);
2588 d->executeScriptEvent(
event, linkScript);
2595bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2599 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2603 m_docSize = fileReadTest.size();
2606 if (m_url.isLocalFile()) {
2607 const QString filePath = docDataFileName(m_url, m_docSize);
2608 qCDebug(OkularCoreDebug) <<
"Metadata file is now:" << filePath;
2609 m_xmlFileName = filePath;
2611 qCDebug(OkularCoreDebug) <<
"Metadata file: disabled";
2620 if (d->m_generator) {
2632 if (!d->m_generator) {
2636 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(CloseDocument)) {
2642 delete d->m_pageController;
2643 d->m_pageController =
nullptr;
2645 delete d->m_scripter;
2646 d->m_scripter =
nullptr;
2649 d->clearAndWaitForRequests();
2651 if (d->m_fontThread) {
2652 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2653 d->m_fontThread->stopExtraction();
2654 d->m_fontThread->wait();
2655 d->m_fontThread =
nullptr;
2662 if (d->m_generator && d->m_pagesVector.size() > 0) {
2663 d->saveDocumentInfo();
2671 for (
const Page *p : std::as_const(d->m_pagesVector)) {
2675 const Action *a =
static_cast<const Action *
>(oRect->object());
2676 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2677 if (backendAction) {
2678 d->m_generator->freeOpaqueActionContents(*backendAction);
2686 for (
const Action *a : additionalActions) {
2687 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2688 if (backendAction) {
2689 d->m_generator->freeOpaqueActionContents(*backendAction);
2695 d->m_generator->closeDocument();
2698 if (d->m_synctex_scanner) {
2699 synctex_scanner_free(d->m_synctex_scanner);
2700 d->m_synctex_scanner =
nullptr;
2704 if (d->m_memCheckTimer) {
2705 d->m_memCheckTimer->stop();
2707 if (d->m_saveBookmarksTimer) {
2708 d->m_saveBookmarksTimer->stop();
2711 if (d->m_generator) {
2713 d->m_generator->d_func()->m_document =
nullptr;
2715 disconnect(d->m_generator,
nullptr,
this,
nullptr);
2718 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2720 d->m_generator =
nullptr;
2721 d->m_generatorName =
QString();
2723 d->m_walletGenerator =
nullptr;
2726 delete d->m_tempFile;
2727 d->m_tempFile =
nullptr;
2728 delete d->m_archiveData;
2729 d->m_archiveData =
nullptr;
2731 d->m_exportCached =
false;
2732 d->m_exportFormats.clear();
2734 d->m_fontsCached =
false;
2735 d->m_fontsCache.clear();
2744 for (; pIt != pEnd; ++pIt) {
2747 d->m_pagesVector.clear();
2750 qDeleteAll(d->m_allocatedPixmaps);
2751 d->m_allocatedPixmaps.clear();
2756 for (; rIt != rEnd; ++rIt) {
2759 d->m_searches.clear();
2764 for (; vIt != vEnd; ++vIt) {
2767 d->m_pageRects.clear();
2768 foreachObserver(notifyVisibleRectsChanged());
2772 d->m_viewportHistory.clear();
2773 d->m_viewportHistory.emplace_back();
2774 d->m_viewportIterator = d->m_viewportHistory.begin();
2775 d->m_allocatedPixmapsTotalMemory = 0;
2776 d->m_allocatedTextPagesFifo.clear();
2778 d->m_pageSizes.clear();
2781 d->m_documentInfoAskedKeys.clear();
2785 d->m_undoStack->clear();
2786 d->m_docdataMigrationNeeded =
false;
2798 Q_ASSERT(!d->m_observers.contains(pObserver));
2799 d->m_observers << pObserver;
2802 if (!d->m_pagesVector.isEmpty()) {
2811 if (d->m_observers.contains(pObserver)) {
2814 for (; it != end; ++it) {
2815 (*it)->deletePixmap(pObserver);
2819 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2820 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2821 while (aIt != aEnd) {
2822 AllocatedPixmap *p = *aIt;
2823 if (p->observer == pObserver) {
2824 aIt = d->m_allocatedPixmaps.erase(aIt);
2831 for (
PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
2832 if (executingRequest->observer() == pObserver) {
2833 d->cancelRenderingBecauseOf(executingRequest,
nullptr);
2838 d->m_observers.remove(pObserver);
2845 bool configchanged =
false;
2846 if (d->m_generator) {
2852 if (configchanged) {
2855 for (; it != end; ++it) {
2856 (*it)->deletePixmaps();
2860 qDeleteAll(d->m_allocatedPixmaps);
2861 d->m_allocatedPixmaps.clear();
2862 d->m_allocatedPixmapsTotalMemory = 0;
2869 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2870 d->cleanupPixmapMemory();
2876 return d->m_generator;
2881 if (d->m_generator) {
2883 return iface ? true :
false;
2891 if (d->m_generator->canSign()) {
2892 return d->m_generator->sign(data, newPath);
2900 return d->m_generator ? d->m_generator->certificateStore() :
nullptr;
2905 d->editorCommandOverride = editCmd;
2910 return d->editorCommandOverride;
2928 if (d->m_generator && !missingKeys.
isEmpty()) {
2929 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2940 const QString pagesSize = d->pagesSizeString();
2950 d->m_documentInfo.d->values.insert(info.d->values);
2951 d->m_documentInfo.d->titles.insert(info.d->titles);
2952 result.d->values.insert(info.d->values);
2953 result.d->titles.insert(info.d->titles);
2955 d->m_documentInfoAskedKeys += keys;
2962 return d->m_generator ? d->m_generator->generateDocumentSynopsis() :
nullptr;
2967 if (!d->m_generator || !d->m_generator->hasFeature(
Generator::FontInfo) || d->m_fontThread) {
2971 if (d->m_fontsCached) {
2975 for (
int i = 0; i < d->m_fontsCache.count(); ++i) {
2983 d->m_fontThread =
new FontExtractionThread(d->m_generator,
pages());
2984 connect(d->m_fontThread, &FontExtractionThread::gotFont,
this, [
this](
const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2985 connect(d->m_fontThread.data(), &FontExtractionThread::progress,
this, [
this](
int p) { d->slotFontReadingProgress(p); });
2987 d->m_fontThread->startExtraction(
true);
2992 if (!d->m_fontThread) {
2996 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2997 d->m_fontThread->stopExtraction();
2998 d->m_fontThread =
nullptr;
2999 d->m_fontsCache.clear();
3009 return d->m_generator ? d->m_generator->canSign() :
false;
3014 return d->m_generator ? d->m_generator->embeddedFiles() :
nullptr;
3019 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) :
nullptr;
3024 return (*d->m_viewportIterator);
3029 return d->m_pageRects;
3036 for (; vIt != vEnd; ++vIt) {
3042 if (o != excludeObserver) {
3043 o->notifyVisibleRectsChanged();
3050 return (*d->m_viewportIterator).pageNumber;
3055 return d->m_pagesVector.size();
3065 if (action ==
Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3072#if !OKULAR_FORCE_DRM
3078 return d->m_generator ? d->m_generator->isAllowed(action) :
false;
3098 if (d->m_generator) {
3099 if (d->m_pageSizes.isEmpty()) {
3100 d->m_pageSizes = d->m_generator->pageSizes();
3102 return d->m_pageSizes;
3109 if (!d->m_generator) {
3113 d->cacheExportFormats();
3114 return !d->m_exportToText.isNull();
3119 if (!d->m_generator) {
3123 d->cacheExportFormats();
3124 if (d->m_exportToText.isNull()) {
3128 return d->m_generator->exportTo(fileName, d->m_exportToText);
3133 if (!d->m_generator) {
3137 d->cacheExportFormats();
3138 return d->m_exportFormats;
3143 return d->m_generator ? d->m_generator->exportTo(fileName, format) :
false;
3148 return d->m_viewportIterator == d->m_viewportHistory.begin();
3153 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3170 name = reference.
mid(4);
3172 int nameLength = name.
length();
3174 for (i = 0; i < nameLength; ++i) {
3175 if (!name[i].isDigit()) {
3179 lineString = name.
left(i);
3183 lineString = lineString.
trimmed();
3186 int line = lineString.
toInt(&ok);
3193 synctex_node_p node;
3196 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3203 const QSizeF dpi = d->m_generator->dpi();
3206 double px = (synctex_node_visible_h(node) * dpi.
width()) / 72.27;
3207 double py = (synctex_node_visible_v(node) * dpi.
height()) / 72.27;
3218 return d->m_generator ? d->m_generator->metaData(key, option) :
QVariant();
3223 return d->m_rotation;
3228 bool allPagesSameSize =
true;
3230 for (
int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3231 const Page *p = d->m_pagesVector.at(i);
3238 if (allPagesSameSize) {
3247 if (d->m_generator) {
3249 const Page *p = d->m_pagesVector.at(
page);
3282 if (executingRequest.
width() != otherRequest.
width()) {
3287 if (executingRequest.
height() != otherRequest.
height()) {
3292 if (executingRequest.
isTile() != otherRequest.
isTile()) {
3297 if (executingRequest.
isTile()) {
3310 if (!executingRequest->d->mResultImage.isNull()) {
3318 TilesManager *tm = executingRequest->d->tilesManager();
3320 tm->setPixmap(
nullptr, executingRequest->
normalizedRect(),
true );
3323 PagePrivate::PixmapObject
object = executingRequest->
page()->d->m_pixmaps.take(executingRequest->
observer());
3324 delete object.m_pixmap;
3326 if (executingRequest->d->mShouldAbortRender != 0) {
3330 executingRequest->d->mShouldAbortRender = 1;
3332 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->
page()) {
3333 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3350 if (!d->m_pageController) {
3352 qDeleteAll(requests);
3364 Q_ASSERT(request->
observer() == requesterObserver);
3369 d->m_pixmapRequestsMutex.lock();
3370 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3371 while (sIt != sEnd) {
3372 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.
contains((*sIt)->pageNumber()))) {
3375 sIt = d->m_pixmapRequestsStack.erase(sIt);
3384 qCDebug(OkularCoreDebug).nospace() <<
"request observer=" << request->
observer() <<
" " << request->
width() <<
"x" << request->
height() <<
"@" << request->
pageNumber();
3385 if (d->m_pagesVector.value(request->
pageNumber()) ==
nullptr) {
3391 request->d->mPage = d->m_pagesVector.value(request->
pageNumber());
3399 while (tIt != tEnd) {
3400 const Tile &tile = *tIt;
3402 if (tilesRect.
isNull()) {
3403 tilesRect = tile.
rect();
3405 tilesRect |= tile.
rect();
3416 request->d->mPriority = 0;
3422 for (
PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
3423 bool newRequestsContainExecutingRequestPage =
false;
3424 bool requestCancelled =
false;
3427 newRequestsContainExecutingRequestPage =
true;
3430 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3431 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3436 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->
observer() && !newRequestsContainExecutingRequestPage) {
3437 requestCancelled = d->cancelRenderingBecauseOf(executingRequest,
nullptr);
3440 if (requestCancelled) {
3441 observersPixmapCleared << executingRequest->
observer();
3451 d->m_pixmapRequestsStack.push_back(request);
3454 sIt = d->m_pixmapRequestsStack.begin();
3455 sEnd = d->m_pixmapRequestsStack.end();
3456 while (sIt != sEnd && (*sIt)->priority() > request->
priority()) {
3459 d->m_pixmapRequestsStack.insert(sIt, request);
3462 d->m_pixmapRequestsMutex.unlock();
3469 d->sendGeneratorPixmapRequest();
3478 Page *kp = d->m_pagesVector[pageNumber];
3479 if (!d->m_generator || !kp) {
3485 d->m_generator->generateTextPage(kp);
3488void DocumentPrivate::notifyAnnotationChanges(
int page)
3493void DocumentPrivate::notifyFormChanges(
int )
3503 annotation->d_ptr->baseTransform(t.
inverted());
3505 d->m_undoStack->push(uc);
3522 switch (annotation->
subType()) {
3538 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3539 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3540 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3548 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3549 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3550 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3553 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3555 d->m_undoStack->push(uc);
3556 d->m_prevPropsOfAnnotBeingModified.clear();
3562 QUndoCommand *uc =
new Okular::TranslateAnnotationCommand(d, annotation,
page, delta, complete);
3563 d->m_undoStack->push(uc);
3569 QUndoCommand *uc =
new Okular::AdjustAnnotationCommand(d, annotation,
page, delta1, delta2, complete);
3570 d->m_undoStack->push(uc);
3576 QUndoCommand *uc =
new EditAnnotationContentsCommand(d, annotation,
page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3577 d->m_undoStack->push(uc);
3590 switch (annotation->
subType()) {
3607 d->m_undoStack->push(uc);
3612 d->m_undoStack->beginMacro(
i18nc(
"remove a collection of annotations from the page",
"remove annotations"));
3615 d->m_undoStack->push(uc);
3617 d->m_undoStack->endMacro();
3620bool DocumentPrivate::canAddAnnotationsNatively()
const
3631bool DocumentPrivate::canModifyExternalAnnotations()
const
3642bool DocumentPrivate::canRemoveExternalAnnotations()
const
3656 if (!d->m_generator || !kp) {
3662 kp->d->setTextSelections(*rect, color);
3664 kp->d->deleteTextSelections();
3673 return d->m_undoStack->canUndo();
3678 return d->m_undoStack->canRedo();
3714 const int oldPageNumber = oldViewport.
pageNumber;
3722 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3725 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3726 d->m_viewportHistory.pop_front();
3730 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
viewport);
3733 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3735 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3739 if (o != excludeObserver) {
3740 o->notifyViewportChanged(smoothMove);
3743 if (currentPageChanged) {
3744 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3754 }
else if (
page > (
int)d->m_pagesVector.count()) {
3755 page = d->m_pagesVector.count() - 1;
3766 if (o != excludeObserver) {
3767 o->notifyZoom(factor);
3775 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3776 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3779 --d->m_viewportIterator;
3780 foreachObserver(notifyViewportChanged(
true));
3782 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3783 if (oldViewportPage != currentViewportPage)
3784 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3791 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3793 if (nextIterator != d->m_viewportHistory.end()) {
3794 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3797 ++d->m_viewportIterator;
3798 foreachObserver(notifyViewportChanged(
true));
3800 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3801 if (oldViewportPage != currentViewportPage)
3802 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3808 d->m_nextDocumentViewport =
viewport;
3813 d->m_nextDocumentDestination = namedDestination;
3818 d->m_searchCancelled =
false;
3828 if (searchIt == d->m_searches.end()) {
3829 RunningSearch *search =
new RunningSearch();
3830 search->continueOnPage = -1;
3831 searchIt = d->m_searches.insert(searchID, search);
3833 RunningSearch *s = *searchIt;
3836 bool newText = text != s->cachedString;
3837 s->cachedString = text;
3838 s->cachedType = type;
3839 s->cachedCaseSensitivity = caseSensitivity;
3840 s->cachedViewportMove = moveViewport;
3841 s->cachedColor = color;
3842 s->isCurrentlySearching =
true;
3848 *pagesToNotify += s->highlightedPages;
3849 for (
const int pageNumber : std::as_const(s->highlightedPages)) {
3850 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3852 s->highlightedPages.
clear();
3862 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3869 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3870 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3871 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3872 const Page *lastPage = fromStart ? nullptr : d->m_pagesVector[
currentPage];
3877 if (lastPage && lastPage->number() == s->continueOnPage) {
3879 match = lastPage->findText(searchID, text, forward ?
FromTop :
FromBottom, caseSensitivity);
3881 match = lastPage->findText(searchID, text, forward ?
NextResult :
PreviousResult, caseSensitivity, &s->continueOnMatch);
3893 s->pagesDone = pagesDone;
3895 DoContinueDirectionMatchSearchStruct *searchStruct =
new DoContinueDirectionMatchSearchStruct();
3896 searchStruct->pagesToNotify = pagesToNotify;
3897 searchStruct->match = match;
3899 searchStruct->searchID = searchID;
3901 QTimer::singleShot(0,
this, [
this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3909 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3917 if (it == d->m_searches.constEnd()) {
3923 RunningSearch *p = *it;
3924 if (!p->isCurrentlySearching) {
3925 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3933 if (it == d->m_searches.constEnd()) {
3939 RunningSearch *p = *it;
3940 if (!p->isCurrentlySearching) {
3941 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3948 if (!d->m_generator) {
3954 if (searchIt == d->m_searches.end()) {
3959 RunningSearch *s = *searchIt;
3962 for (
const int pageNumber : std::as_const(s->highlightedPages)) {
3963 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3968 foreachObserver(notifySetup(d->m_pagesVector, 0));
3971 d->m_searches.erase(searchIt);
3977 d->m_searchCancelled =
true;
3982 d->m_undoStack->undo();
3987 d->m_undoStack->redo();
3992 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->
text(), prevCursorPos, prevAnchorPos);
3993 d->m_undoStack->push(uc);
3998 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, oldContents, prevCursorPos, prevAnchorPos);
3999 d->m_undoStack->push(uc);
4005 QUndoCommand *uc =
new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
4006 d->m_undoStack->push(uc);
4018 QUndoCommand *uc =
new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4019 d->m_undoStack->push(uc);
4024 QUndoCommand *uc =
new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4025 d->m_undoStack->push(uc);
4030 const int numOfPages =
pages();
4032 d->refreshPixmaps(i);
4034 for (
int i =
currentPage() + 1; i < numOfPages; i++) {
4035 d->refreshPixmaps(i);
4041 return d->m_bookmarkManager;
4047 uint docPages =
pages();
4050 for (uint i = 0; i < docPages; i++) {
4063 uint docPages =
pages();
4067 for (uint i = 0; i < docPages; ++i) {
4077 }
else if (startId >= 0 && endId >= 0) {
4082 if (endId - startId > 0) {
4083 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4091 if (startId >= 0 && endId >= 0) {
4096 if (endId - startId > 0) {
4097 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4108 explicit ExecuteNextActionsHelper(
Document *doc)
4112 connect(doc, &Document::aboutToClose,
this, [
this] { b =
false; });
4115 ~ExecuteNextActionsHelper()
override
4117 m_doc->removeObserver(
this);
4122 if (setupFlags == DocumentChanged || setupFlags == UrlChanged) {
4127 bool shouldExecuteNextAction()
const
4144 const ExecuteNextActionsHelper executeNextActionsHelper(
this);
4162 if (go->
isExternal() && !d->openRelativeFile(filename)) {
4163 qCWarning(OkularCoreDebug).nospace() <<
"Action: Error opening '" << filename <<
"'.";
4168 if (!nextViewport.
isValid()) {
4174 d->m_nextDocumentDestination =
QString();
4183 d->openRelativeFile(fileName);
4189 QUrl url = d->giveAbsoluteUrl(fileName);
4202 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4208 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4216 connect(job, &KIO::OpenUrlJob::result,
this, [
this, mime](
KJob *job) {
4218 Q_EMIT error(
i18n(
"No application found for opening file of mimetype %1.", mime.
name()), -1);
4230 if ((*d->m_viewportIterator).pageNumber > 0) {
4235 if ((*d->m_viewportIterator).pageNumber < (
int)d->m_pagesVector.count() - 1) {
4278 int lilyRow = 0, lilyCol = 0;
4282 }
else if (extractLilyPondSourceReference(browse->
url(), &lilySource, &lilyRow, &lilyCol)) {
4286 const QUrl url = browse->
url();
4290 d->openRelativeFile(url.
fileName());
4296 if (d->m_url.isValid()) {
4316 if (!d->m_scripter) {
4317 d->m_scripter =
new Scripter(d);
4328 if (!d->m_scripter) {
4329 d->m_scripter =
new Scripter(d);
4331 d->m_scripter->execute(linkrendition->
scriptType(), linkrendition->
script());
4337 const BackendOpaqueAction *backendOpaqueAction =
static_cast<const BackendOpaqueAction *
>(action);
4338 Okular::BackendOpaqueAction::OpaqueActionResult res = d->m_generator->opaqueAction(backendOpaqueAction);
4339 if (res & Okular::BackendOpaqueAction::RefreshForms) {
4340 for (
const Page *p : std::as_const(d->m_pagesVector)) {
4347 d->refreshPixmaps(p->number());
4353 if (executeNextActionsHelper.shouldExecuteNextAction()) {
4355 for (
const Action *a : nextActions) {
4369 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for formatting.";
4374 int foundPage = d->findFieldPageNumber(ff);
4376 if (foundPage == -1) {
4377 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4383 std::shared_ptr<Event>
event = Event::createFormatEvent(ff, d->m_pagesVector[foundPage]);
4387 d->executeScriptEvent(
event, linkscript);
4389 const QString formattedText =
event->value().toString();
4391 if (formattedText != unformattedText) {
4397 d->refreshPixmaps(foundPage);
4407 d->refreshPixmaps(foundPage);
4411QString DocumentPrivate::evaluateKeystrokeEventChange(
const QString &oldVal,
const QString &newVal,
int selStart,
int selEnd)
4421 if (selStart < 0 || selEnd < 0 || (selEnd - selStart) + (
static_cast<int>(newUcs4.size()) -
static_cast<int>(oldUcs4.size())) < 0) {
4425 const size_t changeLength = (selEnd - selStart) + (newUcs4.size() - oldUcs4.size());
4426 auto subview = std::u32string_view {newUcs4}.substr(selStart, changeLength);
4427 if (subview.empty()) {
4438 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4442 int foundPage = d->findFieldPageNumber(ff);
4444 if (foundPage == -1) {
4445 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4449 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4464 int selStart = std::min(prevCursorPos, prevAnchorPos);
4465 int selEnd = std::max(prevCursorPos, prevAnchorPos);
4467 int initialSelStart = selStart;
4468 int initialSelEnd = selEnd;
4470 for (codeUnit = 0; codeUnit < initialSelStart && codeUnit < inputString.
size(); codeUnit++) {
4478 for (; codeUnit < initialSelEnd && codeUnit < inputString.
size(); codeUnit++) {
4488 if (oldUcs4.size() - newUcs4.size() == 1 && selStart == selEnd) {
4492 event->setSelStart(selStart);
4493 event->setSelEnd(selEnd);
4495 event->setChange(DocumentPrivate::evaluateKeystrokeEventChange(inputString, newValue.
toString(), selStart, selEnd));
4498 d->executeScriptEvent(
event, linkscript);
4500 if (
event->returnCode()) {
4515 bool returnCode =
false;
4522 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4526 int foundPage = d->findFieldPageNumber(ff);
4528 if (foundPage == -1) {
4529 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4533 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4534 event->setWillCommit(
true);
4538 d->executeScriptEvent(
event, linkscript);
4540 if (!
event->returnCode()) {
4548 returnCode =
event->returnCode();
4558 int foundPage = d->findFieldPageNumber(field);
4560 if (foundPage == -1) {
4561 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4565 std::shared_ptr<Event>
event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4569 d->executeScriptEvent(
event, linkscript);
4584 int foundPage = d->findFieldPageNumber(ff);
4586 if (foundPage == -1) {
4587 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4591 std::shared_ptr<Event>
event = Event::createFormValidateEvent(ff, d->m_pagesVector[foundPage]);
4595 d->executeScriptEvent(
event, linkscript);
4596 if (!
event->returnCode()) {
4604 returnCode =
event->returnCode();
4616 bool returnCode =
true;
4646 Event::EventType eventType = Okular::Event::UnknownEvent;
4649 case Document::CloseDocument:
4650 eventType = Okular::Event::DocWillClose;
4652 case Document::SaveDocumentStart:
4653 eventType = Okular::Event::DocWillSave;
4655 case Document::SaveDocumentFinish:
4656 eventType = Okular::Event::DocDidSave;
4658 case Document::PrintDocumentStart:
4659 eventType = Okular::Event::DocWillPrint;
4661 case Document::PrintDocumentFinish:
4662 eventType = Okular::Event::DocDidPrint;
4666 std::shared_ptr<Event>
event = Event::createDocEvent(eventType);
4670 d->executeScriptEvent(
event, linkScript);
4679 Okular::Event::EventType eventType = Okular::Event::UnknownEvent;
4681 switch (fieldMouseEventType) {
4682 case Document::FieldMouseDown:
4683 eventType = Okular::Event::FieldMouseDown;
4686 eventType = Okular::Event::FieldMouseEnter;
4689 eventType = Okular::Event::FieldMouseExit;
4692 eventType = Okular::Event::FieldMouseUp;
4697 int foundPage = d->findFieldPageNumber(ff);
4699 if (foundPage == -1) {
4700 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4704 std::shared_ptr<Event>
event = Event::createFieldMouseEvent(ff, d->m_pagesVector[foundPage], eventType);
4708 d->executeScriptEvent(
event, linkscript);
4724 qCDebug(OkularCoreDebug) << url.
url() <<
"is not a local file.";
4730 qCDebug(OkularCoreDebug) <<
"No such file:" << absFileName;
4734 bool handled =
false;
4743 editors = buildEditorsMap();
4747 QString p = d->editorCommandOverride;
4753 p = SettingsCore::externalEditorCommand();
4792 if (!d->m_synctex_scanner) {
4796 const QSizeF dpi = d->m_generator->dpi();
4798 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.
width(), absY * 72. / dpi.
height()) > 0) {
4799 synctex_node_p node;
4801 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4802 int line = synctex_node_line(node);
4803 int col = synctex_node_column(node);
4808 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4818 if (d->m_generator) {
4840 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentStart)) {
4843 const Document::PrintError printError = d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4845 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentFinish)) {
4855 case TemporaryFileOpenPrintError:
4856 return i18n(
"Could not open a temporary file");
4857 case FileConversionPrintError:
4858 return i18n(
"Print conversion failed");
4859 case PrintingProcessCrashPrintError:
4860 return i18n(
"Printing process crashed");
4861 case PrintingProcessStartPrintError:
4862 return i18n(
"Printing process could not start");
4863 case PrintToFilePrintError:
4864 return i18n(
"Printing to file failed");
4865 case InvalidPrinterStatePrintError:
4866 return i18n(
"Printer was in invalid state");
4867 case UnableToFindFilePrintError:
4868 return i18n(
"Unable to find file to print");
4869 case NoFileToPrintError:
4870 return i18n(
"There was no file to print");
4871 case NoBinaryToPrintError:
4872 return i18n(
"Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4873 case InvalidPageSizePrintError:
4874 return i18n(
"The page print size is invalid");
4877 case UnknownPrintError:
4886 if (d->m_generator) {
4901 BackendConfigDialog *bcd =
dynamic_cast<BackendConfigDialog *
>(dialog);
4908 d->loadServiceList(offers);
4916 for (; it != itEnd; ++it) {
4917 sortedGenerators.
insert(it.key(), it.value());
4920 bool pagesAdded =
false;
4923 for (; sit != sitEnd; ++sit) {
4929 if (sit.value().generator == d->m_generator) {
4930 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4946 if (md.rawData().value(QStringLiteral(
"X-KDE-okularHasInternalSettings")).toBool()) {
4955 if (!d->m_generator) {
4959 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4960 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4961 return genIt.value().metadata;
4966 return DocumentPrivate::configurableGenerators().size();
4976 result << md.mimeTypes();
4982 for (
const QString &mimeName : std::as_const(result)) {
4986 for (
const QMimeType &mimeType : uniqueMimetypes) {
4987 result.
append(mimeType.name());
4991 result << QStringLiteral(
"application/vnd.kde.okular-archive");
4995 std::sort(result.
begin(), result.
end());
4997 d->m_supportedMimeTypes = result;
5004 if (!d->m_generator) {
5013 if (!d->m_generator) {
5022 d->saveDocumentInfo();
5024 d->clearAndWaitForRequests();
5026 qCDebug(OkularCoreDebug) <<
"Swapping backing file to" << newFileName;
5029 if (result != Generator::SwapBackingFileError) {
5034 if (result == Generator::SwapBackingFileReloadInternalData) {
5040 if (newPagesVector.
count() != d->m_pagesVector.count()) {
5045 for (
int i = 0; i < d->m_undoStack->count(); ++i) {
5048 if (OkularUndoCommand *ouc =
dynamic_cast<OkularUndoCommand *
>(uc)) {
5049 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
5051 qWarning() <<
"Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
5055 qWarning() <<
"Document::swapBackingFile: Unhandled undo command" << uc;
5060 for (
int i = 0; i < d->m_pagesVector.count(); ++i) {
5064 Page *oldPage = d->m_pagesVector[i];
5065 Page *newPage = newPagesVector[i];
5066 newPage->d->adoptGeneratedContents(oldPage->d);
5068 pagePrivatesToDelete << oldPage->d;
5069 oldPage->d = newPage->d;
5070 oldPage->d->m_page = oldPage;
5071 oldPage->d->m_doc = d;
5072 newPage->d =
nullptr;
5074 annotationsToDelete << oldPage->m_annotations;
5075 rectsToDelete << oldPage->m_rects;
5076 oldPage->m_annotations = newPage->m_annotations;
5077 oldPage->m_rects = newPage->m_rects;
5079 qDeleteAll(newPagesVector);
5083 d->m_docFileName = newFileName;
5084 d->updateMetadataXmlNameAndDocSize();
5085 d->m_bookmarkManager->setUrl(d->m_url);
5087 d->m_documentInfoAskedKeys.clear();
5089 if (d->m_synctex_scanner) {
5090 synctex_scanner_free(d->m_synctex_scanner);
5091 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(newFileName).constData(),
nullptr, 1);
5093 d->loadSyncFile(newFileName);
5099 qDeleteAll(annotationsToDelete);
5100 qDeleteAll(rectsToDelete);
5101 qDeleteAll(pagePrivatesToDelete);
5111 qCDebug(OkularCoreDebug) <<
"Swapping backing archive to" << newFileName;
5113 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
5123 delete d->m_archiveData;
5124 d->m_archiveData = newArchive;
5133 d->m_undoStack->setClean();
5135 d->m_undoStack->resetClean();
5139bool Document::isHistoryClean()
const
5141 return d->m_undoStack->isClean();
5146 d->m_undoStack->clear();
5151 if (!d->m_generator) {
5154 Q_ASSERT(!d->m_generatorName.isEmpty());
5157 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5176 return d->canAddAnnotationsNatively();
5190 if (!d->m_generator || fileName.
isEmpty()) {
5193 Q_ASSERT(!d->m_generatorName.isEmpty());
5196 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5202 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentStart)) {
5208 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentFinish)) {
5224 if (viewDoc ==
this) {
5231 d->m_views.insert(view);
5232 view->d_func()->document = d;
5242 if (!viewDoc || viewDoc !=
this) {
5246 view->d_func()->document =
nullptr;
5247 d->m_views.remove(view);
5252 if (d->m_generator) {
5253 return d->m_generator->requestFontData(font);
5259ArchiveData *DocumentPrivate::unpackDocumentArchive(
const QString &archivePath)
5263 if (!mime.
inherits(QStringLiteral(
"application/vnd.kde.okular-archive"))) {
5267 KZip okularArchive(archivePath);
5277 for (
const QString &entry : mainDirEntries) {
5279 qWarning() <<
"Warning: Found a directory inside" << archivePath <<
" - Okular does not create files like that so it is most probably forged.";
5285 if (!mainEntry || !mainEntry->
isFile()) {
5289 std::unique_ptr<QIODevice> mainEntryDevice(
static_cast<const KZipFileEntry *
>(mainEntry)->createDevice());
5291 if (!doc.
setContent(mainEntryDevice.get())) {
5294 mainEntryDevice.reset();
5309 documentFileName = fileEl.
text();
5311 metadataFileName = fileEl.
text();
5316 if (documentFileName.
isEmpty()) {
5321 if (!docEntry || !docEntry->
isFile()) {
5325 std::unique_ptr<ArchiveData> archiveData(
new ArchiveData());
5330 if (!archiveData->document.open()) {
5334 archiveData->originalFileName = documentFileName;
5337 std::unique_ptr<QIODevice> docEntryDevice(
static_cast<const KZipFileEntry *
>(docEntry)->createDevice());
5338 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5339 archiveData->document.close();
5343 if (metadataEntry && metadataEntry->
isFile()) {
5344 std::unique_ptr<QIODevice> metadataEntryDevice(
static_cast<const KZipFileEntry *
>(metadataEntry)->createDevice());
5346 if (archiveData->metadataFile.open()) {
5347 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5348 archiveData->metadataFile.close();
5352 return archiveData.release();
5357 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5358 if (!d->m_archiveData) {
5362 const QString tempFileName = d->m_archiveData->document.fileName();
5367 if (ret != OpenSuccess) {
5368 delete d->m_archiveData;
5369 d->m_archiveData =
nullptr;
5377 if (!d->m_generator) {
5383 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
5388 QString docPath = d->m_docFileName;
5394 KZip okularArchive(fileName);
5403 const KUserGroup userGroup(QStringLiteral(
""));