document.cpp 92.4 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it>             *
3
 *   Copyright (C) 2004-2005 by Albert Astals Cid <tsdgeos@terra.es>       *
4 5 6 7 8 9 10
 *                                                                         *
 *   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
11 12
#include "document.h"

13
// qt/kde/system includes
14
#include <QtCore/QtAlgorithms>
15
#include <QtCore/QDir>
16 17
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
18
#include <QtCore/QHash>
19
#include <QtCore/QMap>
20
#include <QtCore/QMutex>
21
#include <QtCore/QProcess>
22 23 24 25
#include <QtCore/QTextStream>
#include <QtCore/QTimer>
#include <QtGui/QApplication>

26
#include <kaboutdata.h>
27
#include <kauthorized.h>
28
#include <kcomponentdata.h>
29
#include <kconfigdialog.h>
Enrico Ros's avatar
Enrico Ros committed
30
#include <kdebug.h>
31 32
#include <klibloader.h>
#include <klocale.h>
33
#include <kmessagebox.h>
34
#include <kmimetypetrader.h>
35
#include <krun.h>
36
#include <kservicetypetrader.h>
37
#include <kstandarddirs.h>
38
#include <ktemporaryfile.h>
39
#include <ktoolinvocation.h>
40 41

// local includes
Pino Toscano's avatar
Pino Toscano committed
42
#include "action.h"
43
#include "audioplayer.h"
44
#include "audioplayer_p.h"
45
#include "bookmarkmanager.h"
46
#include "chooseenginedialog_p.h"
47
#include "generator.h"
48
#include "generator_p.h"
49 50 51
#include "interfaces/configinterface.h"
#include "interfaces/guiinterface.h"
#include "interfaces/printinterface.h"
52
#include "observer.h"
53
#include "page.h"
54
#include "page_p.h"
55
#include "pagecontroller_p.h"
56 57
#include "settings.h"
#include "sourcereference.h"
58

59 60
#include <config-okular.h>

61 62 63 64
#ifdef Q_OS_WIN
#include <windows.h>
#endif

65 66
using namespace Okular;

Pino Toscano's avatar
Pino Toscano committed
67 68
static int OkularDebug = 4650;

69
struct AllocatedPixmap
70
{
71 72 73
    // owner of the page
    int id;
    int page;
74
    qulonglong memory;
75
    // public constructor: initialize data
76
    AllocatedPixmap( int i, int p, qulonglong m ) : id( i ), page( p ), memory( m ) {}
77 78
};

79 80
struct RunningSearch
{
81 82
    // store search properties
    int continueOnPage;
83
    RegularAreaRect continueOnMatch;
84
    QLinkedList< int > highlightedPages;
85 86 87

    // fields related to previous searches (used for 'continueSearch')
    QString cachedString;
88
    Document::SearchType cachedType;
89
    Qt::CaseSensitivity cachedCaseSensitivity;
90 91 92
    bool cachedViewportMove;
    bool cachedNoDialogs;
    QColor cachedColor;
93 94
};

95 96 97
struct GeneratorInfo
{
    GeneratorInfo()
98
        : generator( 0 ), library( 0 ), hasConfig( false )
99 100 101
    {}

    Generator * generator;
102
    KLibrary * library;
103
    QString catalogName;
104
    bool hasConfig;
105 106
};

107
#define foreachObserver( cmd ) {\
108
    QMap< int, DocumentObserver * >::const_iterator it=d->m_observers.begin(), end=d->m_observers.end();\
109
    for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
110

111 112 113 114
#define foreachObserverD( cmd ) {\
    QMap< int, DocumentObserver * >::const_iterator it = m_observers.begin(), end = m_observers.end();\
    for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }

115
/***** Document ******/
116

