document.cpp 61.1 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.                                   *
 ***************************************************************************/

11 12
// qt/kde/system includes
#include <qdir.h>
13
#include <qfile.h>
14
#include <qfileinfo.h>
15
#include <qimage.h>
16
#include <qtextstream.h>
17
#include <qvector.h>
18
#include <qtimer.h>
19
#include <qmap.h>
20
#include <kconfigdialog.h>
Enrico Ros's avatar
Enrico Ros committed
21
#include <kdebug.h>
22
#include <kimageio.h>
23 24 25
#include <klocale.h>
#include <kfinddialog.h>
#include <kmessagebox.h>
Albert Astals Cid's avatar
Albert Astals Cid committed
26
#include <ktoolinvocation.h>
27
#include <kuserprofile.h>
28
#include <krun.h>
29
#include <kstandarddirs.h>
30 31
#include <klibloader.h>
#include <ktrader.h>
32 33
#include <qcombobox.h>
#include <qlabel.h>
34 35 36

// local includes
#include "document.h"
37
#include "generator.h"
38
#include "observer.h"
39
#include "page.h"
40
#include "link.h"
41
#include "chooseenginedialog.h"
42

43
#include "settings.h"
44

45
// structures used internally by KPDFDocument for local variables storage
46 47
class AllocatedPixmap;
class RunningSearch;
48
class KPDFDocumentPrivate
49
{
50
    public:
51 52
        // find descriptors, mapped by ID (we handle multiple searches)
        QMap< int, RunningSearch * > searches;
53

54 55
        // needed because for remote documents docFileName is a local file and
        // we want the remote url when the document refers to relativeNames
56
        KUrl url;
57

58
        // cached stuff
59 60
        QString docFileName;
        QString xmlFileName;
61

62 63 64
        // list of the mimetypes 'generator_kimgio' can understand
        QStringList kimgioMimes;

65
        // viewport stuff
66 67
        QLinkedList< DocumentViewport > viewportHistory;
        QLinkedList< DocumentViewport >::iterator viewportIterator;
68
        DocumentViewport nextDocumentViewport; // see KPDFLink::Goto for an explanation
69

70
        // observers / requests / allocator stuff
71
        QMap< int, DocumentObserver * > observers;
72 73
        QLinkedList< PixmapRequest * > pixmapRequestsStack;
        QLinkedList< AllocatedPixmap * > allocatedPixmapsFifo;
74
        int allocatedPixmapsTotalMemory;
Enrico Ros's avatar
Enrico Ros committed
75
        bool warnedOutOfMemory;
76

77 78
        // timers (memory checking / info saver)
        QTimer * memCheckTimer;
79
        QTimer * saveBookmarksTimer;
80 81
};

82
struct AllocatedPixmap
83
{
84 85 86 87
    // owner of the page
    int id;
    int page;
    int memory;
88
    // public constructor: initialize data
89
    AllocatedPixmap( int i, int p, int m ) : id( i ), page( p ), memory( m ) {};
90 91
};

92 93
struct RunningSearch
{
94 95
    // store search properties
    int continueOnPage;
96
    RegularAreaRect continueOnMatch;
97
    QLinkedList< int > highlightedPages;
98 99 100 101 102 103 104 105

    // fields related to previous searches (used for 'continueSearch')
    QString cachedString;
    KPDFDocument::SearchType cachedType;
    bool cachedCaseSensitive;
    bool cachedViewportMove;
    bool cachedNoDialogs;
    QColor cachedColor;
106 107
};

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

112 113 114

/** KPDFDocument **/

115
KPDFDocument::KPDFDocument( QHash<QString, Generator*> * genList )
116
    : m_loadedGenerators ( genList ), generator( 0 ),  d( new KPDFDocumentPrivate )
117
{
118
    d->allocatedPixmapsTotalMemory = 0;
119 120
    d->memCheckTimer = 0;
    d->saveBookmarksTimer = 0;
Enrico Ros's avatar
Enrico Ros committed
121
    d->warnedOutOfMemory = false;
122
}
Enrico Ros's avatar
Enrico Ros committed
123

124 125
KPDFDocument::~KPDFDocument()
{
126
    // delete generator, pages, and related stuff
127
    closeDocument();
Albert Astals Cid's avatar
Albert Astals Cid committed
128

129
    // delete the private structure
130 131 132
    delete d;
}

133

