document.cpp 61.3 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
// qt/kde/system includes
12
#include <qapplication.h>
13
#include <qdir.h>
14
#include <qfile.h>
15
#include <qfileinfo.h>
16
#include <qimage.h>
17
#include <qtextstream.h>
18
#include <qvector.h>
19
#include <qtimer.h>
20
#include <qmap.h>
21
#include <kconfigdialog.h>
Enrico Ros's avatar
Enrico Ros committed
22
#include <kdebug.h>
23 24 25
#include <klocale.h>
#include <kfinddialog.h>
#include <kmessagebox.h>
26
#include <kmimetypetrader.h>
Albert Astals Cid's avatar
Albert Astals Cid committed
27
#include <ktoolinvocation.h>
28
#include <krun.h>
29
#include <kstandarddirs.h>
30
#include <klibloader.h>
31 32
#include <qcombobox.h>
#include <qlabel.h>
33 34 35

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

42
#include "settings.h"
43

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

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

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

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

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

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

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

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

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

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

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

111 112 113

/** KPDFDocument **/

114
KPDFDocument::KPDFDocument( QHash<QString, Generator*> * genList )
115
    : m_loadedGenerators ( genList ), generator( 0 ),  d( new KPDFDocumentPrivate )
116
{
117
    d->allocatedPixmapsTotalMemory = 0;
118 119
    d->memCheckTimer = 0;
    d->saveBookmarksTimer = 0;
Enrico Ros's avatar
Enrico Ros committed
120
    d->warnedOutOfMemory = false;
Albert Astals Cid's avatar
Albert Astals Cid committed
121
    m_usingCachedGenerator = 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
{
    // docFile is always local so we can use QFile on it
    QFile fileReadTest( docFile );
138
    if ( !fileReadTest.open( QIODevice::ReadOnly ) )
139
    {
140
        d->docFileName.clear();
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
    KService::List offers = KMimeTypeTrader::self()->query(mime->name(),"oKular/Generator",constraint);
158
    if (offers.isEmpty())
159
    {
160
	kWarning() << "No plugin for '" << mime->name() << "' mimetype." << endl;
161
	   return false;
162 163
    }
    int hRank=0;
164 165

    // best ranked offer search
Piotr Szymanski's avatar
Piotr Szymanski committed
166
    if (offers.count() > 1 && KpdfSettings::chooseGenerators() )
167
    {
168
        QStringList list;
169
        int count=offers.count();
170
        for (int i=0;i<count;++i)
171
        {
172
            list << offers[i]->property("Name").toString();
173
        }
174
        ChooseEngineDialog * choose = new ChooseEngineDialog (list, mime->name(), 0);
175

176 177 178 179
        int retval=choose->exec();
        int index=choose->selectedGenerator();
        delete choose;
        switch( retval )
180 181
        {
            case QDialog::Accepted:
182
                hRank=index;
183 184 185 186 187
                break;
            case QDialog::Rejected:
                return false;
                break;
        }
188
    }
189 190

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

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

        if ( !generator )
        {
213
            kWarning() << "Sth broke." << endl;
214 215 216 217 218
            return false;
        }
        if ( offers[hRank]->property("[X-KDE-oKularHasInternalSettings]").toBool() )
        {
            m_loadedGenerators->insert(propName,generator);
219
            m_usingCachedGenerator=true;
220 221
        }
        // end 
222
    }
223
    else
224
    {
225
        generator -> setDocument( this );
226
        m_usingCachedGenerator=true;
227
    }
228 229 230 231 232
    // 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 )));

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

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

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

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

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

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

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

287 288 289
    return true;
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304

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

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

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

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

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

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

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

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

350 351 352 353 354 355 356
    // 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();

357
    // reset internal variables
358

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

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

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

Piotr Szymanski's avatar
Piotr Szymanski committed
378
void KPDFDocument::notifyObservers (NotifyRequest * request)
379
{
Piotr Szymanski's avatar
Piotr Szymanski committed
380
    switch (request->type)
381
    {
Piotr Szymanski's avatar
Piotr Szymanski committed
382 383 384 385 386 387 388 389 390 391 392 393
        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;
394 395 396
    }
}

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

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

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

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

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

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

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

456

457 458 459 460 461
bool KPDFDocument::isOpened() const
{
    return generator;
}

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

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

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

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

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

487 488 489 490 491
const QList<EmbeddedFile*> *KPDFDocument::embeddedFiles() const
{
    return generator ? generator->embeddedFiles() : NULL;
}

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

497 498
const DocumentViewport & KPDFDocument::viewport() const
{
499
    return (*d->viewportIterator);
500 501
}

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

507
uint KPDFDocument::pages() const
Albert Astals Cid's avatar
Albert Astals Cid committed
508
{
509
    return pages_vector.size();
Albert Astals Cid's avatar
Albert Astals Cid committed
510 511
}

512
KUrl KPDFDocument::currentDocument() const
Enrico Ros's avatar
Enrico Ros committed
513 514 515 516
{
    return d->url;
}

517
bool KPDFDocument::isAllowed( int flags ) const
518
{
519
    return generator ? generator->isAllowed( flags ) : false;
520 521
}

522 523
bool KPDFDocument::supportsSearching() const
{
524
    return generator ? generator->supportsSearching() : false;
525 526
}

527 528 529 530 531
bool KPDFDocument::supportsRotation() const
{
    return generator ? generator->supportsRotation() : false;
}

532 533 534 535 536 537 538 539 540 541
bool KPDFDocument::supportsPaperSizes() const
{
    return generator ? generator->supportsPaperSizes() : false;
}

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

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

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

552
QString KPDFDocument::getMetaData( const QString & key, const QString & option ) const
553
{
554
    return generator ? generator->getMetaData( key, option ) : QString();
555
}
556

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

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

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

Enrico Ros's avatar
Enrico Ros committed
599 600
        if ( !request->async )
            request->priority = 0;
601

Enrico Ros's avatar
Enrico Ros committed
602 603
        if ( request->async && threadingDisabled )
            request->async = false;
604

Enrico Ros's avatar
Enrico Ros committed
605 606 607
        // add request to the 'stack' at the right place
        if ( !request->priority )
            // add priority zero requests to the top of the stack
608
            d->pixmapRequestsStack.append( request );
Enrico Ros's avatar
Enrico Ros committed
609 610 611 612 613 614 615 616 617
        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 );
        }