117
class Okular::DocumentPrivate
118
{
119
    public:
120
        DocumentPrivate( Document *parent )
121 122
          : m_parent( parent ),
            m_lastSearchID( -1 ),
123
            m_tempFile( 0 ),
124
            m_docSize( -1 ),
125 126
            m_allocatedPixmapsTotalMemory( 0 ),
            m_warnedOutOfMemory( false ),
127
            m_rotation( Rotation0 ),
128
            m_exportCached( false ),
129 130 131 132
            m_bookmarkManager( 0 ),
            m_memCheckTimer( 0 ),
            m_saveBookmarksTimer( 0 ),
            m_generator( 0 ),
133 134
            m_generatorsLoaded( false ),
            m_fontsCached( false )
135 136 137
        {
        }

138 139 140
        // private methods
        QString pagesSizeString() const;
        QString localizedSize(const QSizeF &size) const;
141 142 143
        void cleanupPixmapMemory( qulonglong bytesOffset = 0 );
        qulonglong getTotalMemory();
        qulonglong getFreeMemory();
144
        void loadDocumentInfo();
145
        QString giveAbsolutePath( const QString & fileName ) const;
146
        bool openRelativeFile( const QString & fileName );
147 148
        Generator * loadGeneratorLibrary( const QString& name, const QString& libname );
        void loadAllGeneratorLibraries();
149
        void loadServiceList( const KService::List& offers );
150
        void unloadGenerator( const GeneratorInfo& info );
151
        void cacheExportFormats();
152 153 154 155 156 157

        // private slots
        void saveDocumentInfo() const;
        void slotTimedMemoryCheck();
        void sendGeneratorRequest();
        void rotationFinished( int page );
158 159
        void fontReadingProgress( int page );
        void fontReadingGotFont( const Okular::FontInfo& font );
160
        void slotGeneratorConfigChanged( const QString& );
161 162 163

        // member variables
        Document *m_parent;
164

165 166 167
        // find descriptors, mapped by ID (we handle multiple searches)
        QMap< int, RunningSearch * > m_searches;
        int m_lastSearchID;
168

169 170 171
        // needed because for remote documents docFileName is a local file and
        // we want the remote url when the document refers to relativeNames
        KUrl m_url;
172

173 174 175
        // cached stuff
        QString m_docFileName;
        QString m_xmlFileName;
176
        KTemporaryFile *m_tempFile;
177
        int m_docSize;
178

179 180 181 182
        // viewport stuff
        QLinkedList< DocumentViewport > m_viewportHistory;
        QLinkedList< DocumentViewport >::iterator m_viewportIterator;
        DocumentViewport m_nextDocumentViewport; // see Link::Goto for an explanation
183

184 185 186
        // observers / requests / allocator stuff
        QMap< int, DocumentObserver * > m_observers;
        QLinkedList< PixmapRequest * > m_pixmapRequestsStack;
187
        QMutex m_pixmapRequestsMutex;
188
        QLinkedList< AllocatedPixmap * > m_allocatedPixmapsFifo;
189
        qulonglong m_allocatedPixmapsTotalMemory;
190
        bool m_warnedOutOfMemory;
191

192 193
        // the rotation applied to the document
        Rotation m_rotation;
194

195 196 197 198 199
        // the current size of the pages (if available), and the cache of the
        // available page sizes
        PageSize m_pageSize;
        PageSize::List m_pageSizes;

200 201 202 203 204
        // cache of the export formats
        bool m_exportCached;
        ExportFormat::List m_exportFormats;
        ExportFormat m_exportToText;

205 206 207 208 209 210 211
        // our bookmark manager
        BookmarkManager *m_bookmarkManager;

        // timers (memory checking / info saver)
        QTimer *m_memCheckTimer;
        QTimer *m_saveBookmarksTimer;

212
        QHash<QString, GeneratorInfo> m_loadedGenerators;
213
        Generator * m_generator;
214
        QString m_generatorName;
215
        bool m_generatorsLoaded;
216 217
        QVector< Page * > m_pagesVector;
        QVector< VisiblePageRect * > m_pageRects;
218 219 220

        // cache of the mimetype we support
        QStringList m_supportedMimeTypes;
221

222
        QPointer< FontExtractionThread > m_fontThread;
223 224
        bool m_fontsCached;
        FontInfo::List m_fontsCache;
225 226
};

