document.cpp 202 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it>             *
3
 *   Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org>          *
4
 *   Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group         *
5
6
 *                      company, info@kdab.com. Work sponsored by the      *
 *                      LiMux project of the city of Munich                *
7
8
9
10
11
12
13
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

Albert Astals Cid's avatar
Albert Astals Cid committed
14
#include "document.h"
15
#include "document_p.h"
16
#include "documentcommands_p.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
17

18
#include <limits.h>
19
#include <memory>
Christian Ehrlicher's avatar
Christian Ehrlicher committed
20
21
22
#ifdef Q_OS_WIN
#define _WIN32_WINNT 0x0500
#include <windows.h>
23
#elif defined(Q_OS_FREEBSD)
Albert Astals Cid's avatar
Albert Astals Cid committed
24
25
// clang-format off
// FreeBSD really wants this include order
Albert Astals Cid's avatar
Albert Astals Cid committed
26
#include <sys/types.h>
Albert Astals Cid's avatar
Albert Astals Cid committed
27
28
#include <sys/sysctl.h>
// clang-format on
29
#include <vm/vm_param.h>
Christian Ehrlicher's avatar
Christian Ehrlicher committed
30
31
#endif

32
// qt/kde/system includes
Albert Astals Cid's avatar
Albert Astals Cid committed
33
34
#include <QApplication>
#include <QDesktopServices>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
35
36
37
38
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLabel>
Albert Astals Cid's avatar
Albert Astals Cid committed
39
#include <QMap>
Laurent Montel's avatar
Laurent Montel committed
40
#include <QMimeDatabase>
Alex Richardson's avatar
Alex Richardson committed
41
#include <QPageSize>
Albert Astals Cid's avatar
Albert Astals Cid committed
42
#include <QPrintDialog>
43
#include <QRegularExpression>
Albert Astals Cid's avatar
Albert Astals Cid committed
44
45
#include <QScreen>
#include <QStack>
Alex Richardson's avatar
Alex Richardson committed
46
#include <QStandardPaths>
Albert Astals Cid's avatar
Albert Astals Cid committed
47
48
49
50
51
52
#include <QTemporaryFile>
#include <QTextStream>
#include <QTimer>
#include <QUndoCommand>
#include <QWindow>
#include <QtAlgorithms>
53

54
55
#include <KAuthorized>
#include <KConfigDialog>
Albert Astals Cid's avatar
Albert Astals Cid committed
56
57
58
#include <KFormat>
#include <KIO/Global>
#include <KLocalizedString>
59
60
#include <KMacroExpander>
#include <KMessageBox>
61
#include <KApplicationTrader>
Albert Astals Cid's avatar
Albert Astals Cid committed
62
#include <KPluginMetaData>
63
#include <KProcess>
Olivier Churlaud's avatar
Olivier Churlaud committed
64
#include <KRun>
65
#include <KShell>
66
#include <Kdelibs4Migration>
Albert Astals Cid's avatar
Albert Astals Cid committed
67
#include <kzip.h>
68
69

// local includes
Pino Toscano's avatar
Pino Toscano committed
70
#include "action.h"
71
72
#include "annotations.h"
#include "annotations_p.h"
73
#include "audioplayer.h"
74
#include "audioplayer_p.h"
75
#include "bookmarkmanager.h"
76
#include "chooseenginedialog_p.h"
77
#include "debug_p.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
78
#include "form.h"
79
#include "generator_p.h"
80
81
82
#include "interfaces/configinterface.h"
#include "interfaces/guiinterface.h"
#include "interfaces/printinterface.h"
83
#include "interfaces/saveinterface.h"
84
#include "misc.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
85
#include "observer.h"
86
#include "page.h"
87
#include "page_p.h"
88
#include "pagecontroller_p.h"
89
#include "script/event_p.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
90
#include "scripter.h"
91
#include "settings_core.h"
92
#include "sourcereference.h"
93
#include "sourcereference_p.h"
94
#include "texteditors_p.h"
95
#include "tile.h"
96
#include "tilesmanager_p.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
97
#include "utils.h"
98
#include "utils_p.h"
99
100
#include "view.h"
#include "view_p.h"
101

102
103
#include <config-okular.h>

104
105
106
107
#if HAVE_MALLOC_TRIM
#include "malloc.h"
#endif

108
109
using namespace Okular;

Albert Astals Cid's avatar
Albert Astals Cid committed
110
struct AllocatedPixmap {
111
    // owner of the page
112
    DocumentObserver *observer;
113
    int page;
114
    qulonglong memory;
115
    // public constructor: initialize data
Albert Astals Cid's avatar
Albert Astals Cid committed
116
117
118
119
120
121
    AllocatedPixmap(DocumentObserver *o, int p, qulonglong m)
        : observer(o)
        , page(p)
        , memory(m)
    {
    }
122
123
};

Albert Astals Cid's avatar
Albert Astals Cid committed
124
struct ArchiveData {
125
126
127
128
    ArchiveData()
    {
    }

129
    QString originalFileName;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
130
131
    QTemporaryFile document;
    QTemporaryFile metadataFile;
132
133
};