134
bool KPDFDocument::openDocument( const QString & docFile, const KUrl& url, const KMimeType::Ptr &mime )
135 136 137 138
{
    // docFile is always local so we can use QFile on it
    QFile fileReadTest( docFile );
    if ( !fileReadTest.open( IO_ReadOnly ) )
139
    {
140
        d->docFileName = QString::null;
141
        return false;
142
    }
143
    // determine the related "xml document-info" filename
144
    d->url = url;
145 146 147
    d->docFileName = docFile;
    QString fn = docFile.contains('/') ? docFile.section('/', -1, -1) : docFile;
    fn = "kpdf/" + QString::number(fileReadTest.size()) + "." + fn + ".xml";
148
    fileReadTest.close();
149
    d->xmlFileName = locateLocal( "data", fn );
150

151 152 153 154 155 156
    if (mime.count()<=0)
	return false;
    
    // 0. load Generator
    // request only valid non-disabled plugins suitable for the mimetype
    QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
157
    KTrader::OfferList offers=KTrader::self()->query(mime->name(),"oKular/Generator",constraint, "[X-KDE-Priority]");
158 159
    
    if (offers.isEmpty())
160
    {
161
	kWarning() << "No plugin for '" << mime->name() << "' mimetype." << endl;
162
	   return false;
163 164
    }
    int hRank=0;
165 166

    // best ranked offer search
Piotr Szymanski's avatar
Piotr Szymanski committed
167
    if (offers.count() > 1 && KpdfSettings::chooseGenerators() )
168
    {
169 170 171 172 173
        ChooseEngineDialog * choose = new ChooseEngineDialog (0,0);
        int count=offers.count();
        int i;
        for (i=0;i<count;i++)
        {
Albert Astals Cid's avatar
Albert Astals Cid committed
174
            choose->engineList->insertItem( i, offers[i]->property("Name").toString() );
175 176 177 178 179 180 181 182 183
        }

        choose -> description-> setText(
        QString("More then one generator found for %1 mimetype, please select which one to use:").arg(mime->name())
        );

        switch( choose->exec() )
        {
            case QDialog::Accepted:
Albert Astals Cid's avatar
Albert Astals Cid committed
184
                hRank=choose->engineList->currentIndex();
185 186 187 188 189
                break;
            case QDialog::Rejected:
                return false;
                break;
        }
190
    }
191 192

    QString propName=offers[hRank]->property("Name").toString();
193
    m_usingCachedGenerator=false;
194 195
    generator=m_loadedGenerators->take(propName);
    if (!generator)
196
    {
197 198 199
        KLibLoader *loader = KLibLoader::self();
        if (!loader)
        {
200
            kWarning() << "Could not start library loader: '" << loader->lastErrorMessage() << "'." << endl;
201 202 203 204 205
            return false;
        }
        KLibrary *lib = loader->globalLibrary( QFile::encodeName( offers[hRank]->library() ) );
        if (!lib) 
        {
206
            kWarning() << "Could not load '" << lib->fileName() << "' library." << endl;
207 208 209 210 211 212 213 214
            return false;
        }

        Generator* (*create_plugin)(KPDFDocument* doc) = ( Generator* (*)(KPDFDocument* doc) ) lib->symbol( "create_plugin" );
        generator=create_plugin(this);

        if ( !generator )
        {
215
            kWarning() << "Sth broke." << endl;
216 217 218 219 220
            return false;
        }
        if ( offers[hRank]->property("[X-KDE-oKularHasInternalSettings]").toBool() )
        {
            m_loadedGenerators->insert(propName,generator);
221
            m_usingCachedGenerator=true;
222 223
        }
        // end 
224
    }
225
    else
226
    {
227
        generator -> setDocument( this );
228
        m_usingCachedGenerator=true;
229
    }
230 231 232 233 234
    // connect error reporting signals
    connect (generator,SIGNAL(error(QString&,int )),this,SIGNAL(error(QString&,int )));
    connect (generator,SIGNAL(warning(QString&,int )),this,SIGNAL(warning(QString&,int )));
    connect (generator,SIGNAL(notice(QString&,int )),this,SIGNAL(notice(QString&,int )));

235
    // 1. load Document (and set busy cursor while loading)
236
    QApplication::setOverrideCursor( Qt::waitCursor );
237
    bool openOk = generator->loadDocument( docFile, pages_vector );
238
    QApplication::restoreOverrideCursor();
239
    if ( !openOk || pages_vector.size() <= 0 )
240
    {
241
        if (!m_usingCachedGenerator)
242 243 244
        {
            delete generator;
        }
245
        generator = 0;
246
        return openOk;
247
    }
248

249
    // 2. load Additional Data (our bookmarks and metadata) about the document
250 251
    loadDocumentInfo();

252
    // 3. setup observers inernal lists and data
253
    foreachObserver( notifySetup( pages_vector, true ) );
254

255 256 257
    // 4. set initial page (restoring the page saved in xml if loaded)
    DocumentViewport loadedViewport = (*d->viewportIterator);
    if ( loadedViewport.pageNumber != -1 )
258
    {
259
        (*d->viewportIterator) = DocumentViewport();
260 261 262
        if ( loadedViewport.pageNumber >= (int)pages_vector.size() )
            loadedViewport.pageNumber = pages_vector.size() - 1;
    }
263
    else
264
        loadedViewport.pageNumber = 0;
265
    setViewport( loadedViewport );
266

267
    // start bookmark saver timer
268 269 270 271 272
    if ( !d->saveBookmarksTimer )
    {
        d->saveBookmarksTimer = new QTimer( this );
        connect( d->saveBookmarksTimer, SIGNAL( timeout() ), this, SLOT( saveDocumentInfo() ) );
    }
273 274 275
    d->saveBookmarksTimer->start( 5 * 60 * 1000 );

    // start memory check timer
276 277 278 279 280
    if ( !d->memCheckTimer )
    {
        d->memCheckTimer = new QTimer( this );
        connect( d->memCheckTimer, SIGNAL( timeout() ), this, SLOT( slotTimedMemoryCheck() ) );
    }
281 282
    d->memCheckTimer->start( 2000 );

283 284 285 286 287 288
    if (d->nextDocumentViewport.pageNumber != -1)
    {
        setViewport(d->nextDocumentViewport);
        d->nextDocumentViewport = DocumentViewport();
    }

289 290 291
    return true;
}

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