227
QString DocumentPrivate::pagesSizeString() const
228 229
{
    if (m_generator)
230
    {
231
        if (m_generator->pagesSizeMetric() != Generator::None)
232
        {
233 234 235
            QSizeF size = m_parent->allPagesSize();
            if (size.isValid()) return localizedSize(size);
            else return QString();
236
        }
237
        else return QString();
238
    }
239 240
    else return QString();
}
241

242
QString DocumentPrivate::localizedSize(const QSizeF &size) const
243 244 245
{
    double inchesWidth = 0, inchesHeight = 0;
    switch (m_generator->pagesSizeMetric())
246
    {
247 248 249 250
        case Generator::Points:
            inchesWidth = size.width() / 72.0;
            inchesHeight = size.height() / 72.0;
        break;
251

252 253
        case Generator::None:
        break;
254
    }
255
    if (KGlobal::locale()->measureSystem() == KLocale::Imperial)
256
    {
257
        return i18n("%1 x %2 in", inchesWidth, inchesHeight);
258
    }
259
    else
260
    {
261
        return i18n("%1 x %2 mm", inchesWidth * 25.4, inchesHeight * 25.4);
262
    }
263 264
}

265
void DocumentPrivate::cleanupPixmapMemory( qulonglong /*sure? bytesOffset*/ )
266
{
267
    // [MEM] choose memory parameters based on configuration profile
268 269
    qulonglong clipValue = -1;
    qulonglong memoryToFree = -1;
270
    switch ( Settings::memoryLevel() )
271
    {
272 273 274 275 276 277 278 279 280 281 282 283
        case Settings::EnumMemoryLevel::Low:
            memoryToFree = m_allocatedPixmapsTotalMemory;
            break;

        case Settings::EnumMemoryLevel::Normal:
            memoryToFree = m_allocatedPixmapsTotalMemory - getTotalMemory() / 3;
            clipValue = (m_allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
            break;

        case Settings::EnumMemoryLevel::Aggressive:
            clipValue = (m_allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
            break;
284
    }
285

286 287 288 289
    if ( clipValue > memoryToFree )
        memoryToFree = clipValue;

    if ( memoryToFree > 0 )
290
    {
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
        // [MEM] free memory starting from older pixmaps
        int pagesFreed = 0;
        QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmapsFifo.begin();
        QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmapsFifo.end();
        while ( (pIt != pEnd) && (memoryToFree > 0) )
        {
            AllocatedPixmap * p = *pIt;
            if ( m_observers.value( p->id )->canUnloadPixmap( p->page ) )
            {
                // update internal variables
                pIt = m_allocatedPixmapsFifo.erase( pIt );
                m_allocatedPixmapsTotalMemory -= p->memory;
                memoryToFree -= p->memory;
                pagesFreed++;
                // delete pixmap
                m_pagesVector.at( p->page )->deletePixmap( p->id );
                // delete allocation descriptor
                delete p;
            } else
                ++pIt;
        }
        //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmapsFifo.count() + pagesFreed, pagesFreed, m_allocatedPixmapsFifo.count() );
313
    }
314 315
}

316
qulonglong DocumentPrivate::getTotalMemory()
317
{
318
    static qulonglong cachedValue = 0;
319 320
    if ( cachedValue )
        return cachedValue;
321

322 323 324 325 326
#ifdef __linux__
    // if /proc/meminfo doesn't exist, return 128MB
    QFile memFile( "/proc/meminfo" );
    if ( !memFile.open( QIODevice::ReadOnly ) )
        return (cachedValue = 134217728);
327

328 329 330 331
    // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
    // and 'Cached' fields. consider swapped memory as used memory.
    QTextStream readStream( &memFile );
     while ( true )
332
    {
333
        QString entry = readStream.readLine();
334
        if ( entry.isNull() ) break;
335 336
        if ( entry.startsWith( "MemTotal:" ) )
            return (cachedValue = (1024 * entry.section( ' ', -2, -2 ).toInt()));
337
    }
338 339 340 341 342 343
#elif defined(Q_OS_WIN)
    MEMORYSTATUSEX stat;

    GlobalMemoryStatusEx (&stat);

    return stat.ullTotalPhys;
344 345 346
#endif
    return (cachedValue = 134217728);
}
Enrico Ros's avatar
Enrico Ros committed
347

348
qulonglong DocumentPrivate::getFreeMemory()
349 350
{
    static QTime lastUpdate = QTime::currentTime();
351
    static qulonglong cachedValue = 0;
352

353 354
    if ( lastUpdate.secsTo( QTime::currentTime() ) <= 2 )
        return cachedValue;
355

356 357 358 359 360
#ifdef __linux__
    // if /proc/meminfo doesn't exist, return MEMORY FULL
    QFile memFile( "/proc/meminfo" );
    if ( !memFile.open( QIODevice::ReadOnly ) )
        return 0;
361

362 363
    // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
    // and 'Cached' fields. consider swapped memory as used memory.
364
    qulonglong memoryFree = 0;
365 366 367 368 369
    QString entry;
    QTextStream readStream( &memFile );
    while ( true )
    {
        entry = readStream.readLine();
370
        if ( entry.isNull() ) break;
371 372 373 374 375 376 377 378 379
        if ( entry.startsWith( "MemFree:" ) ||
                entry.startsWith( "Buffers:" ) ||
                entry.startsWith( "Cached:" ) ||
                entry.startsWith( "SwapFree:" ) )
            memoryFree += entry.section( ' ', -2, -2 ).toInt();
        if ( entry.startsWith( "SwapTotal:" ) )
            memoryFree -= entry.section( ' ', -2, -2 ).toInt();
    }
    memFile.close();
380

381
    lastUpdate = QTime::currentTime();
382

383
    return ( cachedValue = (1024 * memoryFree) );
384
#elif defined(Q_OS_WIN)
Christian Ehrlicher's avatar
Christian Ehrlicher committed
385
    MEMORYSTATUSEX stat;
386

Christian Ehrlicher's avatar
Christian Ehrlicher committed
387
    GlobalMemoryStatusEx (&stat);
388 389

    return stat.ullAvailPhys;
390 391 392 393
#else
    // tell the memory is full.. will act as in LOW profile
    return 0;
#endif
394 395
}

396
void DocumentPrivate::loadDocumentInfo()
397 398
// note: load data and stores it internally (document or pages). observers
// are still uninitialized at this point so don't access them
399
{
400
    //kDebug(OkularDebug) << "Using '" << d->m_xmlFileName << "' as document info file." << endl;
401 402 403
    if ( m_xmlFileName.isEmpty() )
        return;

404 405 406
    QFile infoFile( m_xmlFileName );
    if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) )
        return;
407

408 409 410
    // Load DOM from XML file
    QDomDocument doc( "documentInfo" );
    if ( !doc.setContent( &infoFile ) )
411
    {
412
        kDebug(OkularDebug) << "Can't load XML pair! Check for broken xml." << endl;
413 414
        infoFile.close();
        return;
415
    }
416
    infoFile.close();
417

418 419 420 421 422 423 424
    QDomElement root = doc.documentElement();
    if ( root.tagName() != "documentInfo" )
        return;

    // Parse the DOM tree
    QDomNode topLevelNode = root.firstChild();
    while ( topLevelNode.isElement() )
425
    {
426
        QString catName = topLevelNode.toElement().tagName();
427

428 429
        // Restore page attributes (bookmark, annotations, ...) from the DOM
        if ( catName == "pageList" )
430
        {
431 432
            QDomNode pageNode = topLevelNode.firstChild();
            while ( pageNode.isElement() )
433
            {
434 435 436 437 438 439 440 441 442
                QDomElement pageElement = pageNode.toElement();
                if ( pageElement.hasAttribute( "number" ) )
                {
                    // get page number (node's attribute)
                    bool ok;
                    int pageNumber = pageElement.attribute( "number" ).toInt( &ok );

                    // pass the domElement to the right page, to read config data from
                    if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() )
443
                        m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement );
444 445
                }
                pageNode = pageNode.nextSibling();
446 447 448
            }
        }