Albert Astals Cid's avatar
Albert Astals Cid committed
134
struct RunningSearch {
135
136
    // store search properties
    int continueOnPage;
137
    RegularAreaRect continueOnMatch;
Albert Astals Cid's avatar
Albert Astals Cid committed
138
    QSet<int> highlightedPages;
139
140
141

    // fields related to previous searches (used for 'continueSearch')
    QString cachedString;
142
    Document::SearchType cachedType;
143
    Qt::CaseSensitivity cachedCaseSensitivity;
Pino Toscano's avatar
Pino Toscano committed
144
    bool cachedViewportMove : 1;
145
    bool isCurrentlySearching : 1;
146
    QColor cachedColor;
147
    int pagesDone;
148
149
};

Albert Astals Cid's avatar
Albert Astals Cid committed
150
151
152
153
154
155
156
#define foreachObserver(cmd)                                                                                                                                                                                                                   \
    {                                                                                                                                                                                                                                          \
        QSet<DocumentObserver *>::const_iterator it = d->m_observers.constBegin(), end = d->m_observers.constEnd();                                                                                                                            \
        for (; it != end; ++it) {                                                                                                                                                                                                              \
            (*it)->cmd;                                                                                                                                                                                                                        \
        }                                                                                                                                                                                                                                      \
    }
157

Albert Astals Cid's avatar
Albert Astals Cid committed
158
159
160
161
162
163
164
#define foreachObserverD(cmd)                                                                                                                                                                                                                  \
    {                                                                                                                                                                                                                                          \
        QSet<DocumentObserver *>::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();                                                                                                                                  \
        for (; it != end; ++it) {                                                                                                                                                                                                              \
            (*it)->cmd;                                                                                                                                                                                                                        \
        }                                                                                                                                                                                                                                      \
    }
165

166
167
168
#define OKULAR_HISTORY_MAXSTEPS 100
#define OKULAR_HISTORY_SAVEDSTEPS 10

169
170
171
// how often to run slotTimedMemoryCheck
const int kMemCheckTime = 2000; // in msec

172
/***** Document ******/
173

174
QString DocumentPrivate::pagesSizeString() const
175
{
Albert Astals Cid's avatar
Albert Astals Cid committed
176
177
    if (m_generator) {
        if (m_generator->pagesSizeMetric() != Generator::None) {
178
            QSizeF size = m_parent->allPagesSize();
179
            // Single page size
Albert Astals Cid's avatar
Albert Astals Cid committed
180
181
            if (size.isValid())
                return localizedSize(size);
182
183
184
185
186
187

            // Multiple page sizes
            QString sizeString;
            QHash<QString, int> pageSizeFrequencies;

            // Compute frequencies of each page size
Albert Astals Cid's avatar
Albert Astals Cid committed
188
            for (int i = 0; i < m_pagesVector.count(); ++i) {
189
                const Page *p = m_pagesVector.at(i);
Albert Astals Cid's avatar
Albert Astals Cid committed
190
                sizeString = localizedSize(QSizeF(p->width(), p->height()));
191
192
193
194
195
196
197
                pageSizeFrequencies[sizeString] = pageSizeFrequencies.value(sizeString, 0) + 1;
            }

            // Figure out which page size is most frequent
            int largestFrequencySeen = 0;
            QString mostCommonPageSize = QString();
            QHash<QString, int>::const_iterator i = pageSizeFrequencies.constBegin();
Albert Astals Cid's avatar
Albert Astals Cid committed
198
199
            while (i != pageSizeFrequencies.constEnd()) {
                if (i.value() > largestFrequencySeen) {
200
201
202
203
204
                    largestFrequencySeen = i.value();
                    mostCommonPageSize = i.key();
                }
                ++i;
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
205
            QString finalText = i18nc("@info %1 is a page size", "Most pages are %1.", mostCommonPageSize);
206
207

            return finalText;
Albert Astals Cid's avatar
Albert Astals Cid committed
208
209
210
211
        } else
            return QString();
    } else
        return QString();
212
}
213

214
215
216
217
QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const
{
    const QPrinter::Orientation orientation = inchesWidth > inchesHeight ? QPrinter::Landscape : QPrinter::Portrait;

Albert Astals Cid's avatar
Albert Astals Cid committed
218
    const QSize pointsSize(inchesWidth * 72.0, inchesHeight * 72.0);
219
    const QPageSize::PageSizeId paperSize = QPageSize::id(pointsSize, QPageSize::FuzzyOrientationMatch);
220

221
    const QString paperName = QPageSize::name(paperSize);
222

223
    if (orientation == QPrinter::Portrait) {
Laurent Montel's avatar
Laurent Montel committed
224
        return i18nc("paper type and orientation (eg: Portrait A4)", "Portrait %1", paperName);
225
    } else {
Laurent Montel's avatar
Laurent Montel committed
226
        return i18nc("paper type and orientation (eg: Portrait A4)", "Landscape %1", paperName);
227
228
229
    }
}

230
QString DocumentPrivate::localizedSize(const QSizeF size) const
231
232
{
    double inchesWidth = 0, inchesHeight = 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
233
234
235
236
    switch (m_generator->pagesSizeMetric()) {
    case Generator::Points:
        inchesWidth = size.width() / 72.0;
        inchesHeight = size.height() / 72.0;
237
        break;
238

Albert Astals Cid's avatar
Albert Astals Cid committed
239
240
241
242
243
    case Generator::Pixels: {
        const QSizeF dpi = m_generator->dpi();
        inchesWidth = size.width() / dpi.width();
        inchesHeight = size.height() / dpi.height();
    } break;
244

Albert Astals Cid's avatar
Albert Astals Cid committed
245
    case Generator::None:
246
        break;
247
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
248
    if (QLocale::system().measurementSystem() == QLocale::ImperialSystem) {
249
        return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight));
Albert Astals Cid's avatar
Albert Astals Cid committed
250
    } else {
251
        return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight));