QString KPDFDocument::getXMLFile()
{
    if (generator)
        return generator->getXMLFile();
   
    return QString::null;
}

void KPDFDocument::setupGUI(KActionCollection* ac, QToolBox* tBox )
{
    if (generator)
        generator->setupGUI(ac,tBox);
}

307
void KPDFDocument::closeDocument()
308
{
309 310 311
    // save document info if a document is still opened
    if ( generator && pages_vector.size() > 0 )
        saveDocumentInfo();
312

313 314 315 316 317
    // stop timers
    if ( d->memCheckTimer )
        d->memCheckTimer->stop();
    if ( d->saveBookmarksTimer )
        d->saveBookmarksTimer->stop();
318

319 320
    if (generator)
        generator->freeGUI();
321 322 323 324 325
    if (!m_usingCachedGenerator)
    {
        // delete contents generator
        delete generator;
    }
326
    generator = 0;
327
    d->url = KUrl();
Enrico Ros's avatar
Enrico Ros committed
328
    // remove requests left in queue
329 330
    QLinkedList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin();
    QLinkedList< PixmapRequest * >::iterator sEnd = d->pixmapRequestsStack.end();
Enrico Ros's avatar
Enrico Ros committed
331 332 333 334
    for ( ; sIt != sEnd; ++sIt )
        delete *sIt;
    d->pixmapRequestsStack.clear();

335
    // send an empty list to observers (to free their data)
336
    foreachObserver( notifySetup( QVector< KPDFPage * >(), true ) );
337

338
    // delete pages and clear 'pages_vector' container
339 340
    QVector< KPDFPage * >::iterator pIt = pages_vector.begin();
    QVector< KPDFPage * >::iterator pEnd = pages_vector.end();
341 342
    for ( ; pIt != pEnd; ++pIt )
        delete *pIt;
343 344
    pages_vector.clear();

345
    // clear 'memory allocation' descriptors
346 347
    QLinkedList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
    QLinkedList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
348 349 350
    for ( ; aIt != aEnd; ++aIt )
        delete *aIt;
    d->allocatedPixmapsFifo.clear();
351

352 353 354 355 356 357 358
    // clear 'running searches' descriptors
    QMap< int, RunningSearch * >::iterator rIt = d->searches.begin();
    QMap< int, RunningSearch * >::iterator rEnd = d->searches.end();
    for ( ; rIt != rEnd; ++rIt )
        delete *rIt;
    d->searches.clear();

359
    // reset internal variables
360

361 362 363
    d->viewportHistory.clear();
    d->viewportHistory.append( DocumentViewport() );
    d->viewportIterator = d->viewportHistory.begin();
364
    d->allocatedPixmapsTotalMemory = 0;
365 366
}