449 450 451 452 453 454 455
        // Restore 'general info' from the DOM
        else if ( catName == "generalInfo" )
        {
            QDomNode infoNode = topLevelNode.firstChild();
            while ( infoNode.isElement() )
            {
                QDomElement infoElement = infoNode.toElement();
456

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
                // restore viewports history
                if ( infoElement.tagName() == "history" )
                {
                    // clear history
                    m_viewportHistory.clear();
                    // append old viewports
                    QDomNode historyNode = infoNode.firstChild();
                    while ( historyNode.isElement() )
                    {
                        QDomElement historyElement = historyNode.toElement();
                        if ( historyElement.hasAttribute( "viewport" ) )
                        {
                            QString vpString = historyElement.attribute( "viewport" );
                            m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(),
                                    DocumentViewport( vpString ) );
                        }
                        historyNode = historyNode.nextSibling();
                    }
                    // consistancy check
                    if ( m_viewportHistory.isEmpty() )
                        m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() );
                }
                infoNode = infoNode.nextSibling();
            }
        }
482

483 484
        topLevelNode = topLevelNode.nextSibling();
    } // </documentInfo>
485 486
}

487
QString DocumentPrivate::giveAbsolutePath( const QString & fileName ) const
488
{
489 490
    if ( !m_url.isValid() )
        return QString();
491

492 493 494
    if ( !QDir::isRelativePath( fileName ) )
        return fileName;

495
    return m_url.upUrl().url() + fileName;
496 497
}

