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;
952 const QWindow *
window = m_widget && m_widget->window() ? m_widget->window()->windowHandle() :
nullptr;
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) {
1208 std::shared_ptr<Event>
event;
1212 event = Event::createFormCalculateEvent(form, m_pagesVector[pageIdx]);
1214 executeScriptEvent(event, linkscript);
1216 oldVal = form->value().toString();
1220 const QString newVal =
event->value().toString();
1221 if (newVal != oldVal) {
1223 form->setAppearanceValue(
QVariant(newVal));
1224 bool returnCode =
true;
1230 m_parent->processValidateAction(action, form, returnCode);
1236 form->commitValue(form->value().toString());
1240 m_parent->processFormatAction(action, form);
1242 form->commitFormattedValue(form->value().toString());
1243 Q_EMIT m_parent->refreshFormWidget(form);
1244 pageNeedsRefresh =
true;
1250 qWarning() <<
"Form that is part of calculate order doesn't have a calculate action";
1254 if (pageNeedsRefresh) {
1255 refreshPixmaps(p->number());
1262void DocumentPrivate::saveDocumentInfo()
const
1264 if (m_xmlFileName.isEmpty()) {
1268 QFile infoFile(m_xmlFileName);
1269 qCDebug(OkularCoreDebug) <<
"About to save document info to" << m_xmlFileName;
1271 qCWarning(OkularCoreDebug) <<
"Failed to open docdata file" << m_xmlFileName;
1276 QDomProcessingInstruction xmlPi = doc.createProcessingInstruction(QStringLiteral(
"xml"), QStringLiteral(
"version=\"1.0\" encoding=\"utf-8\""));
1278 QDomElement root = doc.createElement(QStringLiteral(
"documentInfo"));
1280 doc.appendChild(root);
1284 if (m_docdataMigrationNeeded) {
1285 QDomElement pageList = doc.createElement(QStringLiteral(
"pageList"));
1293 const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems;
1296 for (; pIt != pEnd; ++pIt) {
1297 (*pIt)->d->saveLocalContents(pageList, doc, saveWhat);
1302 QDomElement generalInfo = doc.createElement(QStringLiteral(
"generalInfo"));
1306 QDomElement rotationNode = doc.createElement(QStringLiteral(
"rotation"));
1311 const auto currentViewportIterator = std::list<DocumentViewport>::const_iterator(m_viewportIterator);
1312 std::list<DocumentViewport>::const_iterator backIterator = currentViewportIterator;
1313 if (backIterator != m_viewportHistory.end()) {
1315 int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
1316 while (backSteps-- && backIterator != m_viewportHistory.begin()) {
1321 QDomElement historyNode = doc.createElement(QStringLiteral(
"history"));
1325 std::list<DocumentViewport>::const_iterator endIt = currentViewportIterator;
1327 while (backIterator != endIt) {
1328 QString name = (backIterator == currentViewportIterator) ? QStringLiteral(
"current") : QStringLiteral(
"oldPage");
1329 QDomElement historyEntry = doc.createElement(name);
1330 historyEntry.
setAttribute(QStringLiteral(
"viewport"), (*backIterator).toString());
1336 QDomElement viewsNode = doc.createElement(QStringLiteral(
"views"));
1338 for (
View *view : std::as_const(m_views)) {
1339 QDomElement viewEntry = doc.createElement(QStringLiteral(
"view"));
1340 viewEntry.
setAttribute(QStringLiteral(
"name"), view->name());
1342 saveViewsInfo(view, viewEntry);
1353void DocumentPrivate::slotTimedMemoryCheck()
1356 if (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024 * 1024) {
1357 cleanupPixmapMemory();
1361void DocumentPrivate::sendGeneratorPixmapRequest()
1367 const qulonglong memoryToFree = calculateMemoryToFree();
1368 const int currentViewportPage = (*m_viewportIterator).pageNumber;
1369 int maxDistance = INT_MAX;
1371 const AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap(
true);
1372 if (pixmapToReplace) {
1373 maxDistance = qAbs(pixmapToReplace->page - currentViewportPage);
1379 m_pixmapRequestsMutex.lock();
1380 while (!m_pixmapRequestsStack.empty() && !request) {
1383 m_pixmapRequestsStack.pop_back();
1388 TilesManager *tilesManager = r->d->tilesManager();
1390 const QScreen *screen =
nullptr;
1407 m_pixmapRequestsStack.pop_back();
1412 m_pixmapRequestsStack.pop_back();
1414 }
else if (!r->d->mForce && r->
preload() && qAbs(r->
pageNumber() - currentViewportPage) >= maxDistance) {
1415 m_pixmapRequestsStack.pop_back();
1421 m_pixmapRequestsStack.pop_back();
1427 qCDebug(OkularCoreDebug).nospace() <<
"Start using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1433 tilesManager->setPixmap(pixmap,
NormalizedRect(0, 0, 1, 1),
true );
1441 r->
page()->d->setTilesManager(r->
observer(), tilesManager);
1450 while (tIt != tEnd) {
1452 if (tilesRect.
isNull()) {
1453 tilesRect = tile.
rect();
1455 tilesRect |= tile.
rect();
1468 m_pixmapRequestsStack.pop_back();
1473 else if (tilesManager && (
long)r->
width() * (
long)r->
height() < 3L * screenSize) {
1474 qCDebug(OkularCoreDebug).nospace() <<
"Stop using tiles on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1481 }
else if ((
long)requestRect.
width() * (
long)requestRect.
height() > 100L * screenSize && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy)) {
1482 m_pixmapRequestsStack.pop_back();
1483 if (!m_warnedOutOfMemory) {
1484 qCWarning(OkularCoreDebug).nospace() <<
"Running out of memory on page " << r->
pageNumber() <<
" (" << r->
width() <<
"x" << r->
height() <<
" px);";
1485 qCWarning(OkularCoreDebug) <<
"this message will be reported only once.";
1486 m_warnedOutOfMemory =
true;
1496 m_pixmapRequestsMutex.unlock();
1501 qulonglong pixmapBytes = 0;
1502 TilesManager *tm = request->d->tilesManager();
1504 pixmapBytes = tm->totalMemory();
1506 pixmapBytes = 4 * request->
width() * request->
height();
1509 if (pixmapBytes > (1024 * 1024)) {
1510 cleanupPixmapMemory(memoryToFree );
1514 if (m_generator->canGeneratePixmap()) {
1515 QRect requestRect = !request->
isTile() ?
QRect(0, 0, request->
width(), request->
height()) : request->normalizedRect().geometry(request->width(), request->height());
1516 qCDebug(OkularCoreDebug).nospace() <<
"sending request observer=" << request->
observer() <<
" " << requestRect.
width() <<
"x" << requestRect.
height() <<
"@" << request->
pageNumber() <<
" async == " << request->
asynchronous()
1517 <<
" isTile == " << request->
isTile();
1518 m_pixmapRequestsStack.remove(request);
1524 if ((
int)m_rotation % 2) {
1540 m_executingPixmapRequests.push_back(request);
1541 m_pixmapRequestsMutex.unlock();
1542 m_generator->generatePixmap(request);
1544 m_pixmapRequestsMutex.unlock();
1550void DocumentPrivate::rotationFinished(
int page,
Okular::Page *okularPage)
1552 const Okular::Page *wantedPage = m_pagesVector.value(page,
nullptr);
1553 if (!wantedPage || wantedPage != okularPage) {
1562void DocumentPrivate::slotFontReadingProgress(
int page)
1564 Q_EMIT m_parent->fontReadingProgress(page);
1566 if (page >= (
int)m_parent->pages() - 1) {
1567 Q_EMIT m_parent->fontReadingEnded();
1568 m_fontThread =
nullptr;
1569 m_fontsCached =
true;
1576 if (m_fontsCache.indexOf(font) == -1) {
1577 m_fontsCache.append(font);
1579 Q_EMIT m_parent->gotFont(font);
1583void DocumentPrivate::slotGeneratorConfigChanged()
1590 bool configchanged =
false;
1592 for (; it != itEnd; ++it) {
1596 if (it_changed && (m_generator == it.value().generator)) {
1597 configchanged =
true;
1601 if (configchanged) {
1604 for (; it !=
end; ++it) {
1605 (*it)->deletePixmaps();
1609 qDeleteAll(m_allocatedPixmaps);
1610 m_allocatedPixmaps.clear();
1611 m_allocatedPixmapsTotalMemory = 0;
1618 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.empty() && !m_pagesVector.isEmpty()) {
1619 cleanupPixmapMemory();
1623void DocumentPrivate::refreshPixmaps(
int pageNumber)
1625 Page *page = m_pagesVector.value(pageNumber,
nullptr);
1632 for (; it != itEnd; ++it) {
1633 const QSize size = (*it).m_pixmap->size();
1635 p->d->mForce =
true;
1636 pixmapsToRequest << p;
1650 TilesManager *tilesManager = page->d->tilesManager(observer);
1652 tilesManager->markDirty();
1654 PixmapRequest *p =
new PixmapRequest(observer, pageNumber, tilesManager->width(), tilesManager->height(), 1 , 1, PixmapRequest::Asynchronous);
1659 for (; vIt != vEnd; ++vIt) {
1660 if ((*vIt)->pageNumber == pageNumber) {
1661 visibleRect = (*vIt)->rect;
1666 if (!visibleRect.
isNull()) {
1669 p->d->mForce =
true;
1680void DocumentPrivate::_o_configChanged()
1683 calculateMaxTextPages();
1684 while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) {
1685 int pageToKick = m_allocatedTextPagesFifo.takeFirst();
1686 m_pagesVector.at(pageToKick)->setTextPage(
nullptr);
1690void DocumentPrivate::doContinueDirectionMatchSearch(
void *doContinueDirectionMatchSearchStruct)
1692 DoContinueDirectionMatchSearchStruct *searchStruct =
static_cast<DoContinueDirectionMatchSearchStruct *
>(doContinueDirectionMatchSearchStruct);
1693 RunningSearch *search = m_searches.value(searchStruct->searchID);
1695 if ((m_searchCancelled && !searchStruct->match) || !search) {
1700 search->isCurrentlySearching =
false;
1703 Q_EMIT m_parent->searchFinished(searchStruct->searchID, Document::SearchCancelled);
1704 delete searchStruct->pagesToNotify;
1705 delete searchStruct;
1709 const bool forward = search->cachedType == Document::NextMatch;
1710 bool doContinue =
false;
1712 if (!searchStruct->match) {
1713 const int pageCount = m_pagesVector.count();
1714 if (search->pagesDone < pageCount) {
1716 if (searchStruct->currentPage >= pageCount) {
1717 searchStruct->currentPage = 0;
1718 Q_EMIT m_parent->notice(
i18n(
"Continuing search from beginning"), 3000);
1719 }
else if (searchStruct->currentPage < 0) {
1720 searchStruct->currentPage = pageCount - 1;
1721 Q_EMIT m_parent->notice(
i18n(
"Continuing search from bottom"), 3000);
1728 const Page *page = m_pagesVector[searchStruct->currentPage];
1730 if (!page->hasTextPage()) {
1731 m_parent->requestTextPage(page->number());
1735 searchStruct->match = page->findText(searchStruct->searchID, search->cachedString, forward ?
FromTop :
FromBottom, search->cachedCaseSensitivity);
1736 if (!searchStruct->match) {
1738 searchStruct->currentPage++;
1740 searchStruct->currentPage--;
1742 search->pagesDone++;
1744 search->pagesDone = 1;
1748 QTimer::singleShot(0, m_parent, [
this, searchStruct] { doContinueDirectionMatchSearch(searchStruct); });
1750 doProcessSearchMatch(searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor);
1751 delete searchStruct;
1755void DocumentPrivate::doProcessSearchMatch(
RegularAreaRect *match, RunningSearch *search,
QSet<int> *pagesToNotify,
int currentPage,
int searchID,
bool moveViewport,
const QColor &color)
1760 bool foundAMatch =
false;
1762 search->isCurrentlySearching =
false;
1768 search->continueOnPage = currentPage;
1769 search->continueOnMatch = *
match;
1770 search->highlightedPages.
insert(currentPage);
1772 m_pagesVector[currentPage]->d->setHighlight(searchID, match, color);
1775 pagesToNotify->
insert(currentPage);
1780 const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible(matchRectWithBuffer, currentPage);
1783 if (moveViewport && !matchRectFullyVisible) {
1785 searchViewport.rePos.enabled =
true;
1786 searchViewport.rePos.normalizedX = (
match->first().left +
match->first().right) / 2.0;
1787 searchViewport.rePos.normalizedY = (
match->first().top +
match->first().bottom) / 2.0;
1788 m_parent->setViewport(searchViewport,
nullptr,
true);
1794 for (
int pageNumber : std::as_const(*pagesToNotify)) {
1801 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1803 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1806 delete pagesToNotify;
1809void DocumentPrivate::doContinueAllDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID)
1813 RunningSearch *search = m_searches.value(searchID);
1815 if (m_searchCancelled || !search) {
1821 search->isCurrentlySearching =
false;
1824 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1825 for (
const MatchesVector &mv : std::as_const(*pageMatches)) {
1829 delete pagesToNotify;
1833 if (currentPage < m_pagesVector.count()) {
1835 Page *page = m_pagesVector.at(currentPage);
1836 int pageNumber = page->number();
1839 if (!page->hasTextPage()) {
1840 m_parent->requestTextPage(pageNumber);
1847 lastMatch = page->findText(searchID, search->cachedString,
NextResult, search->cachedCaseSensitivity, lastMatch);
1849 lastMatch = page->findText(searchID, search->cachedString,
FromTop, search->cachedCaseSensitivity);
1857 (*pageMatches)[page].append(lastMatch);
1861 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID] { doContinueAllDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID); });
1866 search->isCurrentlySearching =
false;
1867 bool foundAMatch = pageMatches->
count() != 0;
1871 for (; it != itEnd; ++it) {
1873 it.
key()->d->setHighlight(searchID, match, search->cachedColor);
1876 search->highlightedPages.
insert(it.
key()->number());
1877 pagesToNotify->
insert(it.
key()->number());
1885 for (
int pageNumber : std::as_const(*pagesToNotify)) {
1892 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
1894 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
1898 delete pagesToNotify;
1902void DocumentPrivate::doContinueGooglesDocumentSearch(
void *pagesToNotifySet,
void *pageMatchesMap,
int currentPage,
int searchID,
const QStringList &words)
1904 typedef QPair<RegularAreaRect *, QColor> MatchColor;
1907 RunningSearch *search = m_searches.value(searchID);
1909 if (m_searchCancelled || !search) {
1915 search->isCurrentlySearching =
false;
1918 Q_EMIT m_parent->searchFinished(searchID, Document::SearchCancelled);
1920 for (
const MatchesVector &mv : std::as_const(*pageMatches)) {
1921 for (
const MatchColor &mc : mv) {
1926 delete pagesToNotify;
1930 const int wordCount = words.
count();
1931 const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
1932 int baseHue, baseSat, baseVal;
1933 search->cachedColor.getHsv(&baseHue, &baseSat, &baseVal);
1935 if (currentPage < m_pagesVector.count()) {
1937 Page *page = m_pagesVector.at(currentPage);
1938 int pageNumber = page->number();
1941 if (!page->hasTextPage()) {
1942 m_parent->requestTextPage(pageNumber);
1946 bool allMatched = wordCount > 0, anyMatched =
false;
1947 for (
int w = 0; w < wordCount; w++) {
1948 const QString &word = words[w];
1949 int newHue = baseHue - w * hueStep;
1956 bool wordMatched =
false;
1959 lastMatch = page->findText(searchID, word,
NextResult, search->cachedCaseSensitivity, lastMatch);
1961 lastMatch = page->findText(searchID, word,
FromTop, search->cachedCaseSensitivity);
1969 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
1972 allMatched = allMatched && wordMatched;
1973 anyMatched = anyMatched || wordMatched;
1977 const bool matchAll = search->cachedType == Document::GoogleAll;
1978 if (!allMatched && matchAll) {
1980 for (
const MatchColor &mc : matches) {
1983 pageMatches->
remove(page);
1986 QTimer::singleShot(0, m_parent, [
this, pagesToNotifySet, pageMatches, currentPage, searchID, words] { doContinueGooglesDocumentSearch(pagesToNotifySet, pageMatches, currentPage + 1, searchID, words); });
1991 search->isCurrentlySearching =
false;
1992 bool foundAMatch = pageMatches->
count() != 0;
1996 for (; it != itEnd; ++it) {
1997 for (
const MatchColor &mc : it.
value()) {
1998 it.
key()->d->setHighlight(searchID, mc.first, mc.second);
2001 search->highlightedPages.
insert(it.
key()->number());
2002 pagesToNotify->
insert(it.
key()->number());
2011 for (
int pageNumber : std::as_const(*pagesToNotify)) {
2018 Q_EMIT m_parent->searchFinished(searchID, Document::MatchFound);
2020 Q_EMIT m_parent->searchFinished(searchID, Document::NoMatchFound);
2024 delete pagesToNotify;
2032 bool giveDefault = option.
toBool();
2034 if ((SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper) && SettingsCore::changeColors()) {
2035 color = SettingsCore::paperColor();
2036 }
else if (giveDefault) {
2043 switch (SettingsCore::textAntialias()) {
2044 case SettingsCore::EnumTextAntialias::Enabled:
2047 case SettingsCore::EnumTextAntialias::Disabled:
2054 switch (SettingsCore::graphicsAntialias()) {
2055 case SettingsCore::EnumGraphicsAntialias::Enabled:
2058 case SettingsCore::EnumGraphicsAntialias::Disabled:
2065 switch (SettingsCore::textHinting()) {
2066 case SettingsCore::EnumTextHinting::Enabled:
2069 case SettingsCore::EnumTextHinting::Disabled:
2078bool DocumentPrivate::isNormalizedRectangleFullyVisible(
const Okular::NormalizedRect &rectOfInterest,
int rectPage)
2080 bool rectFullyVisible =
false;
2085 for (; (vIt != vEnd) && !rectFullyVisible; ++vIt) {
2086 if ((*vIt)->pageNumber == rectPage && (*vIt)->rect.contains(rectOfInterest.
left, rectOfInterest.
top) && (*vIt)->rect.contains(rectOfInterest.
right, rectOfInterest.
bottom)) {
2087 rectFullyVisible =
true;
2090 return rectFullyVisible;
2093struct pdfsyncpoint {
2102void DocumentPrivate::loadSyncFile(
const QString &filePath)
2111 const QString coreName = ts.readLine();
2113 const QString versionstr = ts.readLine();
2118 if (!
match.hasMatch()) {
2124 int currentpage = -1;
2128 fileStack.
push(coreName + texStr);
2130 const QSizeF dpi = m_generator->dpi();
2133 while (!ts.atEnd()) {
2134 line = ts.readLine();
2136 const int tokenSize = tokens.
count();
2137 if (tokenSize < 1) {
2141 int id = tokens.
at(1).toInt();
2147 pt.row = tokens.
at(2).toInt();
2150 pt.file = fileStack.
top();
2154 currentpage = tokens.
at(1).toInt() - 1;
2157 qCDebug(OkularCoreDebug) <<
"PdfSync: 'p*' line ignored";
2159 int id = tokens.
at(1).toInt();
2161 if (it != points.
end()) {
2162 it->x = tokens.
at(2).toInt();
2163 it->y = tokens.
at(3).toInt();
2164 it->page = currentpage;
2173 fileStack.
push(newfile);
2178 qCDebug(OkularCoreDebug) <<
"PdfSync: going one level down too much";
2181 qCDebug(OkularCoreDebug).nospace() <<
"PdfSync: unknown line format: '" << line <<
"'";
2186 for (
const pdfsyncpoint &pt : std::as_const(points)) {
2188 if (pt.page < 0 || pt.page >= m_pagesVector.size()) {
2193 Okular::NormalizedPoint p((pt.x * dpi.
width()) / (72.27 * 65536.0 * m_pagesVector[pt.page]->width()), (pt.y * dpi.
height()) / (72.27 * 65536.0 * m_pagesVector[pt.page]->height()));
2198 for (
int i = 0; i < refRects.size(); ++i) {
2199 if (!refRects.at(i).isEmpty()) {
2200 m_pagesVector[i]->setSourceReferences(refRects.at(i));
2205void DocumentPrivate::clearAndWaitForRequests()
2207 m_pixmapRequestsMutex.lock();
2208 std::list<PixmapRequest *>::const_iterator sIt = m_pixmapRequestsStack.begin();
2209 std::list<PixmapRequest *>::const_iterator sEnd = m_pixmapRequestsStack.end();
2210 for (; sIt != sEnd; ++sIt) {
2213 m_pixmapRequestsStack.clear();
2214 m_pixmapRequestsMutex.unlock();
2217 bool startEventLoop =
false;
2219 m_pixmapRequestsMutex.lock();
2220 startEventLoop = !m_executingPixmapRequests.empty();
2223 for (
PixmapRequest *executingRequest : std::as_const(m_executingPixmapRequests)) {
2224 executingRequest->d->mShouldAbortRender = 1;
2227 if (m_generator->d_ptr->mTextPageGenerationThread) {
2228 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
2232 m_pixmapRequestsMutex.unlock();
2233 if (startEventLoop) {
2234 m_closingLoop = &loop;
2236 m_closingLoop =
nullptr;
2238 }
while (startEventLoop);
2245 for (uint pageIdx = 0, nPages = m_parent->pages(); pageIdx < nPages; pageIdx++) {
2246 const Page *p = m_parent->
page(pageIdx);
2247 if (p && p->formFields().contains(field)) {
2248 foundPage =
static_cast<int>(pageIdx);
2255void DocumentPrivate::executeScriptEvent(
const std::shared_ptr<Event> &event,
const Okular::ScriptAction *linkscript)
2258 m_scripter =
new Scripter(
this);
2264 m_scripter->setEvent(
nullptr);
2269 , d(new DocumentPrivate(this))
2271 d->m_widget = widget;
2273 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
DocumentViewport());
2276 connect(SettingsCore::self(), &SettingsCore::configChanged,
this, [
this] { d->_o_configChanged(); });
2281 qRegisterMetaType<Okular::FontInfo>();
2290 for (; viewIt != viewEnd; ++viewIt) {
2292 v->d_func()->document =
nullptr;
2296 delete d->m_bookmarkManager;
2300 for (; it != itEnd; ++it) {
2301 d->unloadGenerator(it.value());
2303 d->m_loadedGenerators.clear();
2309QString DocumentPrivate::docDataFileName(
const QUrl &url, qint64 document_size)
2316 qCDebug(OkularCoreDebug) <<
"creating docdata folder" << docdataDir;
2321 return newokularfile;
2350 for (
const QString &supported : mimetypes) {
2352 if (mimeType == type && !exactMatches.
contains(md)) {
2356 if (
type.inherits(supported) && !offers.
contains(md)) {
2362 if (!exactMatches.
isEmpty()) {
2363 offers = exactMatches;
2371 int offercount = offers.
size();
2372 if (offercount > 1) {
2375 const QString property = QStringLiteral(
"X-KDE-Priority");
2376 return s1.
rawData().
value(property).
toInt() > s2.rawData().value(property).toInt();
2378 std::stable_sort(offers.
begin(), offers.
end(), cmp);
2380 if (SettingsCore::chooseGenerators()) {
2382 for (
int i = 0; i < offercount; ++i) {
2383 list << offers.
at(i).pluginId();
2385 ChooseEngineDialog choose(list, type, widget);
2391 hRank = choose.selectedGenerator();
2394 Q_ASSERT(hRank < offers.
size());
2395 return offers.
at(hRank);
2413 bool triedMimeFromFileContent =
false;
2420 d->m_docFileName = docFile;
2422 if (!d->updateMetadataXmlNameAndDocSize()) {
2429 qWarning() <<
"failed to read" << url << filedata;
2438 d->m_docSize = filedata.
size();
2439 triedMimeFromFileContent =
true;
2442 const bool fromFileDescriptor = fd >= 0;
2446 KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2447 if (!offer.
isValid() && !triedMimeFromFileContent) {
2449 triedMimeFromFileContent =
true;
2450 if (newmime != mime) {
2452 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2460 if (!newmime.
isDefault() && newmime != mime) {
2462 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget);
2467 d->m_openError =
i18n(
"Can not find a plugin which is able to handle the document being passed.");
2469 qCWarning(OkularCoreDebug).nospace() <<
"No plugin for mimetype '" << mime.
name() <<
"'.";
2474 OpenResult openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2475 if (openResult == OpenError) {
2477 triedOffers << offer;
2478 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2480 while (offer.isValid()) {
2481 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2483 if (openResult == OpenError) {
2484 triedOffers << offer;
2485 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2491 if (openResult == OpenError && !triedMimeFromFileContent) {
2493 triedMimeFromFileContent =
true;
2494 if (newmime != mime) {
2496 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2497 while (offer.isValid()) {
2498 openResult = d->openDocumentInternal(offer, fromFileDescriptor, docFile, filedata, password);
2500 if (openResult == OpenError) {
2501 triedOffers << offer;
2502 offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers);
2510 if (openResult == OpenSuccess) {
2518 if (openResult != OpenSuccess) {
2524 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(docFile).constData(),
nullptr, 1);
2526 d->loadSyncFile(docFile);
2529 d->m_generatorName = offer.
pluginId();
2530 d->m_pageController =
new PageController();
2531 connect(d->m_pageController, &PageController::rotationFinished,
this, [
this](
int p,
Okular::Page *op) { d->rotationFinished(p, op); });
2533 for (
Page *p : std::as_const(d->m_pagesVector)) {
2537 d->m_docdataMigrationNeeded =
false;
2540 if (d->m_archiveData) {
2542 d->m_archiveData->metadataFile.fileName();
2543 d->loadDocumentInfo(d->m_archiveData->metadataFile, LoadPageInfo);
2544 d->loadDocumentInfo(LoadGeneralInfo);
2546 if (d->loadDocumentInfo(LoadPageInfo)) {
2547 d->m_docdataMigrationNeeded =
true;
2549 d->loadDocumentInfo(LoadGeneralInfo);
2552 d->m_bookmarkManager->setUrl(d->m_url);
2559 if (loadedViewport.
isValid()) {
2561 if (loadedViewport.
pageNumber >= (
int)d->m_pagesVector.size()) {
2562 loadedViewport.
pageNumber = d->m_pagesVector.size() - 1;
2570 if (!d->m_saveBookmarksTimer) {
2571 d->m_saveBookmarksTimer =
new QTimer(
this);
2574 d->m_saveBookmarksTimer->start(5 * 60 * 1000);
2577 if (!d->m_memCheckTimer) {
2578 d->m_memCheckTimer =
new QTimer(
this);
2581 d->m_memCheckTimer->start(kMemCheckTime);
2587 d->m_nextDocumentDestination =
QString();
2592 const QStringList docScripts = d->m_generator->metaData(QStringLiteral(
"DocumentScripts"), QStringLiteral(
"JavaScript")).toStringList();
2594 d->m_scripter =
new Scripter(d);
2595 for (
const QString &docscript : docScripts) {
2597 std::shared_ptr<Event>
event = Event::createDocEvent(Event::DocOpen);
2598 d->executeScriptEvent(
event, linkScript);
2605bool DocumentPrivate::updateMetadataXmlNameAndDocSize()
2609 if (!fileReadTest.isFile() && !fileReadTest.isReadable()) {
2613 m_docSize = fileReadTest.size();
2616 if (m_url.isLocalFile()) {
2617 const QString filePath = docDataFileName(m_url, m_docSize);
2618 qCDebug(OkularCoreDebug) <<
"Metadata file is now:" << filePath;
2619 m_xmlFileName = filePath;
2621 qCDebug(OkularCoreDebug) <<
"Metadata file: disabled";
2630 if (d->m_generator) {
2642 if (!d->m_generator) {
2646 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(CloseDocument)) {
2652 delete d->m_pageController;
2653 d->m_pageController =
nullptr;
2655 delete d->m_scripter;
2656 d->m_scripter =
nullptr;
2659 d->clearAndWaitForRequests();
2661 if (d->m_fontThread) {
2662 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
2663 d->m_fontThread->stopExtraction();
2664 d->m_fontThread->wait();
2665 d->m_fontThread =
nullptr;
2672 if (d->m_generator && d->m_pagesVector.size() > 0) {
2673 d->saveDocumentInfo();
2681 for (
const Page *p : std::as_const(d->m_pagesVector)) {
2685 const Action *a =
static_cast<const Action *
>(oRect->object());
2686 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2687 if (backendAction) {
2688 d->m_generator->freeOpaqueActionContents(*backendAction);
2696 for (
const Action *a : additionalActions) {
2697 const BackendOpaqueAction *backendAction =
dynamic_cast<const BackendOpaqueAction *
>(a);
2698 if (backendAction) {
2699 d->m_generator->freeOpaqueActionContents(*backendAction);
2705 d->m_generator->closeDocument();
2708 if (d->m_synctex_scanner) {
2709 synctex_scanner_free(d->m_synctex_scanner);
2710 d->m_synctex_scanner =
nullptr;
2714 if (d->m_memCheckTimer) {
2715 d->m_memCheckTimer->stop();
2717 if (d->m_saveBookmarksTimer) {
2718 d->m_saveBookmarksTimer->stop();
2721 if (d->m_generator) {
2723 d->m_generator->d_func()->m_document =
nullptr;
2725 disconnect(d->m_generator,
nullptr,
this,
nullptr);
2728 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
2730 d->m_generator =
nullptr;
2731 d->m_generatorName =
QString();
2733 d->m_walletGenerator =
nullptr;
2736 delete d->m_tempFile;
2737 d->m_tempFile =
nullptr;
2738 delete d->m_archiveData;
2739 d->m_archiveData =
nullptr;
2741 d->m_exportCached =
false;
2742 d->m_exportFormats.clear();
2744 d->m_fontsCached =
false;
2745 d->m_fontsCache.clear();
2754 for (; pIt != pEnd; ++pIt) {
2757 d->m_pagesVector.clear();
2760 qDeleteAll(d->m_allocatedPixmaps);
2761 d->m_allocatedPixmaps.clear();
2766 for (; rIt != rEnd; ++rIt) {
2769 d->m_searches.clear();
2774 for (; vIt != vEnd; ++vIt) {
2777 d->m_pageRects.clear();
2778 foreachObserver(notifyVisibleRectsChanged());
2782 d->m_viewportHistory.clear();
2783 d->m_viewportHistory.emplace_back();
2784 d->m_viewportIterator = d->m_viewportHistory.begin();
2785 d->m_allocatedPixmapsTotalMemory = 0;
2786 d->m_allocatedTextPagesFifo.clear();
2788 d->m_pageSizes.clear();
2791 d->m_documentInfoAskedKeys.clear();
2795 d->m_undoStack->clear();
2796 d->m_docdataMigrationNeeded =
false;
2808 Q_ASSERT(!d->m_observers.contains(pObserver));
2809 d->m_observers << pObserver;
2812 if (!d->m_pagesVector.isEmpty()) {
2821 if (d->m_observers.contains(pObserver)) {
2824 for (; it != end; ++it) {
2825 (*it)->deletePixmap(pObserver);
2829 std::list<AllocatedPixmap *>::iterator aIt = d->m_allocatedPixmaps.begin();
2830 std::list<AllocatedPixmap *>::iterator aEnd = d->m_allocatedPixmaps.end();
2831 while (aIt != aEnd) {
2832 AllocatedPixmap *p = *aIt;
2833 if (p->observer == pObserver) {
2834 aIt = d->m_allocatedPixmaps.erase(aIt);
2841 for (
PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
2842 if (executingRequest->observer() == pObserver) {
2843 d->cancelRenderingBecauseOf(executingRequest,
nullptr);
2848 d->m_observers.remove(pObserver);
2855 bool configchanged =
false;
2856 if (d->m_generator) {
2862 if (configchanged) {
2865 for (; it != end; ++it) {
2866 (*it)->deletePixmaps();
2870 qDeleteAll(d->m_allocatedPixmaps);
2871 d->m_allocatedPixmaps.clear();
2872 d->m_allocatedPixmapsTotalMemory = 0;
2879 if (SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.empty() && !d->m_pagesVector.isEmpty()) {
2880 d->cleanupPixmapMemory();
2886 return d->m_generator;
2891 if (d->m_generator) {
2893 return iface ? true :
false;
2901 if (d->m_generator->canSign()) {
2902 return d->m_generator->sign(data, newPath);
2910 return d->m_generator ? d->m_generator->certificateStore() :
nullptr;
2915 d->editorCommandOverride = editCmd;
2920 return d->editorCommandOverride;
2938 if (d->m_generator && !missingKeys.
isEmpty()) {
2939 DocumentInfo info = d->m_generator->generateDocumentInfo(missingKeys);
2950 const QString pagesSize = d->pagesSizeString();
2960 d->m_documentInfo.d->values.insert(info.d->values);
2961 d->m_documentInfo.d->titles.insert(info.d->titles);
2962 result.d->values.insert(info.d->values);
2963 result.d->titles.insert(info.d->titles);
2965 d->m_documentInfoAskedKeys += keys;
2972 return d->m_generator ? d->m_generator->generateDocumentSynopsis() :
nullptr;
2977 if (!d->m_generator || !d->m_generator->hasFeature(
Generator::FontInfo) || d->m_fontThread) {
2981 if (d->m_fontsCached) {
2985 for (
int i = 0; i < d->m_fontsCache.count(); ++i) {
2993 d->m_fontThread =
new FontExtractionThread(d->m_generator,
pages());
2994 connect(d->m_fontThread, &FontExtractionThread::gotFont,
this, [
this](
const Okular::FontInfo &f) { d->fontReadingGotFont(f); });
2995 connect(d->m_fontThread.data(), &FontExtractionThread::progress,
this, [
this](
int p) { d->slotFontReadingProgress(p); });
2997 d->m_fontThread->startExtraction(
true);
3002 if (!d->m_fontThread) {
3006 disconnect(d->m_fontThread,
nullptr,
this,
nullptr);
3007 d->m_fontThread->stopExtraction();
3008 d->m_fontThread =
nullptr;
3009 d->m_fontsCache.clear();
3019 return d->m_generator ? d->m_generator->canSign() :
false;
3024 return d->m_generator ? d->m_generator->embeddedFiles() :
nullptr;
3029 return (n >= 0 && n < d->m_pagesVector.count()) ? d->m_pagesVector.at(n) :
nullptr;
3034 return (*d->m_viewportIterator);
3039 return d->m_pageRects;
3046 for (; vIt != vEnd; ++vIt) {
3052 if (o != excludeObserver) {
3053 o->notifyVisibleRectsChanged();
3060 return (*d->m_viewportIterator).pageNumber;
3065 return d->m_pagesVector.size();
3075 if (action ==
Okular::AllowNotes && (d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled)) {
3082#if !OKULAR_FORCE_DRM
3088 return d->m_generator ? d->m_generator->isAllowed(action) :
false;
3108 if (d->m_generator) {
3109 if (d->m_pageSizes.isEmpty()) {
3110 d->m_pageSizes = d->m_generator->pageSizes();
3112 return d->m_pageSizes;
3119 if (!d->m_generator) {
3123 d->cacheExportFormats();
3124 return !d->m_exportToText.isNull();
3129 if (!d->m_generator) {
3133 d->cacheExportFormats();
3134 if (d->m_exportToText.isNull()) {
3138 return d->m_generator->exportTo(fileName, d->m_exportToText);
3143 if (!d->m_generator) {
3147 d->cacheExportFormats();
3148 return d->m_exportFormats;
3153 return d->m_generator ? d->m_generator->exportTo(fileName, format) :
false;
3158 return d->m_viewportIterator == d->m_viewportHistory.begin();
3163 return d->m_viewportIterator == --(d->m_viewportHistory.end());
3180 name = reference.
mid(4);
3182 int nameLength = name.
length();
3184 for (i = 0; i < nameLength; ++i) {
3185 if (!name[i].isDigit()) {
3189 lineString = name.
left(i);
3193 lineString = lineString.
trimmed();
3196 int line = lineString.
toInt(&ok);
3203 synctex_node_p node;
3206 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
3213 const QSizeF dpi = d->m_generator->dpi();
3216 double px = (synctex_node_visible_h(node) * dpi.
width()) / 72.27;
3217 double py = (synctex_node_visible_v(node) * dpi.
height()) / 72.27;
3228 return d->m_generator ? d->m_generator->metaData(key, option) :
QVariant();
3233 return d->m_rotation;
3238 bool allPagesSameSize =
true;
3240 for (
int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) {
3241 const Page *p = d->m_pagesVector.at(i);
3248 if (allPagesSameSize) {
3257 if (d->m_generator) {
3259 const Page *p = d->m_pagesVector.at(
page);
3292 if (executingRequest.
width() != otherRequest.
width()) {
3297 if (executingRequest.
height() != otherRequest.
height()) {
3302 if (executingRequest.
isTile() != otherRequest.
isTile()) {
3307 if (executingRequest.
isTile()) {
3320 if (!executingRequest->d->mResultImage.isNull()) {
3328 TilesManager *tm = executingRequest->d->tilesManager();
3330 tm->setPixmap(
nullptr, executingRequest->
normalizedRect(),
true );
3333 PagePrivate::PixmapObject
object = executingRequest->
page()->d->m_pixmaps.take(executingRequest->
observer());
3334 delete object.m_pixmap;
3336 if (executingRequest->d->mShouldAbortRender != 0) {
3340 executingRequest->d->mShouldAbortRender = 1;
3342 if (m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->
page()) {
3343 m_generator->d_ptr->mTextPageGenerationThread->abortExtraction();
3360 if (!d->m_pageController) {
3362 qDeleteAll(requests);
3374 Q_ASSERT(request->
observer() == requesterObserver);
3379 d->m_pixmapRequestsMutex.lock();
3380 std::list<PixmapRequest *>::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
3381 while (sIt != sEnd) {
3382 if ((*sIt)->observer() == requesterObserver && (removeAllPrevious || requestedPages.
contains((*sIt)->pageNumber()))) {
3385 sIt = d->m_pixmapRequestsStack.erase(sIt);
3394 qCDebug(OkularCoreDebug).nospace() <<
"request observer=" << request->
observer() <<
" " << request->
width() <<
"x" << request->
height() <<
"@" << request->
pageNumber();
3395 if (d->m_pagesVector.value(request->
pageNumber()) ==
nullptr) {
3401 request->d->mPage = d->m_pagesVector.value(request->
pageNumber());
3409 while (tIt != tEnd) {
3410 const Tile &tile = *tIt;
3412 if (tilesRect.
isNull()) {
3413 tilesRect = tile.
rect();
3415 tilesRect |= tile.
rect();
3426 request->d->mPriority = 0;
3432 for (
PixmapRequest *executingRequest : std::as_const(d->m_executingPixmapRequests)) {
3433 bool newRequestsContainExecutingRequestPage =
false;
3434 bool requestCancelled =
false;
3437 newRequestsContainExecutingRequestPage =
true;
3440 if (shouldCancelRenderingBecauseOf(*executingRequest, *newRequest)) {
3441 requestCancelled = d->cancelRenderingBecauseOf(executingRequest, newRequest);
3446 if (!requestCancelled && removeAllPrevious && requesterObserver == executingRequest->
observer() && !newRequestsContainExecutingRequestPage) {
3447 requestCancelled = d->cancelRenderingBecauseOf(executingRequest,
nullptr);
3450 if (requestCancelled) {
3451 observersPixmapCleared << executingRequest->
observer();
3461 d->m_pixmapRequestsStack.push_back(request);
3464 sIt = d->m_pixmapRequestsStack.begin();
3465 sEnd = d->m_pixmapRequestsStack.end();
3466 while (sIt != sEnd && (*sIt)->priority() > request->
priority()) {
3469 d->m_pixmapRequestsStack.insert(sIt, request);
3472 d->m_pixmapRequestsMutex.unlock();
3479 d->sendGeneratorPixmapRequest();
3488 Page *kp = d->m_pagesVector[pageNumber];
3489 if (!d->m_generator || !kp) {
3495 d->m_generator->generateTextPage(kp);
3498void DocumentPrivate::notifyAnnotationChanges(
int page)
3503void DocumentPrivate::notifyFormChanges(
int )
3510 d->recalculateForms();
3518 annotation->d_ptr->baseTransform(t.
inverted());
3520 d->m_undoStack->push(uc);
3537 switch (annotation->
subType()) {
3553 Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull());
3554 if (!d->m_prevPropsOfAnnotBeingModified.isNull()) {
3555 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties";
3563 Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull());
3564 if (d->m_prevPropsOfAnnotBeingModified.isNull()) {
3565 qCCritical(OkularCoreDebug) <<
"Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified";
3568 QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified;
3570 d->m_undoStack->push(uc);
3571 d->m_prevPropsOfAnnotBeingModified.clear();
3577 QUndoCommand *uc =
new Okular::TranslateAnnotationCommand(d, annotation,
page, delta, complete);
3578 d->m_undoStack->push(uc);
3584 QUndoCommand *uc =
new Okular::AdjustAnnotationCommand(d, annotation,
page, delta1, delta2, complete);
3585 d->m_undoStack->push(uc);
3591 QUndoCommand *uc =
new EditAnnotationContentsCommand(d, annotation,
page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos);
3592 d->m_undoStack->push(uc);
3605 switch (annotation->
subType()) {
3622 d->m_undoStack->push(uc);
3627 d->m_undoStack->beginMacro(
i18nc(
"remove a collection of annotations from the page",
"remove annotations"));
3630 d->m_undoStack->push(uc);
3632 d->m_undoStack->endMacro();
3635bool DocumentPrivate::canAddAnnotationsNatively()
const
3646bool DocumentPrivate::canModifyExternalAnnotations()
const
3657bool DocumentPrivate::canRemoveExternalAnnotations()
const
3671 if (!d->m_generator || !kp) {
3677 kp->d->setTextSelections(*rect, color);
3679 kp->d->deleteTextSelections();
3688 return d->m_undoStack->canUndo();
3693 return d->m_undoStack->canRedo();
3729 const int oldPageNumber = oldViewport.
pageNumber;
3737 d->m_viewportHistory.erase(++d->m_viewportIterator, d->m_viewportHistory.end());
3740 if (d->m_viewportHistory.size() >= OKULAR_HISTORY_MAXSTEPS) {
3741 d->m_viewportHistory.pop_front();
3745 d->m_viewportIterator = d->m_viewportHistory.insert(d->m_viewportHistory.end(),
viewport);
3748 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3750 const bool currentPageChanged = (oldPageNumber != currentViewportPage);
3754 if (o != excludeObserver) {
3755 o->notifyViewportChanged(smoothMove);
3758 if (currentPageChanged) {
3759 o->notifyCurrentPageChanged(oldPageNumber, currentViewportPage);
3769 }
else if (
page > (
int)d->m_pagesVector.count()) {
3770 page = d->m_pagesVector.count() - 1;
3781 if (o != excludeObserver) {
3782 o->notifyZoom(factor);
3790 if (d->m_viewportIterator != d->m_viewportHistory.begin()) {
3791 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3794 --d->m_viewportIterator;
3795 foreachObserver(notifyViewportChanged(
true));
3797 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3798 if (oldViewportPage != currentViewportPage)
3799 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3806 auto nextIterator = std::list<DocumentViewport>::const_iterator(d->m_viewportIterator);
3808 if (nextIterator != d->m_viewportHistory.end()) {
3809 const int oldViewportPage = (*d->m_viewportIterator).pageNumber;
3812 ++d->m_viewportIterator;
3813 foreachObserver(notifyViewportChanged(
true));
3815 const int currentViewportPage = (*d->m_viewportIterator).pageNumber;
3816 if (oldViewportPage != currentViewportPage)
3817 foreachObserver(notifyCurrentPageChanged(oldViewportPage, currentViewportPage));
3823 d->m_nextDocumentViewport =
viewport;
3828 d->m_nextDocumentDestination = namedDestination;
3833 d->m_searchCancelled =
false;
3843 if (searchIt == d->m_searches.end()) {
3844 RunningSearch *search =
new RunningSearch();
3845 search->continueOnPage = -1;
3846 searchIt = d->m_searches.insert(searchID, search);
3848 RunningSearch *s = *searchIt;
3851 bool newText = text != s->cachedString;
3852 s->cachedString = text;
3853 s->cachedType = type;
3854 s->cachedCaseSensitivity = caseSensitivity;
3855 s->cachedViewportMove = moveViewport;
3856 s->cachedColor = color;
3857 s->isCurrentlySearching =
true;
3863 *pagesToNotify += s->highlightedPages;
3864 for (
const int pageNumber : std::as_const(s->highlightedPages)) {
3865 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3867 s->highlightedPages.
clear();
3877 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID] { d->doContinueAllDocumentSearch(pagesToNotify, pageMatches, 0, searchID); });
3884 const int viewportPage = (*d->m_viewportIterator).pageNumber;
3885 const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1;
3886 int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
3887 const Page *lastPage = fromStart ? nullptr : d->m_pagesVector[
currentPage];
3892 if (lastPage && lastPage->number() == s->continueOnPage) {
3894 match = lastPage->findText(searchID, text, forward ?
FromTop :
FromBottom, caseSensitivity);
3896 match = lastPage->findText(searchID, text, forward ?
NextResult :
PreviousResult, caseSensitivity, &s->continueOnMatch);
3908 s->pagesDone = pagesDone;
3910 DoContinueDirectionMatchSearchStruct *searchStruct =
new DoContinueDirectionMatchSearchStruct();
3911 searchStruct->pagesToNotify = pagesToNotify;
3912 searchStruct->match = match;
3914 searchStruct->searchID = searchID;
3916 QTimer::singleShot(0,
this, [
this, searchStruct] { d->doContinueDirectionMatchSearch(searchStruct); });
3924 QTimer::singleShot(0,
this, [
this, pagesToNotify, pageMatches, searchID, words] { d->doContinueGooglesDocumentSearch(pagesToNotify, pageMatches, 0, searchID, words); });
3932 if (it == d->m_searches.constEnd()) {
3938 RunningSearch *p = *it;
3939 if (!p->isCurrentlySearching) {
3940 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor);
3948 if (it == d->m_searches.constEnd()) {
3954 RunningSearch *p = *it;
3955 if (!p->isCurrentlySearching) {
3956 searchText(searchID, p->cachedString,
false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor);
3963 if (!d->m_generator) {
3969 if (searchIt == d->m_searches.end()) {
3974 RunningSearch *s = *searchIt;
3977 for (
const int pageNumber : std::as_const(s->highlightedPages)) {
3978 d->m_pagesVector.at(pageNumber)->d->deleteHighlights(searchID);
3983 foreachObserver(notifySetup(d->m_pagesVector, 0));
3986 d->m_searches.erase(searchIt);
3992 d->m_searchCancelled =
true;
3997 d->m_undoStack->undo();
4002 d->m_undoStack->redo();
4007 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, form->
text(), prevCursorPos, prevAnchorPos);
4008 d->m_undoStack->push(uc);
4013 QUndoCommand *uc =
new EditFormTextCommand(this->d, form, pageNumber, newContents, newCursorPos, oldContents, prevCursorPos, prevAnchorPos);
4014 d->m_undoStack->push(uc);
4020 QUndoCommand *uc =
new EditFormListCommand(this->d, form, pageNumber, newChoices, prevChoices);
4021 d->m_undoStack->push(uc);
4033 QUndoCommand *uc =
new EditFormComboCommand(this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos);
4034 d->m_undoStack->push(uc);
4039 QUndoCommand *uc =
new EditFormButtonsCommand(this->d, pageNumber, formButtons, newButtonStates);
4040 d->m_undoStack->push(uc);
4045 const int numOfPages =
pages();
4047 d->refreshPixmaps(i);
4049 for (
int i =
currentPage() + 1; i < numOfPages; i++) {
4050 d->refreshPixmaps(i);
4056 return d->m_bookmarkManager;
4062 uint docPages =
pages();
4065 for (uint i = 0; i < docPages; i++) {
4078 uint docPages =
pages();
4082 for (uint i = 0; i < docPages; ++i) {
4092 }
else if (startId >= 0 && endId >= 0) {
4097 if (endId - startId > 0) {
4098 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4106 if (startId >= 0 && endId >= 0) {
4111 if (endId - startId > 0) {
4112 range += QStringLiteral(
"%1-%2").
arg(startId + 1).
arg(endId + 1);
4123 explicit ExecuteNextActionsHelper(
Document *doc)
4127 connect(doc, &Document::aboutToClose,
this, [
this] { b =
false; });
4130 ~ExecuteNextActionsHelper()
override
4132 m_doc->removeObserver(
this);
4137 if (setupFlags == DocumentChanged || setupFlags == UrlChanged) {
4142 bool shouldExecuteNextAction()
const
4159 const ExecuteNextActionsHelper executeNextActionsHelper(
this);
4177 if (go->
isExternal() && !d->openRelativeFile(filename)) {
4178 qCWarning(OkularCoreDebug).nospace() <<
"Action: Error opening '" << filename <<
"'.";
4183 if (!nextViewport.
isValid()) {
4189 d->m_nextDocumentDestination =
QString();
4198 d->openRelativeFile(fileName);
4204 QUrl url = d->giveAbsoluteUrl(fileName);
4217 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4223 Q_EMIT error(
i18n(
"The document is trying to execute an external application and, for your safety, Okular does not allow that."), -1);
4233 Q_EMIT error(
i18n(
"No application found for opening file of mimetype %1.", mime.
name()), -1);
4245 if ((*d->m_viewportIterator).pageNumber > 0) {
4250 if ((*d->m_viewportIterator).pageNumber < (
int)d->m_pagesVector.count() - 1) {
4293 int lilyRow = 0, lilyCol = 0;
4297 }
else if (extractLilyPondSourceReference(browse->
url(), &lilySource, &lilyRow, &lilyCol)) {
4301 const QUrl url = browse->
url();
4305 d->openRelativeFile(url.
fileName());
4311 if (d->m_url.isValid()) {
4331 if (!d->m_scripter) {
4332 d->m_scripter =
new Scripter(d);
4343 if (!d->m_scripter) {
4344 d->m_scripter =
new Scripter(d);
4346 d->m_scripter->execute(linkrendition->
scriptType(), linkrendition->
script());
4352 const BackendOpaqueAction *backendOpaqueAction =
static_cast<const BackendOpaqueAction *
>(action);
4353 Okular::BackendOpaqueAction::OpaqueActionResult res = d->m_generator->opaqueAction(backendOpaqueAction);
4354 if (res & Okular::BackendOpaqueAction::RefreshForms) {
4355 for (
const Page *p : std::as_const(d->m_pagesVector)) {
4360 d->refreshPixmaps(p->number());
4366 if (executeNextActionsHelper.shouldExecuteNextAction()) {
4368 for (
const Action *a : nextActions) {
4382 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for formatting.";
4387 int foundPage = d->findFieldPageNumber(ff);
4389 if (foundPage == -1) {
4390 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4396 std::shared_ptr<Event>
event = Event::createFormatEvent(ff, d->m_pagesVector[foundPage]);
4400 d->executeScriptEvent(
event, linkscript);
4402 const QString formattedText =
event->value().toString();
4404 if (formattedText != unformattedText) {
4410 d->refreshPixmaps(foundPage);
4420 d->refreshPixmaps(foundPage);
4424QString DocumentPrivate::evaluateKeystrokeEventChange(
const QString &oldVal,
const QString &newVal,
int selStart,
int selEnd)
4434 if (selStart < 0 || selEnd < 0 || (selEnd - selStart) + (
static_cast<int>(newUcs4.size()) -
static_cast<int>(oldUcs4.size())) < 0) {
4438 const size_t changeLength = (selEnd - selStart) + (newUcs4.size() - oldUcs4.size());
4439 auto subview = std::u32string_view {newUcs4}.substr(selStart, changeLength);
4440 if (subview.empty()) {
4445 Q_ASSERT(subview.length() == changeLength);
4452 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4456 int foundPage = d->findFieldPageNumber(ff);
4458 if (foundPage == -1) {
4459 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4463 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4478 int selStart = std::min(prevCursorPos, prevAnchorPos);
4479 int selEnd = std::max(prevCursorPos, prevAnchorPos);
4481 int initialSelStart = selStart;
4482 int initialSelEnd = selEnd;
4484 for (codeUnit = 0; codeUnit < initialSelStart && codeUnit < inputString.
size(); codeUnit++) {
4492 for (; codeUnit < initialSelEnd && codeUnit < inputString.
size(); codeUnit++) {
4502 if (oldUcs4.size() - newUcs4.size() == 1 && selStart == selEnd) {
4506 event->setSelStart(selStart);
4507 event->setSelEnd(selEnd);
4509 event->setChange(DocumentPrivate::evaluateKeystrokeEventChange(inputString, newValue.
toString(), selStart, selEnd));
4512 d->executeScriptEvent(
event, linkscript);
4514 if (
event->returnCode()) {
4529 bool returnCode =
false;
4536 qCDebug(OkularCoreDebug) <<
"Unsupported action type" << action->
actionType() <<
"for keystroke.";
4540 int foundPage = d->findFieldPageNumber(ff);
4542 if (foundPage == -1) {
4543 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4547 std::shared_ptr<Event>
event = Event::createKeystrokeEvent(ff, d->m_pagesVector[foundPage]);
4548 event->setWillCommit(
true);
4552 d->executeScriptEvent(
event, linkscript);
4554 if (!
event->returnCode()) {
4562 returnCode =
event->returnCode();
4572 int foundPage = d->findFieldPageNumber(field);
4574 if (foundPage == -1) {
4575 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4579 std::shared_ptr<Event>
event = Event::createFormFocusEvent(field, d->m_pagesVector[foundPage]);
4583 d->executeScriptEvent(
event, linkscript);
4598 int foundPage = d->findFieldPageNumber(ff);
4600 if (foundPage == -1) {
4601 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4605 std::shared_ptr<Event>
event = Event::createFormValidateEvent(ff, d->m_pagesVector[foundPage]);
4609 d->executeScriptEvent(
event, linkscript);
4610 if (!
event->returnCode()) {
4618 returnCode =
event->returnCode();
4630 bool returnCode =
true;
4662 Event::EventType eventType = Okular::Event::UnknownEvent;
4665 case Document::CloseDocument:
4666 eventType = Okular::Event::DocWillClose;
4668 case Document::SaveDocumentStart:
4669 eventType = Okular::Event::DocWillSave;
4671 case Document::SaveDocumentFinish:
4672 eventType = Okular::Event::DocDidSave;
4674 case Document::PrintDocumentStart:
4675 eventType = Okular::Event::DocWillPrint;
4677 case Document::PrintDocumentFinish:
4678 eventType = Okular::Event::DocDidPrint;
4682 std::shared_ptr<Event>
event = Event::createDocEvent(eventType);
4686 d->executeScriptEvent(
event, linkScript);
4695 Okular::Event::EventType eventType = Okular::Event::UnknownEvent;
4697 switch (fieldMouseEventType) {
4698 case Document::FieldMouseDown:
4699 eventType = Okular::Event::FieldMouseDown;
4702 eventType = Okular::Event::FieldMouseEnter;
4705 eventType = Okular::Event::FieldMouseExit;
4708 eventType = Okular::Event::FieldMouseUp;
4713 int foundPage = d->findFieldPageNumber(ff);
4715 if (foundPage == -1) {
4716 qCDebug(OkularCoreDebug) <<
"Could not find page for formfield!";
4720 std::shared_ptr<Event>
event = Event::createFieldMouseEvent(ff, d->m_pagesVector[foundPage], eventType);
4724 d->executeScriptEvent(
event, linkscript);
4740 qCDebug(OkularCoreDebug) << url.
url() <<
"is not a local file.";
4746 qCDebug(OkularCoreDebug) <<
"No such file:" << absFileName;
4750 bool handled =
false;
4759 editors = buildEditorsMap();
4763 QString p = d->editorCommandOverride;
4769 p = SettingsCore::externalEditorCommand();
4808 if (!d->m_synctex_scanner) {
4812 const QSizeF dpi = d->m_generator->dpi();
4814 if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.
width(), absY * 72. / dpi.
height()) > 0) {
4815 synctex_node_p node;
4817 while ((node = synctex_scanner_next_result(d->m_synctex_scanner))) {
4818 int line = synctex_node_line(node);
4819 int col = synctex_node_column(node);
4824 const char *name = synctex_scanner_get_name(d->m_synctex_scanner, synctex_node_tag(node));
4834 if (d->m_generator) {
4856 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentStart)) {
4859 const Document::PrintError printError = d->m_generator ? d->m_generator->print(printer) : Document::UnknownPrintError;
4861 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(PrintDocumentFinish)) {
4871 case TemporaryFileOpenPrintError:
4872 return i18n(
"Could not open a temporary file");
4873 case FileConversionPrintError:
4874 return i18n(
"Print conversion failed");
4875 case PrintingProcessCrashPrintError:
4876 return i18n(
"Printing process crashed");
4877 case PrintingProcessStartPrintError:
4878 return i18n(
"Printing process could not start");
4879 case PrintToFilePrintError:
4880 return i18n(
"Printing to file failed");
4881 case InvalidPrinterStatePrintError:
4882 return i18n(
"Printer was in invalid state");
4883 case UnableToFindFilePrintError:
4884 return i18n(
"Unable to find file to print");
4885 case NoFileToPrintError:
4886 return i18n(
"There was no file to print");
4887 case NoBinaryToPrintError:
4888 return i18n(
"Could not find a suitable binary for printing. Make sure CUPS lpr binary is available");
4889 case InvalidPageSizePrintError:
4890 return i18n(
"The page print size is invalid");
4893 case UnknownPrintError:
4902 if (d->m_generator) {
4917 BackendConfigDialog *bcd =
dynamic_cast<BackendConfigDialog *
>(dialog);
4924 d->loadServiceList(offers);
4932 for (; it != itEnd; ++it) {
4933 sortedGenerators.
insert(it.key(), it.value());
4936 bool pagesAdded =
false;
4939 for (; sit != sitEnd; ++sit) {
4945 if (sit.value().generator == d->m_generator) {
4946 const int rowCount = bcd->thePageWidget()->model()->rowCount();
4962 if (md.rawData().value(QStringLiteral(
"X-KDE-okularHasInternalSettings")).toBool()) {
4971 if (!d->m_generator) {
4975 auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName);
4976 Q_ASSERT(genIt != d->m_loadedGenerators.constEnd());
4977 return genIt.value().metadata;
4982 return DocumentPrivate::configurableGenerators().size();
4992 result << md.mimeTypes();
4998 for (
const QString &mimeName : std::as_const(result)) {
5002 for (
const QMimeType &mimeType : uniqueMimetypes) {
5003 result.
append(mimeType.name());
5007 result << QStringLiteral(
"application/vnd.kde.okular-archive");
5011 std::sort(result.
begin(), result.
end());
5013 d->m_supportedMimeTypes = result;
5020 if (!d->m_generator) {
5029 if (!d->m_generator) {
5038 d->saveDocumentInfo();
5040 d->clearAndWaitForRequests();
5042 qCDebug(OkularCoreDebug) <<
"Swapping backing file to" << newFileName;
5045 if (result != Generator::SwapBackingFileError) {
5050 if (result == Generator::SwapBackingFileReloadInternalData) {
5056 if (newPagesVector.
count() != d->m_pagesVector.count()) {
5061 for (
int i = 0; i < d->m_undoStack->count(); ++i) {
5064 if (OkularUndoCommand *ouc =
dynamic_cast<OkularUndoCommand *
>(uc)) {
5065 const bool success = ouc->refreshInternalPageReferences(newPagesVector);
5067 qWarning() <<
"Document::swapBackingFile: refreshInternalPageReferences failed" << ouc;
5071 qWarning() <<
"Document::swapBackingFile: Unhandled undo command" << uc;
5076 for (
int i = 0; i < d->m_pagesVector.count(); ++i) {
5080 Page *oldPage = d->m_pagesVector[i];
5081 Page *newPage = newPagesVector[i];
5082 newPage->d->adoptGeneratedContents(oldPage->d);
5084 pagePrivatesToDelete << oldPage->d;
5085 oldPage->d = newPage->d;
5086 oldPage->d->m_page = oldPage;
5087 oldPage->d->m_doc = d;
5088 newPage->d =
nullptr;
5090 annotationsToDelete << oldPage->m_annotations;
5091 rectsToDelete << oldPage->m_rects;
5092 oldPage->m_annotations = newPage->m_annotations;
5093 oldPage->m_rects = newPage->m_rects;
5095 qDeleteAll(newPagesVector);
5099 d->m_docFileName = newFileName;
5100 d->updateMetadataXmlNameAndDocSize();
5101 d->m_bookmarkManager->setUrl(d->m_url);
5103 d->m_documentInfoAskedKeys.clear();
5105 if (d->m_synctex_scanner) {
5106 synctex_scanner_free(d->m_synctex_scanner);
5107 d->m_synctex_scanner = synctex_scanner_new_with_output_file(
QFile::encodeName(newFileName).constData(),
nullptr, 1);
5109 d->loadSyncFile(newFileName);
5115 qDeleteAll(annotationsToDelete);
5116 qDeleteAll(rectsToDelete);
5117 qDeleteAll(pagePrivatesToDelete);
5127 qCDebug(OkularCoreDebug) <<
"Swapping backing archive to" << newFileName;
5129 ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive(newFileName);
5139 delete d->m_archiveData;
5140 d->m_archiveData = newArchive;
5149 d->m_undoStack->setClean();
5151 d->m_undoStack->resetClean();
5155bool Document::isHistoryClean()
const
5157 return d->m_undoStack->isClean();
5162 d->m_undoStack->clear();
5167 if (!d->m_generator) {
5170 Q_ASSERT(!d->m_generatorName.isEmpty());
5173 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5192 return d->canAddAnnotationsNatively();
5206 if (!d->m_generator || fileName.
isEmpty()) {
5209 Q_ASSERT(!d->m_generatorName.isEmpty());
5212 Q_ASSERT(genIt != d->m_loadedGenerators.end());
5218 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentStart)) {
5224 if (
const Okular::Action *action = d->m_generator->additionalDocumentAction(SaveDocumentFinish)) {
5240 if (viewDoc ==
this) {
5247 d->m_views.insert(view);
5248 view->d_func()->document = d;
5258 if (!viewDoc || viewDoc !=
this) {
5262 view->d_func()->document =
nullptr;
5263 d->m_views.remove(view);
5268 if (d->m_generator) {
5269 return d->m_generator->requestFontData(font);
5275ArchiveData *DocumentPrivate::unpackDocumentArchive(
const QString &archivePath)
5279 if (!mime.
inherits(QStringLiteral(
"application/vnd.kde.okular-archive"))) {
5283 KZip okularArchive(archivePath);
5293 for (
const QString &entry : mainDirEntries) {
5295 qWarning() <<
"Warning: Found a directory inside" << archivePath <<
" - Okular does not create files like that so it is most probably forged.";
5301 if (!mainEntry || !mainEntry->
isFile()) {
5305 std::unique_ptr<QIODevice> mainEntryDevice(
static_cast<const KZipFileEntry *
>(mainEntry)->createDevice());
5307 if (!doc.
setContent(mainEntryDevice.get())) {
5310 mainEntryDevice.reset();
5325 documentFileName = fileEl.
text();
5327 metadataFileName = fileEl.
text();
5332 if (documentFileName.
isEmpty()) {
5337 if (!docEntry || !docEntry->
isFile()) {
5341 std::unique_ptr<ArchiveData> archiveData(
new ArchiveData());
5346 if (!archiveData->document.open()) {
5350 archiveData->originalFileName = documentFileName;
5353 std::unique_ptr<QIODevice> docEntryDevice(
static_cast<const KZipFileEntry *
>(docEntry)->createDevice());
5354 copyQIODevice(docEntryDevice.get(), &archiveData->document);
5355 archiveData->document.close();
5359 if (metadataEntry && metadataEntry->
isFile()) {
5360 std::unique_ptr<QIODevice> metadataEntryDevice(
static_cast<const KZipFileEntry *
>(metadataEntry)->createDevice());
5362 if (archiveData->metadataFile.open()) {
5363 copyQIODevice(metadataEntryDevice.get(), &archiveData->metadataFile);
5364 archiveData->metadataFile.close();
5368 return archiveData.release();
5373 d->m_archiveData = DocumentPrivate::unpackDocumentArchive(docFile);
5374 if (!d->m_archiveData) {
5378 const QString tempFileName = d->m_archiveData->document.fileName();
5383 if (ret != OpenSuccess) {
5384 delete d->m_archiveData;
5385 d->m_archiveData =
nullptr;
5393 if (!d->m_generator) {
5399 QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();