367
void KPDFDocument::addObserver( DocumentObserver * pObserver )
368
{
369
    // keep the pointer to the observer in a map
370
    d->observers[ pObserver->observerId() ] = pObserver;
371 372 373

    // if the observer is added while a document is already opened, tell it
    if ( !pages_vector.isEmpty() )
374
    {
375
        pObserver->notifySetup( pages_vector, true );
376 377
        pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
    }
378 379
}

Piotr Szymanski's avatar
Piotr Szymanski committed
380
void KPDFDocument::notifyObservers (NotifyRequest * request)
381
{
Piotr Szymanski's avatar
Piotr Szymanski committed
382
    switch (request->type)
383
    {
Piotr Szymanski's avatar
Piotr Szymanski committed
384 385 386 387 388 389 390 391 392 393 394 395
        case DocumentObserver::Setup:
            foreachObserver( notifySetup( pages_vector, request->toggle ) );
            break;
        case DocumentObserver::Viewport:
            foreachObserver( notifyViewportChanged( request->toggle ) );
            break;
        case DocumentObserver::Page:
            foreachObserver( notifyPageChanged( request->page, request->flags ) );
            break;
        case DocumentObserver::Contents:
            foreachObserver( notifyContentsCleared( request->flags ) );
            break;
396 397 398
    }
}

399
void KPDFDocument::removeObserver( DocumentObserver * pObserver )
400 401
{
    // remove observer from the map. it won't receive notifications anymore
402 403
    if ( d->observers.contains( pObserver->observerId() ) )
    {
404
        // free observer's pixmap data
405
        int observerId = pObserver->observerId();
406
        QVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
407 408
        for ( ; it != end; ++it )
            (*it)->deletePixmap( observerId );
409

410
        // [MEM] free observer's allocation descriptors
411 412
        QLinkedList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
        QLinkedList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
413 414 415 416 417
        while ( aIt != aEnd )
        {
            AllocatedPixmap * p = *aIt;
            if ( p->id == observerId )
            {
Pino Toscano's avatar
Pino Toscano committed
418
                aIt = d->allocatedPixmapsFifo.erase( aIt );
419 420 421 422 423 424
                delete p;
            }
            else
                ++aIt;
        }

425
        // delete observer entry from the map
426 427
        d->observers.remove( observerId );
    }
428 429
}

430
void KPDFDocument::reparseConfig()
431
{
432 433
    // reparse generator config and if something changed clear KPDFPages
    if ( generator && generator->reparseConfig() )
434
    {
435
        // invalidate pixmaps
436
        QVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
437
        for ( ; it != end; ++it )
438
            (*it)->deletePixmapsAndRects();
439 440

        // [MEM] remove allocation descriptors
441 442
        QLinkedList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
        QLinkedList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
443 444 445 446 447 448
        for ( ; aIt != aEnd; ++aIt )
            delete *aIt;
        d->allocatedPixmapsFifo.clear();
        d->allocatedPixmapsTotalMemory = 0;

        // send reload signals to observers
449
        foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) );
450
    }
451 452

    // free memory if in 'low' profile
Piotr Szymanski's avatar
Piotr Szymanski committed
453
    if ( KpdfSettings::memoryLevel() == KpdfSettings::EnumMemoryLevel::Low &&
454 455
         !d->allocatedPixmapsFifo.isEmpty() && !pages_vector.isEmpty() )
        cleanupPixmapMemory();
456 457
}

458

459 460 461 462 463
bool KPDFDocument::isOpened() const
{
    return generator;
}

464 465 466 467 468 469 470 471 472 473
bool KPDFDocument::handleEvent( QEvent * event )
{
    return generator ? generator->handleEvent( event ) : true;
}

bool KPDFDocument::canConfigurePrinter( ) const
{
    return generator ? generator->canConfigurePrinter() : false;
}