498
bool DocumentPrivate::openRelativeFile( const QString & fileName )
Albert Astals Cid's avatar
Albert Astals Cid committed
499
{
500 501 502
    QString absFileName = giveAbsolutePath( fileName );
    if ( absFileName.isEmpty() )
        return false;
503

504
    kDebug(OkularDebug) << "openDocument: '" << absFileName << "'" << endl;
Albert Astals Cid's avatar
Albert Astals Cid committed
505

506 507
    emit m_parent->openUrl( absFileName );
    return true;
508 509
}

510
Generator * DocumentPrivate::loadGeneratorLibrary( const QString& name, const QString& libname )
511
{
512
    KLibrary *lib = KLibLoader::self()->library( QFile::encodeName( libname ), QLibrary::ExportExternalSymbolsHint );
513 514 515
    if ( !lib )
    {
        kWarning() << "Could not load '" << libname << "' library." << endl;
Tobias Koenig's avatar
Tobias Koenig committed
516
        kWarning() << KLibLoader::self()->lastErrorMessage() << endl;
Tobias Koenig's avatar
Tobias Koenig committed
517
        emit m_parent->error( i18n( "Could not load the necessary plugin to view the document" ), -1 );
518 519 520
        return 0;
    }

Pino Toscano's avatar
Pino Toscano committed
521
    Generator* (*create_plugin)() = ( Generator* (*)() ) lib->resolveFunction( "create_plugin" );
522 523 524 525 526
    if ( !create_plugin )
    {
        kWarning(OkularDebug) << "Broken generator " << libname << ": missing create_plugin()!" << endl;
        return 0;
    }
527 528 529 530 531 532 533 534
    Generator * generator = create_plugin();
    if ( !generator )
    {
        kWarning() << "Broken generator " << libname << "!" << endl;
        return 0;
    }
    GeneratorInfo info;
    info.generator = generator;
535
    info.library = lib;
536
    if ( generator->componentData() && generator->componentData()->aboutData() )
537
        info.catalogName = generator->componentData()->aboutData()->catalogName();
538
    m_loadedGenerators.insert( name, info );
539 540 541
    return generator;
}