252
    }
253
254
}

255
qulonglong DocumentPrivate::calculateMemoryToFree()
256
{
257
    // [MEM] choose memory parameters based on configuration profile
258
259
    qulonglong clipValue = 0;
    qulonglong memoryToFree = 0;
260

Albert Astals Cid's avatar
Albert Astals Cid committed
261
262
263
    switch (SettingsCore::memoryLevel()) {
    case SettingsCore::EnumMemoryLevel::Low:
        memoryToFree = m_allocatedPixmapsTotalMemory;
264
        break;
265

Albert Astals Cid's avatar
Albert Astals Cid committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
    case SettingsCore::EnumMemoryLevel::Normal: {
        qulonglong thirdTotalMemory = getTotalMemory() / 3;
        qulonglong freeMemory = getFreeMemory();
        if (m_allocatedPixmapsTotalMemory > thirdTotalMemory)
            memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory;
        if (m_allocatedPixmapsTotalMemory > freeMemory)
            clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
    } break;

    case SettingsCore::EnumMemoryLevel::Aggressive: {
        qulonglong freeMemory = getFreeMemory();
        if (m_allocatedPixmapsTotalMemory > freeMemory)
            clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
    } break;
    case SettingsCore::EnumMemoryLevel::Greedy: {
        qulonglong freeSwap;
        qulonglong freeMemory = getFreeMemory(&freeSwap);
        const qulonglong memoryLimit = qMin(qMax(freeMemory, getTotalMemory() / 2), freeMemory + freeSwap);
        if (m_allocatedPixmapsTotalMemory > memoryLimit)
            clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2;
    } break;
    }

    if (clipValue > memoryToFree)
290
291
        memoryToFree = clipValue;

292
293
294
295
296
    return memoryToFree;
}

void DocumentPrivate::cleanupPixmapMemory()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
297
    cleanupPixmapMemory(calculateMemoryToFree());
298
299
}

Albert Astals Cid's avatar
Albert Astals Cid committed
300
void DocumentPrivate::cleanupPixmapMemory(qulonglong memoryToFree)
301
{
Albert Astals Cid's avatar
Albert Astals Cid committed
302
    if (memoryToFree < 1)
303
        return;
304

305
    const int currentViewportPage = (*m_viewportIterator).pageNumber;
306

307
    // Create a QMap of visible rects, indexed by page number
Albert Astals Cid's avatar
Albert Astals Cid committed
308
309
310
311
    QMap<int, VisiblePageRect *> visibleRects;
    QVector<Okular::VisiblePageRect *>::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
    for (; vIt != vEnd; ++vIt)
        visibleRects.insert((*vIt)->pageNumber, (*vIt));
312

313
314
    // Free memory starting from pages that are farthest from the current one
    int pagesFreed = 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
315
316
317
    while (memoryToFree > 0) {
        AllocatedPixmap *p = searchLowestPriorityPixmap(true, true);
        if (!p) // No pixmap to remove
318
            break;
319

Frederik Gladhorn's avatar
Frederik Gladhorn committed
320
        qCDebug(OkularCoreDebug).nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page;
321
322
323
324
325

        // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove
        // the memory used by the AllocatedPixmap so at most it can reach zero
        m_allocatedPixmapsTotalMemory -= p->memory;
        // Make sure memoryToFree does not underflow
Albert Astals Cid's avatar
Albert Astals Cid committed
326
        if (p->memory > memoryToFree)
327
328
329
330
331
            memoryToFree = 0;
        else
            memoryToFree -= p->memory;
        pagesFreed++;
        // delete pixmap
Albert Astals Cid's avatar
Albert Astals Cid committed
332
        m_pagesVector.at(p->page)->deletePixmap(p->observer);
333
334
335
        // delete allocation descriptor
        delete p;
    }
Mailson Menezes's avatar
Mailson Menezes committed
336

337
    // If we're still on low memory, try to free individual tiles
338

339
340
    // Store pages that weren't completely removed

Albert Astals Cid's avatar
Albert Astals Cid committed
341
342
    QLinkedList<AllocatedPixmap *> pixmapsToKeep;
    while (memoryToFree > 0) {
343
        int clean_hits = 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
344
345
346
        for (DocumentObserver *observer : qAsConst(m_observers)) {
            AllocatedPixmap *p = searchLowestPriorityPixmap(false, true, observer);
            if (!p) // No pixmap to remove
347
                continue;
Mailson Menezes's avatar
Mailson Menezes committed
348

349
350
            clean_hits++;

Albert Astals Cid's avatar
Albert Astals Cid committed
351
352
            TilesManager *tilesManager = m_pagesVector.at(p->page)->d->tilesManager(observer);
            if (tilesManager && tilesManager->totalMemory() > 0) {
353
354
                qulonglong memoryDiff = p->memory;
                NormalizedRect visibleRect;
Albert Astals Cid's avatar
Albert Astals Cid committed
355
356
                if (visibleRects.contains(p->page))
                    visibleRect = visibleRects[p->page]->rect;
357

358
                // Free non visible tiles
Albert Astals Cid's avatar
Albert Astals Cid committed
359
                tilesManager->cleanupPixmapMemory(memoryToFree, visibleRect, currentViewportPage);
360

Mailson Menezes's avatar
Mailson Menezes committed
361
                p->memory = tilesManager->totalMemory();
362
                memoryDiff -= p->memory;
363
                memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0;
364
365
                m_allocatedPixmapsTotalMemory -= memoryDiff;

Albert Astals Cid's avatar
Albert Astals Cid committed
366
367
                if (p->memory > 0)
                    pixmapsToKeep.append(p);
368
369
                else
                    delete p;
Albert Astals Cid's avatar
Albert Astals Cid committed
370
371
            } else
                pixmapsToKeep.append(p);
Mailson Menezes's avatar
Mailson Menezes committed
372
        }
373

Albert Astals Cid's avatar
Albert Astals Cid committed
374
375
        if (clean_hits == 0)
            break;
376
    }
377
378

    m_allocatedPixmaps += pixmapsToKeep;
Albert Astals Cid's avatar
Albert Astals Cid committed
379
    // p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() );
380
}
Mailson Menezes's avatar
Mailson Menezes committed
381