474
const DocumentInfo * KPDFDocument::documentInfo() const
Albert Astals Cid's avatar
Albert Astals Cid committed
475
{
Enrico Ros's avatar
Enrico Ros committed
476
    return generator ? generator->generateDocumentInfo() : NULL;
477 478 479 480
}

const DocumentSynopsis * KPDFDocument::documentSynopsis() const
{
Enrico Ros's avatar
Enrico Ros committed
481
    return generator ? generator->generateDocumentSynopsis() : NULL;
Albert Astals Cid's avatar
Albert Astals Cid committed
482 483
}

484 485 486 487 488
const DocumentFonts * KPDFDocument::documentFonts() const
{
    return generator ? generator->generateDocumentFonts() : NULL;
}

489
const KPDFPage * KPDFDocument::page( int n ) const
Albert Astals Cid's avatar
Albert Astals Cid committed
490
{
491
    return ( n < pages_vector.count() ) ? pages_vector[n] : 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
492 493
}

494 495
const DocumentViewport & KPDFDocument::viewport() const
{
496
    return (*d->viewportIterator);
497 498
}

499
uint KPDFDocument::currentPage() const
Albert Astals Cid's avatar
Albert Astals Cid committed
500
{
501
    return (*d->viewportIterator).pageNumber;
Albert Astals Cid's avatar
Albert Astals Cid committed
502 503
}

504
uint KPDFDocument::pages() const
Albert Astals Cid's avatar
Albert Astals Cid committed
505
{
506
    return pages_vector.size();
Albert Astals Cid's avatar
Albert Astals Cid committed
507 508
}

509
KUrl KPDFDocument::currentDocument() const
Enrico Ros's avatar
Enrico Ros committed
510 511 512 513
{
    return d->url;
}

514
bool KPDFDocument::isAllowed( int flags ) const
515
{
516
    return generator ? generator->isAllowed( flags ) : false;
517 518
}

519 520
bool KPDFDocument::supportsSearching() const
{
521
    return generator ? generator->supportsSearching() : false;
522 523
}

524 525 526 527 528
bool KPDFDocument::supportsRotation() const
{
    return generator ? generator->supportsRotation() : false;
}

529 530 531 532 533 534 535 536 537 538
bool KPDFDocument::supportsPaperSizes() const
{
    return generator ? generator->supportsPaperSizes() : false;
}

QStringList KPDFDocument::paperSizes() const
{
    return generator ? generator->paperSizes() : QStringList();
}

539 540 541 542 543 544 545 546 547 548
bool KPDFDocument::historyAtBegin() const
{
    return d->viewportIterator == d->viewportHistory.begin();
}

bool KPDFDocument::historyAtEnd() const
{
    return d->viewportIterator == --(d->viewportHistory.end());
}

549
QString KPDFDocument::getMetaData( const QString & key, const QString & option ) const
550
{
551
    return generator ? generator->getMetaData( key, option ) : QString();
552
}
553