542
void DocumentPrivate::loadAllGeneratorLibraries()
543 544 545 546 547 548 549 550
{
    if ( m_generatorsLoaded )
        return;

    m_generatorsLoaded = true;

    QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
    KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint );
551 552 553
    loadServiceList( offers );
}

554
void DocumentPrivate::loadServiceList( const KService::List& offers )
555
{
556 557 558 559 560 561 562 563
    int count = offers.count();
    if ( count <= 0 )
        return;

    for ( int i = 0; i < count; ++i )
    {
        QString propName = offers.at(i)->name();
        // don't load already loaded generators
564
        QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName );
565 566 567 568 569 570 571 572
        if ( genIt != m_loadedGenerators.end() )
            continue;

        Generator * g = loadGeneratorLibrary( propName, offers.at(i)->library() );
        (void)g;
    }
}

573
void DocumentPrivate::unloadGenerator( const GeneratorInfo& info )
574 575 576 577 578
{
    delete info.generator;
    info.library->unload();
}

579
void DocumentPrivate::cacheExportFormats()
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
{
    if ( m_exportCached )
        return;

    const ExportFormat::List formats = m_generator->exportFormats();
    for ( int i = 0; i < formats.count(); ++i )
    {
        if ( formats.at( i ).mimeType()->name() == QLatin1String( "text/plain" ) )
            m_exportToText = formats.at( i );
        else
            m_exportFormats.append( formats.at( i ) );
    }

    m_exportCached = true;
}

596
void DocumentPrivate::saveDocumentInfo() const
597
{
598 599
    if ( m_docFileName.isEmpty() )
        return;
600

601 602 603 604 605 606 607
    QFile infoFile( m_xmlFileName );
    if (infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate) )
    {
        // 1. Create DOM
        QDomDocument doc( "documentInfo" );
        QDomElement root = doc.createElement( "documentInfo" );
        doc.appendChild( root );
Albert Astals Cid's avatar
Albert Astals Cid committed
608

609 610 611 612 613 614
        // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
        QDomElement pageList = doc.createElement( "pageList" );
        root.appendChild( pageList );
        // <page list><page number='x'>.... </page> save pages that hold data
        QVector< Page * >::const_iterator pIt = m_pagesVector.begin(), pEnd = m_pagesVector.end();
        for ( ; pIt != pEnd; ++pIt )
615
            (*pIt)->d->saveLocalContents( pageList, doc );
616

617 618 619 620 621 622 623 624 625 626 627
        // 2.2. Save document info (current viewport, history, ... ) to DOM
        QDomElement generalInfo = doc.createElement( "generalInfo" );
        root.appendChild( generalInfo );
        // <general info><history> ... </history> save history up to 10 viewports
        QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator;
        if ( backIterator != m_viewportHistory.end() )
        {
            // go back up to 10 steps from the current viewportIterator
            int backSteps = 10;
            while ( backSteps-- && backIterator != m_viewportHistory.begin() )
                --backIterator;
628

629 630 631
            // create history root node
            QDomElement historyNode = doc.createElement( "history" );
            generalInfo.appendChild( historyNode );
632

633 634 635 636 637 638 639 640 641 642 643 644
            // add old[backIterator] and present[viewportIterator] items
            QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator;
            ++endIt;
            while ( backIterator != endIt )
            {
                QString name = (backIterator == m_viewportIterator) ? "current" : "oldPage";
                QDomElement historyEntry = doc.createElement( name );
                historyEntry.setAttribute( "viewport", (*backIterator).toString() );
                historyNode.appendChild( historyEntry );
                ++backIterator;
            }
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
645

646 647 648 649 650 651
        // 3. Save DOM to XML file
        QString xml = doc.toString();
        QTextStream os( &infoFile );
        os << xml;
    }
    infoFile.close();
Albert Astals Cid's avatar
Albert Astals Cid committed
652 653
}