382
/* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap
383
384
385
386
 * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If
 * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before
 * returning it
 */
Albert Astals Cid's avatar
Albert Astals Cid committed
387
AllocatedPixmap *DocumentPrivate::searchLowestPriorityPixmap(bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer)
388
{
Albert Astals Cid's avatar
Albert Astals Cid committed
389
390
391
    QLinkedList<AllocatedPixmap *>::iterator pIt = m_allocatedPixmaps.begin();
    QLinkedList<AllocatedPixmap *>::iterator pEnd = m_allocatedPixmaps.end();
    QLinkedList<AllocatedPixmap *>::iterator farthestPixmap = pEnd;
392
393
394
395
    const int currentViewportPage = (*m_viewportIterator).pageNumber;

    /* Find the pixmap that is farthest from the current viewport */
    int maxDistance = -1;
Albert Astals Cid's avatar
Albert Astals Cid committed
396
397
    while (pIt != pEnd) {
        const AllocatedPixmap *p = *pIt;
398
        // Filter by observer
Albert Astals Cid's avatar
Albert Astals Cid committed
399
400
401
        if (observer == nullptr || p->observer == observer) {
            const int distance = qAbs(p->page - currentViewportPage);
            if (maxDistance < distance && (!unloadableOnly || p->observer->canUnloadPixmap(p->page))) {
402
403
404
                maxDistance = distance;
                farthestPixmap = pIt;
            }
405
        }
406
        ++pIt;
407
    }
408
409

    /* No pixmap to remove */
Albert Astals Cid's avatar
Albert Astals Cid committed
410
    if (farthestPixmap == pEnd)
411
        return nullptr;
412

Albert Astals Cid's avatar
Albert Astals Cid committed
413
414
415
    AllocatedPixmap *selectedPixmap = *farthestPixmap;
    if (thenRemoveIt)
        m_allocatedPixmaps.erase(farthestPixmap);
416
    return selectedPixmap;
417
418
}

419
qulonglong DocumentPrivate::getTotalMemory()
420
{
421
    static qulonglong cachedValue = 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
422
    if (cachedValue)
423
        return cachedValue;
424

Pino Toscano's avatar
Pino Toscano committed
425
#if defined(Q_OS_LINUX)
426
    // if /proc/meminfo doesn't exist, return 128MB
Albert Astals Cid's avatar
Albert Astals Cid committed
427
428
    QFile memFile(QStringLiteral("/proc/meminfo"));
    if (!memFile.open(QIODevice::ReadOnly))
429
        return (cachedValue = 134217728);
430

Albert Astals Cid's avatar
Albert Astals Cid committed
431
432
    QTextStream readStream(&memFile);
    while (true) {
433
        QString entry = readStream.readLine();
Albert Astals Cid's avatar
Albert Astals Cid committed
434
435
436
437
        if (entry.isNull())
            break;
        if (entry.startsWith(QLatin1String("MemTotal:")))
            return (cachedValue = (Q_UINT64_C(1024) * entry.section(QLatin1Char(' '), -2, -2).toULongLong()));
438
    }
439
440
441
#elif defined(Q_OS_FREEBSD)
    qulonglong physmem;
    int mib[] = {CTL_HW, HW_PHYSMEM};
Albert Astals Cid's avatar
Albert Astals Cid committed
442
443
    size_t len = sizeof(physmem);
    if (sysctl(mib, 2, &physmem, &len, NULL, 0) == 0)
444
        return (cachedValue = physmem);
445
446
#elif defined(Q_OS_WIN)
    MEMORYSTATUSEX stat;
447
    stat.dwLength = sizeof(stat);
Albert Astals Cid's avatar
Albert Astals Cid committed
448
    GlobalMemoryStatusEx(&stat);
449

Albert Astals Cid's avatar
Albert Astals Cid committed
450
    return (cachedValue = stat.ullTotalPhys);
451
452
453
#endif
    return (cachedValue = 134217728);
}
Enrico Ros's avatar
Enrico Ros committed
454