554
void KPDFDocument::requestPixmaps( const QLinkedList< PixmapRequest * > & requests )
555
{
556 557 558
    if ( !generator )
    {
        // delete requests..
559
        QLinkedList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
560 561 562
        for ( ; rIt != rEnd; ++rIt )
            delete *rIt;
        // ..and return
563
        return;
564
    }
565

Enrico Ros's avatar
Enrico Ros committed
566 567
    // 1. [CLEAN STACK] remove previous requests of requesterID
    int requesterID = requests.first()->id;
568
    QLinkedList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin(), sEnd = d->pixmapRequestsStack.end();
Enrico Ros's avatar
Enrico Ros committed
569 570 571
    while ( sIt != sEnd )
    {
        if ( (*sIt)->id == requesterID )
572 573 574
        {
            // delete request and remove it from stack
            delete *sIt;
Pino Toscano's avatar
Pino Toscano committed
575
            sIt = d->pixmapRequestsStack.erase( sIt );
576
        }
Enrico Ros's avatar
Enrico Ros committed
577 578 579 580 581
        else
            ++sIt;
    }

    // 2. [ADD TO STACK] add requests to stack
Piotr Szymanski's avatar
Piotr Szymanski committed
582
    bool threadingDisabled = !KpdfSettings::enableThreading();
583
    QLinkedList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
584
    for ( ; rIt != rEnd; ++rIt )
585
    {
Enrico Ros's avatar
Enrico Ros committed
586
        // set the 'page field' (see PixmapRequest) and check if it is valid
587
        PixmapRequest * request = *rIt;
588
        kWarning() << "request id=" << request->id << " " <<request->width << "x" << request->height << "@" << request->pageNumber << endl;
Enrico Ros's avatar
Enrico Ros committed
589
        if ( !(request->page = pages_vector[ request->pageNumber ]) )
590 591 592
        {
            // skip requests referencing an invalid page (must not happen)
            delete request;
593
            continue;
594
        }
595

Enrico Ros's avatar
Enrico Ros committed
596 597
        if ( !request->async )
            request->priority = 0;
598

Enrico Ros's avatar
Enrico Ros committed
599 600
        if ( request->async && threadingDisabled )
            request->async = false;
601

Enrico Ros's avatar
Enrico Ros committed
602 603 604
        // add request to the 'stack' at the right place
        if ( !request->priority )
            // add priority zero requests to the top of the stack
605
            d->pixmapRequestsStack.append( request );
Enrico Ros's avatar
Enrico Ros committed
606 607 608 609 610 611 612 613 614
        else
        {
            // insert in stack sorted by priority
            sIt = d->pixmapRequestsStack.begin();
            sEnd = d->pixmapRequestsStack.end();
            while ( sIt != sEnd && (*sIt)->priority >= request->priority )
                ++sIt;
            d->pixmapRequestsStack.insert( sIt, request );
        }
615
    }
Enrico Ros's avatar
Enrico Ros committed
616

617
    // 3. [START FIRST GENERATION] if <NO>generator is ready, start a new generation,
Enrico Ros's avatar
Enrico Ros committed
618
    // or else (if gen is running) it will be started when the new contents will
619 620 621
    //come from generator (in requestDone())</NO>
    // all handling of requests put into sendGeneratorRequest
    //    if ( generator->canGeneratePixmap() )
Enrico Ros's avatar
Enrico Ros committed
622
        sendGeneratorRequest();
623 624
}

625
void KPDFDocument::requestTextPage( uint page )
626
{
627 628 629 630
    KPDFPage * kp = pages_vector[ page ];
    if ( !generator || !kp )
        return;

631 632
    // Memory management for TextPages

Enrico Ros's avatar
Enrico Ros committed
633
    generator->generateSyncTextPage( kp );
634
}
635 636 637 638 639 640 641 642 643 644 645 646 647 648

void KPDFDocument::addPageAnnotation( int page, Annotation * annotation )
{
    // find out the page to attach annotation
    KPDFPage * kp = pages_vector[ page ];
    if ( !generator || !kp )
        return;

    // add annotation to the page
    kp->addAnnotation( annotation );

    // notify observers about the change
    foreachObserver( notifyPageChanged( page, DocumentObserver::Annotations ) );
}
649 650 651 652
/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
void KPDFDocument::setNextPage()
{
    // advance page and set viewport on observers
653 654
    if ( (*d->viewportIterator).pageNumber < (int)pages_vector.count() - 1 )
        setViewport( DocumentViewport( (*d->viewportIterator).pageNumber + 1 ) );
655
}
656

657
void KPDFDocument::setPrevPage()
658
{
659
    // go to previous page and set viewport on observers
660 661
    if ( (*d->viewportIterator).pageNumber > 0 )
        setViewport( DocumentViewport( (*d->viewportIterator).pageNumber - 1 ) );
662 663
}
*/
Enrico Ros's avatar
Enrico Ros committed
664
void KPDFDocument::setViewportPage( int page, int excludeId, bool smoothMove )
665 666
{
    // clamp page in range [0 ... numPages-1]
Enrico Ros's avatar
Enrico Ros committed
667
    if ( page < 0 )
668
        page = 0;
669 670
    else if ( page > (int)pages_vector.count() )
        page = pages_vector.count() - 1;
671 672

    // make a viewport from the page and broadcast it
Enrico Ros's avatar
Enrico Ros committed
673
    setViewport( DocumentViewport( page ), excludeId, smoothMove );
674 675
}