618
    }
Enrico Ros's avatar
Enrico Ros committed
619

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

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

634 635
    // Memory management for TextPages

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

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 ) );
}
652 653 654 655
/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
void KPDFDocument::setNextPage()
{
    // advance page and set viewport on observers
656 657
    if ( (*d->viewportIterator).pageNumber < (int)pages_vector.count() - 1 )
        setViewport( DocumentViewport( (*d->viewportIterator).pageNumber + 1 ) );
658
}
659

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

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

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

687 688 689 690 691 692 693 694 695 696 697 698 699 700
    // 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();
701

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

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

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

734 735 736 737 738 739 740
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
741
        foreachObserver( notifyViewportChanged( true ) );
742 743 744 745 746 747
    }
}

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

758

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

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

775 776 777 778 779 780 781 782
    // 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;
783

784 785
    // global data for search
    bool foundAMatch = false;
786
    QLinkedList< int > pagesToNotify;
787 788 789

    // remove highlights from pages and queue them for notifying changes
    pagesToNotify += s->highlightedPages;
790
    QLinkedList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
791
    for ( ; it != end; ++it )
792 793
        pages_vector[ *it ]->deleteHighlights( searchID );
    s->highlightedPages.clear();
794

795
    // set hourglass cursor
796
    QApplication::setOverrideCursor( Qt::waitCursor );
797

798
    // 1. ALLDOC - proces all document marking pages
799 800
    if ( type == AllDoc )
    {
801
        // search and highlight 'text' (as a solid phrase) on all pages
802
        QVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
803 804
        for ( ; it != end; ++it )
        {
805
            // get page (from the first to the last)
806
            KPDFPage * page = *it;
807 808 809
            int pageNumber = page->number();

            // request search page if needed
810
            if ( !page->hasSearchPage() )
811
                requestTextPage( pageNumber );
812

813 814
            // loop on a page adding highlights for all found items
            bool addedHighlights = false;
815
            RegularAreaRect * lastMatch = 0;
816 817 818
            while ( 1 )
            {
                if ( lastMatch )
819
                    lastMatch = page->findText( searchID, text, NextRes, caseSensitive, lastMatch );
820
                else