Albert Astals Cid's avatar
Albert Astals Cid committed
455
qulonglong DocumentPrivate::getFreeMemory(qulonglong *freeSwap)
456
{
457
    static QTime lastUpdate = QTime::currentTime().addSecs(-3);
458
    static qulonglong cachedValue = 0;
459
    static qulonglong cachedFreeSwap = 0;
460

Albert Astals Cid's avatar
Albert Astals Cid committed
461
    if (qAbs(lastUpdate.msecsTo(QTime::currentTime())) <= kMemCheckTime - 100) {
462
463
        if (freeSwap)
            *freeSwap = cachedFreeSwap;
464
        return cachedValue;
465
466
467
468
    }

    /* Initialize the returned free swap value to 0. It is overwritten if the
     * actual value is available */
469
470
    if (freeSwap)
        *freeSwap = 0;
471

Pino Toscano's avatar
Pino Toscano committed
472
#if defined(Q_OS_LINUX)
473
    // if /proc/meminfo doesn't exist, return MEMORY FULL
Albert Astals Cid's avatar
Albert Astals Cid committed
474
475
    QFile memFile(QStringLiteral("/proc/meminfo"));
    if (!memFile.open(QIODevice::ReadOnly))
476
        return 0;
477

478
479
    // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
    // and 'Cached' fields. consider swapped memory as used memory.
480
    qulonglong memoryFree = 0;
481
    QString entry;
Albert Astals Cid's avatar
Albert Astals Cid committed
482
    QTextStream readStream(&memFile);
483
    static const int nElems = 5;
Albert Astals Cid's avatar
Albert Astals Cid committed
484
485
486
487
    QString names[nElems] = {QStringLiteral("MemFree:"), QStringLiteral("Buffers:"), QStringLiteral("Cached:"), QStringLiteral("SwapFree:"), QStringLiteral("SwapTotal:")};
    qulonglong values[nElems] = {0, 0, 0, 0, 0};
    bool foundValues[nElems] = {false, false, false, false, false};
    while (true) {
488
        entry = readStream.readLine();
Albert Astals Cid's avatar
Albert Astals Cid committed
489
490
491
492
493
        if (entry.isNull())
            break;
        for (int i = 0; i < nElems; ++i) {
            if (entry.startsWith(names[i])) {
                values[i] = entry.section(QLatin1Char(' '), -2, -2).toULongLong(&foundValues[i]);
494
495
            }
        }
496
497
    }
    memFile.close();
498
    bool found = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
499
    for (int i = 0; found && i < nElems; ++i)
500
        found = found && foundValues[i];
Albert Astals Cid's avatar
Albert Astals Cid committed
501
    if (found) {
502
503
504
        /* MemFree + Buffers + Cached - SwapUsed =
         * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) =
         * = MemFree + Buffers + Cached + SwapFree - SwapTotal */
505
        memoryFree = values[0] + values[1] + values[2] + values[3];
Albert Astals Cid's avatar
Albert Astals Cid committed
506
        if (values[4] > memoryFree)
507
508
509
            memoryFree = 0;
        else
            memoryFree -= values[4];
Albert Astals Cid's avatar
Albert Astals Cid committed
510
    } else {
511
512
        return 0;
    }
513

514
    lastUpdate = QTime::currentTime();
515

516
    if (freeSwap)
Albert Astals Cid's avatar
Albert Astals Cid committed
517
518
        *freeSwap = (cachedFreeSwap = (Q_UINT64_C(1024) * values[3]));
    return (cachedValue = (Q_UINT64_C(1024) * memoryFree));
519
520
521
#elif defined(Q_OS_FREEBSD)
    qulonglong cache, inact, free, psize;
    size_t cachelen, inactlen, freelen, psizelen;
Albert Astals Cid's avatar
Albert Astals Cid committed
522
523
524
525
    cachelen = sizeof(cache);
    inactlen = sizeof(inact);
    freelen = sizeof(free);
    psizelen = sizeof(psize);
526
    // sum up inactive, cached and free memory
Albert Astals Cid's avatar
Albert Astals Cid committed
527
528
    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 &&
        sysctlbyname("vm.stats.vm.v_free_count", &free, &freelen, NULL, 0) == 0 && sysctlbyname("vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0) == 0) {
529
530
        lastUpdate = QTime::currentTime();
        return (cachedValue = (cache + inact + free) * psize);
Albert Astals Cid's avatar
Albert Astals Cid committed
531
    } else {
532
533
        return 0;
    }
534
#elif defined(Q_OS_WIN)
Christian Ehrlicher's avatar
Christian Ehrlicher committed
535
    MEMORYSTATUSEX stat;
536
    stat.dwLength = sizeof(stat);
Albert Astals Cid's avatar
Albert Astals Cid committed
537
    GlobalMemoryStatusEx(&stat);
538

539
540
    lastUpdate = QTime::currentTime();

541
    if (freeSwap)
Albert Astals Cid's avatar
Albert Astals Cid committed
542
543
        *freeSwap = (cachedFreeSwap = stat.ullAvailPageFile);
    return (cachedValue = stat.ullAvailPhys);
544
545
546
547
#else
    // tell the memory is full.. will act as in LOW profile
    return 0;
#endif
548
549
}