Enrico Ros's avatar
Enrico Ros committed
676
void KPDFDocument::setViewport( const DocumentViewport & viewport, int excludeId, bool smoothMove )
677 678
{
    // if already broadcasted, don't redo it
679
    DocumentViewport & oldViewport = *d->viewportIterator;
680 681
    // disabled by enrico on 2005-03-18 (less debug output)
    //if ( viewport == oldViewport )
682
    //    kDebug() << "setViewport with the same viewport." << endl;
683

684 685 686 687 688 689 690 691 692 693 694 695 696 697
    // set internal viewport taking care of history
    if ( oldViewport.pageNumber == viewport.pageNumber || oldViewport.pageNumber == -1 )
    {
        // if page is unchanged save the viewport at current position in queue
        oldViewport = viewport;
    }
    else
    {
        // remove elements after viewportIterator in queue
        d->viewportHistory.erase( ++d->viewportIterator, d->viewportHistory.end() );

        // keep the list to a reasonable size by removing head when needed
        if ( d->viewportHistory.count() >= 100 )
            d->viewportHistory.pop_front();
698

699
        // add the item at the end of the queue
Albert Astals Cid's avatar
Albert Astals Cid committed
700
        d->viewportIterator = d->viewportHistory.insert( d->viewportHistory.end(), viewport );
701 702 703
    }

    // notify change to all other (different from id) observers
704
    QMap< int, DocumentObserver * >::iterator it = d->observers.begin(), end = d->observers.end();
705
    for ( ; it != end ; ++ it )
706
        if ( it.key() != excludeId )
Enrico Ros's avatar
Enrico Ros committed
707
            (*it)->notifyViewportChanged( smoothMove );
708

709
    // [MEM] raise position of currently viewed page in allocation queue
710 711
    if ( d->allocatedPixmapsFifo.count() > 1 )
    {
712
        const int page = viewport.pageNumber;
713 714 715
        QLinkedList< AllocatedPixmap * > viewportPixmaps;
        QLinkedList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
        QLinkedList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
716
        while ( aIt != aEnd )
717
        {
718
            if ( (*aIt)->page == page )
719
            {
720
                viewportPixmaps.append( *aIt );
Pino Toscano's avatar
Pino Toscano committed
721
                aIt = d->allocatedPixmapsFifo.erase( aIt );
722
                continue;
723
            }
724
            ++aIt;
725
        }
726 727 728
        if ( !viewportPixmaps.isEmpty() )
            d->allocatedPixmapsFifo += viewportPixmaps;
    }
729 730
}

731 732 733 734 735 736 737
void KPDFDocument::setPrevViewport()
// restore viewport from the history
{
    if ( d->viewportIterator != d->viewportHistory.begin() )
    {
        // restore previous viewport and notify it to observers
        --d->viewportIterator;
Enrico Ros's avatar
Enrico Ros committed
738
        foreachObserver( notifyViewportChanged( true ) );
739 740 741 742 743 744
    }
}

void KPDFDocument::setNextViewport()
// restore next viewport from the history
{
745
    QLinkedList< DocumentViewport >::iterator nextIterator = d->viewportIterator;
746 747 748 749 750
    ++nextIterator;
    if ( nextIterator != d->viewportHistory.end() )
    {
        // restore next viewport and notify it to observers
        ++d->viewportIterator;
Enrico Ros's avatar
Enrico Ros committed
751
        foreachObserver( notifyViewportChanged( true ) );
752 753 754
    }
}

755

756 757
bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
                               SearchType type, bool moveViewport, const QColor & color, bool noDialogs )
758
{
759
    // safety checks: don't perform searches on empty or unsearchable docs
760
    if ( !generator || !generator->supportsSearching() || pages_vector.isEmpty() )
761 762
        return false;

763
    // if searchID search not recorded, create new descriptor and init params
764 765 766
    if ( !d->searches.contains( searchID ) )
    {
        RunningSearch * search = new RunningSearch();
767
        search->continueOnPage = -1;
768 769 770 771
        d->searches[ searchID ] = search;
    }
    RunningSearch * s = d->searches[ searchID ];

772 773 774 775 776 777 778 779
    // update search stucture
    bool newText = text != s->cachedString;
    s->cachedString = text;
    s->cachedType = type;
    s->cachedCaseSensitive = caseSensitive;
    s->cachedViewportMove = moveViewport;
    s->cachedNoDialogs = noDialogs;
    s->cachedColor = color;
780

781 782
    // global data for search
    bool foundAMatch = false;
783
    QLinkedList< int > pagesToNotify;
784 785 786

    // remove highlights from pages and queue them for notifying changes
    pagesToNotify += s->highlightedPages;
787
    QLinkedList< int >::iterator it = s->hig