654
void DocumentPrivate::slotTimedMemoryCheck()
Enrico Ros's avatar
Enrico Ros committed
655
{
656 657 658 659
    // [MEM] clean memory (for 'free mem dependant' profiles only)
    if ( Settings::memoryLevel() != Settings::EnumMemoryLevel::Low &&
         m_allocatedPixmapsTotalMemory > 1024*1024 )
        cleanupPixmapMemory();
Enrico Ros's avatar
Enrico Ros committed
660 661
}

662
void DocumentPrivate::sendGeneratorRequest()
663
{
664 665
    // find a request
    PixmapRequest * request = 0;
666
    m_pixmapRequestsMutex.lock();
667 668 669 670 671
    while ( !m_pixmapRequestsStack.isEmpty() && !request )
    {
        PixmapRequest * r = m_pixmapRequestsStack.last();
        if (!r)
            m_pixmapRequestsStack.pop_back();
672

673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
        // request only if page isn't already present or request has invalid id
        else if ( r->page()->hasPixmap( r->id(), r->width(), r->height() ) || r->id() <= 0 || r->id() >= MAX_OBSERVER_ID)
        {
            m_pixmapRequestsStack.pop_back();
            delete r;
        }
        else if ( (long)r->width() * (long)r->height() > 20000000L )
        {
            m_pixmapRequestsStack.pop_back();
            if ( !m_warnedOutOfMemory )
            {
                kWarning() << "Running out of memory on page " << r->pageNumber()
                    << " (" << r->width() << "x" << r->height() << " px);" << endl;
                kWarning() << "this message will be reported only once." << endl;
                m_warnedOutOfMemory = true;
            }
            delete r;
        }
        else
            request = r;
    }
694

695 696
    // if no request found (or already generated), return
    if ( !request )
697 698
    {
        m_pixmapRequestsMutex.unlock();
699
        return;
700
    }
701

702
    // [MEM] preventive memory freeing
703
    qulonglong pixmapBytes = 4 * request->width() * request->height();
704 705
    if ( pixmapBytes > (1024 * 1024) )
        cleanupPixmapMemory( pixmapBytes );
706

707
    // submit the request to the generator
708
    if ( m_generator->canGeneratePixmap() )
709
    {
710
        kDebug(OkularDebug) << "sending request id=" << request->id() << " " <<request->width() << "x" << request->height() << "@" << request->pageNumber() << " async == " << request->asynchronous() << endl;
711
        m_pixmapRequestsStack.removeAll ( request );
Tobias Koenig's avatar
Tobias Koenig committed
712

713
        if ( (int)m_rotation % 2 )
714
            request->swap();
Tobias Koenig's avatar
Tobias Koenig committed
715

716 717 718 719
        // we always have to unlock _before_ the generatePixmap() because
        // a sync generation would end with requestDone() -> deadlock, and
        // we can not really know if the generator can do async requests
        m_pixmapRequestsMutex.unlock();
720
        m_generator->generatePixmap( request );
721 722
    }
    else
723 724
    {
        m_pixmapRequestsMutex.unlock();
725 726
        // pino (7/4/2006): set the polling interval from 10 to 30
        QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorRequest()) );
727
    }
728 729
}

730
void DocumentPrivate::rotationFinished( int page )
731
{
732 733 734
    QMap< int, DocumentObserver * >::const_iterator it = m_observers.begin(), end = m_observers.end();
    for ( ; it != end ; ++ it ) {
        (*it)->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations );
Tobias Koenig's avatar
Tobias Koenig committed
735
    }
736 737
}

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
void DocumentPrivate::fontReadingProgress( int page )
{
    emit m_parent->fontReadingProgress( page );

    if ( page == (int)m_parent->pages() )
    {
        emit m_parent->fontReadingEnded();
        m_fontThread = 0;
        m_fontsCached = true;
    }
}

void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font )
{
    // TODO try to avoid duplicate fonts
    m_fontsCache.append( font );

    emit m_parent->gotFont( font );
}

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
void DocumentPrivate::slotGeneratorConfigChanged( const QString& )
{
    if ( !m_generator )
        return;

    // reparse generator config and if something changed clear Pages
    bool configchanged = false;
    QHash< QString, GeneratorInfo >::const_iterator it = m_loadedGenerators.constBegin(), itEnd = m_loadedGenerators.constEnd();
    for ( ; it != itEnd; ++it )
    {
        Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( it.value().generator );
        if ( iface )
        {
            bool it_changed = iface->reparseConfig();
            if ( it_changed && ( m_generator == it.value().generator ) )
                configchanged = true;
        }
    }
    if ( configchanged )
    {
        // invalidate pixmaps
        QVector<Page*>::const_iterator it = m_pagesVector.begin(), end = m_pagesVector.end();
        for ( ; it != end; ++it ) {
            (*it)->deletePixmaps();
        }

        // [MEM] remove allocation descriptors
        QLinkedList< AllocatedPixmap * >::const_iterator aIt = m_allocatedPixmapsFifo.begin();
        QLinkedList< AllocatedPixmap * >::const_iterator aEnd = m_allocatedPixmapsFifo.end();
        for ( ; aIt != aEnd; ++aIt )
            delete *aIt;
        m_allocatedPixmapsFifo.clear();
        m_allocatedPixmapsTotalMemory = 0;

        // send reload signals to observers
        foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) );
    }

    // free memory if in 'low' profile
    if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low &&
         !m_allocatedPixmapsFifo.isEmpty() && !m_pagesVector.isEmpty() )
        cleanupPixmapMemory();
}

802

803
Document::Document( QWidget *widget )
804
    : QObject( widget ), d( new DocumentPrivate( this ) )
805
{
806
    d->m_bookmarkManager = new BookmarkManager( this );
807 808 809

    connect( PageController::self(), SIGNAL( rotationFinished( int ) ),
             this, SLOT( rotationFinished( int ) ) );
810 811

    qRegisterMetaType<Okular::FontInfo>();
812 813
}

814
Document::~Document()
815
{
816 817 818
    // stop any audio playback
    AudioPlayer::instance()->stopPlaybacks();

819 820
    // delete generator, pages, and related stuff
    closeDocument();
821

822 823 824
    // delete the bookmark manager
    delete d->m_bookmarkManager;

825
    // delete the loaded generators
826
    QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd();
827
    for ( ; it != itEnd; ++it )
828
        d->unloadGenerator( it.value() );
829 830
    d->m_loadedGenerators.clear();

831 832
    // delete the private structure
    delete d;
833 834
}

835
static bool kserviceMoreThan( const KService::Ptr &s1, const KService::Ptr &s2 )
836
{
837
    return s1->property( "X-KDE-Priority" ).toInt() > s2->property( "X-KDE-Priority" ).toInt();
838
}
839

840
bool Document::openDocument( const QString & docFile, const KUrl& url, const KMimeType::Ptr &_mime )
Albert Astals Cid's avatar
Albert Astals Cid committed
841
{
842 843
    KMimeType::Ptr mime = _mime;
    QByteArray filedata;
844
    int document_size = -1;
845 846
    bool isstdin = url.fileName( KUrl::ObeyTrailingSlash ) == QLatin1String( "-" );
    if ( !isstdin )
847
    {
848 849 850 851 852 853 854 855 856 857 858 859 860 861
        if ( mime.count() <= 0 )
            return false;

        // docFile is always local so we can use QFile on it
        QFile fileReadTest( docFile );
        if ( !fileReadTest.open( QIODevice::ReadOnly ) )
        {
            d->m_docFileName.clear();
            return false;
        }
        // determine the related "xml document-info" filename
        d->m_url = url;
        d->m_docFileName = docFile;
        QString fn = docFile.contains('/') ? docFile.section('/', -1, -1) : docFile;
862 863
        document_size = fileReadTest.size();
        fn = QString::number( document_size ) + '.' + fn + ".xml";