Albert Astals Cid's avatar
Albert Astals Cid committed
550
bool DocumentPrivate::loadDocumentInfo(LoadDocumentInfoFlags loadWhat)
551
552
// note: load data and stores it internally (document or pages). observers
// are still uninitialized at this point so don't access them
553
{
Albert Astals Cid's avatar
Albert Astals Cid committed
554
555
    // qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file.";
    if (m_xmlFileName.isEmpty())
556
        return false;
557

Albert Astals Cid's avatar
Albert Astals Cid committed
558
559
    QFile infoFile(m_xmlFileName);
    return loadDocumentInfo(infoFile, loadWhat);
560
561
}

Albert Astals Cid's avatar
Albert Astals Cid committed
562
bool DocumentPrivate::loadDocumentInfo(QFile &infoFile, LoadDocumentInfoFlags loadWhat)
563
{
Albert Astals Cid's avatar
Albert Astals Cid committed
564
    if (!infoFile.exists() || !infoFile.open(QIODevice::ReadOnly))
565
        return false;
566

567
    // Load DOM from XML file
Albert Astals Cid's avatar
Albert Astals Cid committed
568
569
    QDomDocument doc(QStringLiteral("documentInfo"));
    if (!doc.setContent(&infoFile)) {
Frederik Gladhorn's avatar
Frederik Gladhorn committed
570
        qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml.";
571
        infoFile.close();
572
        return false;
573
    }
574
    infoFile.close();
575

576
    QDomElement root = doc.documentElement();
577

Albert Astals Cid's avatar
Albert Astals Cid committed
578
    if (root.tagName() != QLatin1String("documentInfo"))
579
        return false;
580

581
    bool loadedAnything = false; // set if something gets actually loaded
582
583
584

    // Parse the DOM tree
    QDomNode topLevelNode = root.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
585
    while (topLevelNode.isElement()) {
586
        QString catName = topLevelNode.toElement().tagName();
587

588
        // Restore page attributes (bookmark, annotations, ...) from the DOM
Albert Astals Cid's avatar
Albert Astals Cid committed
589
        if (catName == QLatin1String("pageList") && (loadWhat & LoadPageInfo)) {
590
            QDomNode pageNode = topLevelNode.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
591
            while (pageNode.isElement()) {
592
                QDomElement pageElement = pageNode.toElement();
Albert Astals Cid's avatar
Albert Astals Cid committed
593
                if (pageElement.hasAttribute(QStringLiteral("number"))) {
594
595
                    // get page number (node's attribute)
                    bool ok;
Albert Astals Cid's avatar
Albert Astals Cid committed
596
                    int pageNumber = pageElement.attribute(QStringLiteral("number")).toInt(&ok);
597
598

                    // pass the domElement to the right page, to read config data from
Albert Astals Cid's avatar
Albert Astals Cid committed
599
600
                    if (ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count()) {
                        if (m_pagesVector[pageNumber]->d->restoreLocalContents(pageElement))
601
602
                            loadedAnything = true;
                    }
603
604
                }
                pageNode = pageNode.nextSibling();
605
606
607
            }
        }

608
        // Restore 'general info' from the DOM
Albert Astals Cid's avatar
Albert Astals Cid committed
609
        else if (catName == QLatin1String("generalInfo") && (loadWhat & LoadGeneralInfo)) {
610
            QDomNode infoNode = topLevelNode.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
611
            while (infoNode.isElement()) {
612
                QDomElement infoElement = infoNode.toElement();
613

614
                // restore viewports history
Albert Astals Cid's avatar
Albert Astals Cid committed
615
                if (infoElement.tagName() == QLatin1String("history")) {
616
617
618
619
                    // clear history
                    m_viewportHistory.clear();
                    // append old viewports
                    QDomNode historyNode = infoNode.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
620
                    while (historyNode.isElement()) {
621
                        QDomElement historyElement = historyNode.toElement();
Albert Astals Cid's avatar
Albert Astals Cid committed
622
623
624
                        if (historyElement.hasAttribute(QStringLiteral("viewport"))) {
                            QString vpString = historyElement.attribute(QStringLiteral("viewport"));
                            m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(), DocumentViewport(vpString));
625
                            loadedAnything = true;
626
627
628
                        }
                        historyNode = historyNode.nextSibling();
                    }
Yuri Chornoivan's avatar
Yuri Chornoivan committed
629
                    // consistency check
Albert Astals Cid's avatar
Albert Astals Cid committed
630
631
632
                    if (m_viewportHistory.isEmpty())
                        m_viewportIterator = m_viewportHistory.insert(m_viewportHistory.end(), DocumentViewport());
                } else if (infoElement.tagName() == QLatin1String("rotation")) {
633
634
                    QString str = infoElement.text();
                    bool ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
635
636
637
                    int newrotation = !str.isEmpty() ? (str.toInt(&ok) % 4) : 0;
                    if (ok && newrotation != 0) {
                        setRotationInternal(newrotation, false);
638
                        loadedAnything = true;
639
                    }
Albert Astals Cid's avatar
Albert Astals Cid committed
640
                } else if (infoElement.tagName() == QLatin1String("views")) {
641
                    QDomNode viewNode = infoNode.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
642
                    while (viewNode.isElement()) {
643
                        QDomElement viewElement = viewNode.toElement();
Albert Astals Cid's avatar
Albert Astals Cid committed
644
645
646
647
648
                        if (viewElement.tagName() == QLatin1String("view")) {
                            const QString viewName = viewElement.attribute(QStringLiteral("name"));
                            for (View *view : qAsConst(m_views)) {
                                if (view->name() == viewName) {
                                    loadViewsInfo(view, viewElement);
649
                                    loadedAnything = true;
650
651
652
653
654
655
656
                                    break;
                                }
                            }
                        }
                        viewNode = viewNode.nextSibling();
                    }
                }
657
658
659
                infoNode = infoNode.nextSibling();
            }
        }
660

661
662
        topLevelNode = topLevelNode.nextSibling();
    } // </documentInfo>
663
664

    return loadedAnything;
665
666
}

Albert Astals Cid's avatar
Albert Astals Cid committed
667
void DocumentPrivate::loadViewsInfo(View *view, const QDomElement &e)
668
669
{
    QDomNode viewNode = e.firstChild();
Albert Astals Cid's avatar
Albert Astals Cid committed
670
    while (viewNode.isElement()) {
671
672
        QDomElement viewElement = viewNode.toElement();

Albert Astals Cid's avatar
Albert Astals Cid committed
673
674
        if (viewElement.tagName() == QLatin1String("zoom")) {
            const QString valueString = viewElement.attribute(QStringLiteral("value"));
675
            bool newzoom_ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
676
677
678
            const double newzoom = !valueString.isEmpty() ? valueString.toDouble(&newzoom_ok) : 1.0;
            if (newzoom_ok && newzoom != 0 && view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable))) {
                view->setCapability(View::Zoom, newzoom);
679
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
680
            const QString modeString = viewElement.attribute(QStringLiteral("mode"));
681
            bool newmode_ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
682
683
684
            const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
            if (newmode_ok && view->supportsCapability(View::ZoomModality) && (view->capabilityFlags(View::ZoomModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
                view->setCapability(View::ZoomModality, newmode);
685
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
686
687
        } else if (viewElement.tagName() == QLatin1String("viewMode")) {
            const QString modeString = viewElement.attribute(QStringLiteral("mode"));
Felix Mauch's avatar
Felix Mauch committed
688
            bool newmode_ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
689
690
691
            const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
            if (newmode_ok && view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
                view->setCapability(View::ViewModeModality, newmode);
Felix Mauch's avatar
Felix Mauch committed
692
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
693
694
        } else if (viewElement.tagName() == QLatin1String("continuous")) {
            const QString modeString = viewElement.attribute(QStringLiteral("mode"));
Felix Mauch's avatar
Felix Mauch committed
695
            bool newmode_ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
696
697
698
            const int newmode = !modeString.isEmpty() ? modeString.toInt(&newmode_ok) : 2;
            if (newmode_ok && view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
                view->setCapability(View::Continuous, newmode);
Felix Mauch's avatar
Felix Mauch committed
699
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
700
701
        } else if (viewElement.tagName() == QLatin1String("trimMargins")) {
            const QString valueString = viewElement.attribute(QStringLiteral("value"));
Felix Mauch's avatar
Felix Mauch committed
702
            bool newmode_ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
703
704
705
            const int newmode = !valueString.isEmpty() ? valueString.toInt(&newmode_ok) : 2;
            if (newmode_ok && view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
                view->setCapability(View::TrimMargins, newmode);
Felix Mauch's avatar
Felix Mauch committed
706
707
            }
        }
708
709
710
711
712

        viewNode = viewNode.nextSibling();
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
713
void DocumentPrivate::saveViewsInfo(View *view, QDomElement &e) const
714
{
Albert Astals Cid's avatar
Albert Astals Cid committed
715
716
717
718
    if (view->supportsCapability(View::Zoom) && (view->capabilityFlags(View::Zoom) & (View::CapabilityRead | View::CapabilitySerializable)) && view->supportsCapability(View::ZoomModality) &&
        (view->capabilityFlags(View::ZoomModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
        QDomElement zoomEl = e.ownerDocument().createElement(QStringLiteral("zoom"));
        e.appendChild(zoomEl);
719
        bool ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
720
721
722
        const double zoom = view->capability(View::Zoom).toDouble(&ok);
        if (ok && zoom != 0) {
            zoomEl.setAttribute(QStringLiteral("value"), QString::number(zoom));
723
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
724
725
726
        const int mode = view->capability(View::ZoomModality).toInt(&ok);
        if (ok) {
            zoomEl.setAttribute(QStringLiteral("mode"), mode);
727
728
        }
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
729
730
731
732
733
734
735
736
737
    if (view->supportsCapability(View::Continuous) && (view->capabilityFlags(View::Continuous) & (View::CapabilityRead | View::CapabilitySerializable))) {
        QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("continuous"));
        e.appendChild(contEl);
        const bool mode = view->capability(View::Continuous).toBool();
        contEl.setAttribute(QStringLiteral("mode"), mode);
    }
    if (view->supportsCapability(View::ViewModeModality) && (view->capabilityFlags(View::ViewModeModality) & (View::CapabilityRead | View::CapabilitySerializable))) {
        QDomElement viewEl = e.ownerDocument().createElement(QStringLiteral("viewMode"));
        e.appendChild(viewEl);
Felix Mauch's avatar
Felix Mauch committed
738
        bool ok = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
739
740
741
        const int mode = view->capability(View::ViewModeModality).toInt(&ok);
        if (ok) {
            viewEl.setAttribute(QStringLiteral("mode"), mode);
Felix Mauch's avatar
Felix Mauch committed
742
743
        }
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
744
745
746
747
748
    if (view->supportsCapability(View::TrimMargins) && (view->capabilityFlags(View::TrimMargins) & (View::CapabilityRead | View::CapabilitySerializable))) {
        QDomElement contEl = e.ownerDocument().createElement(QStringLiteral("trimMargins"));
        e.appendChild(contEl);
        const bool value = view->capability(View::TrimMargins).toBool();
        contEl.setAttribute(QStringLiteral("value"), value);
Felix Mauch's avatar
Felix Mauch committed
749
    }
750
751
}

Albert Astals Cid's avatar
Albert Astals Cid committed
752
QUrl DocumentPrivate::giveAbsoluteUrl(const QString &fileName) const
753
{
Albert Astals Cid's avatar
Albert Astals Cid committed
754
    if (!QDir::isRelativePath(fileName))
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
755
        return QUrl::fromLocalFile(fileName);
756

Albert Astals Cid's avatar
Albert Astals Cid committed
757
    if (!m_url.isValid())
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
758
        return QUrl();
759

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
760
    return QUrl(KIO::upUrl(m_url).toString() + fileName);
761
762
}

Albert Astals Cid's avatar
Albert Astals Cid committed
763
bool DocumentPrivate::openRelativeFile(const QString &fileName)
Albert Astals Cid's avatar
Albert Astals Cid committed
764
{
765
766
    const QUrl newUrl = giveAbsoluteUrl(fileName);
    if (newUrl.isEmpty())
767
        return false;
768

769
    qCDebug(OkularCoreDebug).nospace() << "openRelativeFile: '" << newUrl << "'";
Albert Astals Cid's avatar
Albert Astals Cid committed
770

771
772
    emit m_parent->openUrl(newUrl);
    return m_url == newUrl;
773
774
}

Albert Astals Cid's avatar
Albert Astals Cid committed
775
Generator *DocumentPrivate::loadGeneratorLibrary(const KPluginMetaData &service)
776
{
Albert Astals Cid's avatar
Albert Astals Cid committed
777
    KPluginLoader loader(service.fileName());
778
    qCDebug(OkularCoreDebug) << service.fileName();
779
    KPluginFactory *factory = loader.factory();
Albert Astals Cid's avatar
Albert Astals Cid committed
780
    if (!factory) {
781
        qCWarning(OkularCoreDebug).nospace() << "Invalid plugin factory for " << service.fileName() << ":" << loader.errorString();
782
        return nullptr;
Vishesh Handa's avatar
Vishesh Handa committed
783
784
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
785
    Generator *plugin = factory->create<Okular::Generator>();
Vishesh Handa's avatar
Vishesh Handa committed
786

Albert Astals Cid's avatar
Albert Astals Cid committed
787
788
    GeneratorInfo info(plugin, service);
    m_loadedGenerators.insert(service.pluginId(), info);
789
    return plugin;
790
791
}

792
void DocumentPrivate::loadAllGeneratorLibraries()
793
{
Albert Astals Cid's avatar
Albert Astals Cid committed
794
    if (m_generatorsLoaded)
795
796
        return;

Albert Astals Cid's avatar
Albert Astals Cid committed
797
    loadServiceList(availableGenerators());
798

799
    m_generatorsLoaded = true;
800
801
}

Albert Astals Cid's avatar
Albert Astals Cid committed
802
void DocumentPrivate::loadServiceList(const QVector<KPluginMetaData> &offers)
803
{
804
    int count = offers.count();
Albert Astals Cid's avatar
Albert Astals Cid committed
805
    if (count <= 0)
806
807
        return;

Albert Astals Cid's avatar
Albert Astals Cid committed
808
    for (int i = 0; i < count; ++i) {
809
        QString id = offers.at(i).pluginId();
810
        // don't load already loaded generators
Albert Astals Cid's avatar
Albert Astals Cid committed
811
812
        QHash<QString, GeneratorInfo>::const_iterator genIt = m_loadedGenerators.constFind(id);
        if (!m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd())