document.cpp 54.5 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 <qtextstream.h>
16
#include <qvaluevector.h>
17
#include <qtimer.h>
18
#include <qmap.h>
Enrico Ros's avatar
Enrico Ros committed
19
#include <kdebug.h>
20 21 22
#include <klocale.h>
#include <kfinddialog.h>
#include <kmessagebox.h>
23 24
#include <kapplication.h>
#include <kuserprofile.h>
25 26
#include <kmimetype.h>
#include <krun.h>
27
#include <kstandarddirs.h>
28 29 30

// local includes
#include "document.h"
31
#include "observer.h"
32
#include "page.h"
33
#include "link.h"
34
#include "generator_pdf/generator_pdf.h"  // PDF generator
35
#include "conf/settings.h"
36

37
// structures used internally by KPDFDocument for local variables storage
38 39
class AllocatedPixmap;
class RunningSearch;
40
class KPDFDocumentPrivate
41
{
42
    public:
43 44
        // find descriptors, mapped by ID (we handle multiple searches)
        QMap< int, RunningSearch * > searches;
45

46 47 48 49
        // needed because for remote documents docFileName is a local file and
        // we want the remote url when the document refers to relativeNames
        KURL url;

50
        // cached stuff
51 52
        QString docFileName;
        QString xmlFileName;
53

54 55 56
        // viewport stuff
        QValueList< DocumentViewport > viewportHistory;
        QValueList< DocumentViewport >::iterator viewportIterator;
57
        DocumentViewport nextDocumentViewport; // see KPDFLink::Goto for an explanation
58

59
        // observers / requests / allocator stuff
60
        QMap< int, DocumentObserver * > observers;
Enrico Ros's avatar
Enrico Ros committed
61
        QValueList< PixmapRequest * > pixmapRequestsStack;
62
        QValueList< AllocatedPixmap * > allocatedPixmapsFifo;
63
        int allocatedPixmapsTotalMemory;
Enrico Ros's avatar
Enrico Ros committed
64
        bool warnedOutOfMemory;
65

66 67
        // timers (memory checking / info saver)
        QTimer * memCheckTimer;
68
        QTimer * saveBookmarksTimer;
69 70
};

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

81 82
struct RunningSearch
{
83 84
    // store search properties
    int continueOnPage;
85
    NormalizedRect continueOnMatch;
86 87 88 89 90 91 92 93 94
    QValueList< int > highlightedPages;

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

97
#define foreachObserver( cmd ) {\
98 99
    QMap< int, DocumentObserver * >::iterator it=d->observers.begin(), end=d->observers.end();\
    for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
100

101 102 103

/** KPDFDocument **/

104
KPDFDocument::KPDFDocument()
105
    : generator( 0 ), d( new KPDFDocumentPrivate )
106
{
107
    d->allocatedPixmapsTotalMemory = 0;
108 109
    d->memCheckTimer = 0;
    d->saveBookmarksTimer = 0;
Enrico Ros's avatar
Enrico Ros committed
110
    d->warnedOutOfMemory = false;
111
}
Enrico Ros's avatar
Enrico Ros committed
112

113 114
KPDFDocument::~KPDFDocument()
{
115
    // delete generator, pages, and related stuff
116
    closeDocument();
Albert Astals Cid's avatar
Albert Astals Cid committed
117

118
    // delete the private structure
119 120 121
    delete d;
}

122

123
bool KPDFDocument::openDocument( const QString & docFile, const KURL & url )
124 125 126 127
{
    // docFile is always local so we can use QFile on it
    QFile fileReadTest( docFile );
    if ( !fileReadTest.open( IO_ReadOnly ) )
128
    {
129
        d->docFileName = QString::null;
130
        return false;
131
    }
132
    // determine the related "xml document-info" filename
133
    d->url = url;
134 135 136
    d->docFileName = docFile;
    QString fn = docFile.contains('/') ? docFile.section('/', -1, -1) : docFile;
    fn = "kpdf/" + QString::number(fileReadTest.size()) + "." + fn + ".xml";
137
    fileReadTest.close();
138
    d->xmlFileName = locateLocal( "data", fn );
139

140 141 142
    // create the generator based on the file's mimetype
    KMimeType::Ptr mime = KMimeType::findByPath( docFile );
    QString mimeName = mime->name();
Enrico Ros's avatar
Enrico Ros committed
143 144
    if ( mimeName == "application/pdf" ||
         mimeName == "application/x-pdf" )
Enrico Ros's avatar
Enrico Ros committed
145
        generator = new PDFGenerator( this );
146 147
/*    else if ( mimeName == "application/postscript" )
        kdError() << "PS generator not available" << endl;*/
148 149 150 151 152 153
    else
    {
        kdWarning() << "Unknown mimetype '" << mimeName << "'." << endl;
        return false;
    }

154 155
    // 1. load Document (and set busy cursor while loading)
    QApplication::setOverrideCursor( waitCursor );
156
    bool openOk = generator->loadDocument( docFile, pages_vector );
157
    QApplication::restoreOverrideCursor();
158
    if ( !openOk || pages_vector.size() <= 0 )
159 160 161
    {
        delete generator;
        generator = 0;
162
        return openOk;
163
    }
164

165
    // 2. load Additional Data (our bookmarks and metadata) about the document
166 167
    loadDocumentInfo();

168
    // 3. setup observers inernal lists and data
169
    foreachObserver( notifySetup( pages_vector, true ) );
170

171 172 173 174
    // 4. set initial page (restoring the page saved in xml if loaded)
    DocumentViewport loadedViewport = (*d->viewportIterator);
    if ( loadedViewport.pageNumber != -1 )
        (*d->viewportIterator) = DocumentViewport();
175
    else
176
        loadedViewport.pageNumber = 0;
177
    setViewport( loadedViewport );
178

179
    // start bookmark saver timer
180 181 182 183 184
    if ( !d->saveBookmarksTimer )
    {
        d->saveBookmarksTimer = new QTimer( this );
        connect( d->saveBookmarksTimer, SIGNAL( timeout() ), this, SLOT( saveDocumentInfo() ) );
    }
185 186 187
    d->saveBookmarksTimer->start( 5 * 60 * 1000 );

    // start memory check timer
188 189 190 191 192
    if ( !d->memCheckTimer )
    {
        d->memCheckTimer = new QTimer( this );
        connect( d->memCheckTimer, SIGNAL( timeout() ), this, SLOT( slotTimedMemoryCheck() ) );
    }
193 194
    d->memCheckTimer->start( 2000 );

195 196 197 198 199 200
    if (d->nextDocumentViewport.pageNumber != -1)
    {
        setViewport(d->nextDocumentViewport);
        d->nextDocumentViewport = DocumentViewport();
    }

201 202 203
    return true;
}

204
void KPDFDocument::closeDocument()
205
{
206 207 208
    // save document info if a document is still opened
    if ( generator && pages_vector.size() > 0 )
        saveDocumentInfo();
209

210 211 212 213 214
    // stop timers
    if ( d->memCheckTimer )
        d->memCheckTimer->stop();
    if ( d->saveBookmarksTimer )
        d->saveBookmarksTimer->stop();
215

216 217 218 219
    // delete contents generator
    delete generator;
    generator = 0;

Enrico Ros's avatar
Enrico Ros committed
220 221 222 223 224 225 226
    // remove requests left in queue
    QValueList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin();
    QValueList< PixmapRequest * >::iterator sEnd = d->pixmapRequestsStack.end();
    for ( ; sIt != sEnd; ++sIt )
        delete *sIt;
    d->pixmapRequestsStack.clear();

227
    // send an empty list to observers (to free their data)
228
    foreachObserver( notifySetup( QValueVector< KPDFPage * >(), true ) );
229

230
    // delete pages and clear 'pages_vector' container
231 232 233 234
    QValueVector< KPDFPage * >::iterator pIt = pages_vector.begin();
    QValueVector< KPDFPage * >::iterator pEnd = pages_vector.end();
    for ( ; pIt != pEnd; ++pIt )
        delete *pIt;
235 236
    pages_vector.clear();

237
    // clear 'memory allocation' descriptors
238 239 240 241 242
    QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
    QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
    for ( ; aIt != aEnd; ++aIt )
        delete *aIt;
    d->allocatedPixmapsFifo.clear();
243

244 245 246 247 248 249 250
    // 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();

251
    // reset internal variables
Enrico Ros's avatar
Enrico Ros committed
252
    d->url = KURL();
253 254 255
    d->viewportHistory.clear();
    d->viewportHistory.append( DocumentViewport() );
    d->viewportIterator = d->viewportHistory.begin();
256
    d->allocatedPixmapsTotalMemory = 0;
257 258
}

259

260
void KPDFDocument::addObserver( DocumentObserver * pObserver )
261
{
262
    // keep the pointer to the observer in a map
263
    d->observers[ pObserver->observerId() ] = pObserver;
264 265 266

    // if the observer is added while a document is already opened, tell it
    if ( !pages_vector.isEmpty() )
267
    {
268
        pObserver->notifySetup( pages_vector, true );
269 270
        pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
    }
271 272
}

273
void KPDFDocument::removeObserver( DocumentObserver * pObserver )
274 275
{
    // remove observer from the map. it won't receive notifications anymore
276 277
    if ( d->observers.contains( pObserver->observerId() ) )
    {
278
        // free observer's pixmap data
279 280 281 282
        int observerId = pObserver->observerId();
        QValueVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
        for ( ; it != end; ++it )
            (*it)->deletePixmap( observerId );
283

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
        // [MEM] free observer's allocation descriptors
        QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
        QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
        while ( aIt != aEnd )
        {
            AllocatedPixmap * p = *aIt;
            if ( p->id == observerId )
            {
                aIt = d->allocatedPixmapsFifo.remove( aIt );
                delete p;
            }
            else
                ++aIt;
        }

299
        // delete observer entry from the map
300 301
        d->observers.remove( observerId );
    }
302 303
}

304
void KPDFDocument::reparseConfig()
305
{
306 307
    // reparse generator config and if something changed clear KPDFPages
    if ( generator && generator->reparseConfig() )
308
    {
309
        // invalidate pixmaps
310
        QValueVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
311
        for ( ; it != end; ++it )
312
            (*it)->deletePixmapsAndRects();
313 314 315 316 317 318 319 320 321 322

        // [MEM] remove allocation descriptors
        QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
        QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
        for ( ; aIt != aEnd; ++aIt )
            delete *aIt;
        d->allocatedPixmapsFifo.clear();
        d->allocatedPixmapsTotalMemory = 0;

        // send reload signals to observers
323
        foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) );
324
    }
325 326 327 328 329

    // free memory if in 'low' profile
    if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low &&
         !d->allocatedPixmapsFifo.isEmpty() && !pages_vector.isEmpty() )
        cleanupPixmapMemory();
330 331
}

332

333 334 335 336 337
bool KPDFDocument::isOpened() const
{
    return generator;
}

338
const DocumentInfo * KPDFDocument::documentInfo() const
Albert Astals Cid's avatar
Albert Astals Cid committed
339
{
Enrico Ros's avatar
Enrico Ros committed
340
    return generator ? generator->generateDocumentInfo() : NULL;
341 342 343 344
}

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

348 349 350 351 352
const DocumentFonts * KPDFDocument::documentFonts() const
{
    return generator ? generator->generateDocumentFonts() : NULL;
}

353
const KPDFPage * KPDFDocument::page( uint n ) const
Albert Astals Cid's avatar
Albert Astals Cid committed
354
{
355
    return ( n < pages_vector.count() ) ? pages_vector[n] : 0;
Albert Astals Cid's avatar
Albert Astals Cid committed
356 357
}

358 359
const DocumentViewport & KPDFDocument::viewport() const
{
360
    return (*d->viewportIterator);
361 362
}

363
uint KPDFDocument::currentPage() const
Albert Astals Cid's avatar
Albert Astals Cid committed
364
{
365
    return (*d->viewportIterator).pageNumber;
Albert Astals Cid's avatar
Albert Astals Cid committed
366 367
}

368
uint KPDFDocument::pages() const
Albert Astals Cid's avatar
Albert Astals Cid committed
369
{
370
    return pages_vector.size();
Albert Astals Cid's avatar
Albert Astals Cid committed
371 372
}

Enrico Ros's avatar
Enrico Ros committed
373 374 375 376 377
KURL KPDFDocument::currentDocument() const
{
    return d->url;
}

378
bool KPDFDocument::isAllowed( int flags ) const
379
{
380
    return generator ? generator->isAllowed( flags ) : false;
381 382
}

383 384 385 386 387 388 389 390 391 392
bool KPDFDocument::historyAtBegin() const
{
    return d->viewportIterator == d->viewportHistory.begin();
}

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

393
QString KPDFDocument::getMetaData( const QString & key, const QString & option ) const
394
{
395
    return generator ? generator->getMetaData( key, option ) : QString();
396
}
397

Enrico Ros's avatar
Enrico Ros committed
398
void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & requests )
399
{
400 401 402 403 404 405 406
    if ( !generator )
    {
        // delete requests..
        QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
        for ( ; rIt != rEnd; ++rIt )
            delete *rIt;
        // ..and return
407
        return;
408
    }
409

Enrico Ros's avatar
Enrico Ros committed
410 411 412 413 414 415
    // 1. [CLEAN STACK] remove previous requests of requesterID
    int requesterID = requests.first()->id;
    QValueList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin(), sEnd = d->pixmapRequestsStack.end();
    while ( sIt != sEnd )
    {
        if ( (*sIt)->id == requesterID )
416 417 418
        {
            // delete request and remove it from stack
            delete *sIt;
Enrico Ros's avatar
Enrico Ros committed
419
            sIt = d->pixmapRequestsStack.remove( sIt );
420
        }
Enrico Ros's avatar
Enrico Ros committed
421 422 423 424 425 426
        else
            ++sIt;
    }

    // 2. [ADD TO STACK] add requests to stack
    bool threadingDisabled = !Settings::enableThreading();
427 428
    QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
    for ( ; rIt != rEnd; ++rIt )
429
    {
Enrico Ros's avatar
Enrico Ros committed
430
        // set the 'page field' (see PixmapRequest) and check if it is valid
431
        PixmapRequest * request = *rIt;
Enrico Ros's avatar
Enrico Ros committed
432
        if ( !(request->page = pages_vector[ request->pageNumber ]) )
433 434 435
        {
            // skip requests referencing an invalid page (must not happen)
            delete request;
436
            continue;
437
        }
438

Enrico Ros's avatar
Enrico Ros committed
439 440
        if ( !request->async )
            request->priority = 0;
441

Enrico Ros's avatar
Enrico Ros committed
442 443
        if ( request->async && threadingDisabled )
            request->async = false;
444

Enrico Ros's avatar
Enrico Ros committed
445 446 447
        // add request to the 'stack' at the right place
        if ( !request->priority )
            // add priority zero requests to the top of the stack
448
            d->pixmapRequestsStack.append( request );
Enrico Ros's avatar
Enrico Ros committed
449 450 451 452 453 454 455 456 457
        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 );
        }
458
    }
Enrico Ros's avatar
Enrico Ros committed
459 460 461 462 463 464

    // 3. [START FIRST GENERATION] if generator is ready, start a new generation,
    // or else (if gen is running) it will be started when the new contents will
    //come from generator (in requestDone())
    if ( generator->canGeneratePixmap() )
        sendGeneratorRequest();
465 466
}

467
void KPDFDocument::requestTextPage( uint page )
468
{
469 470 471 472
    KPDFPage * kp = pages_vector[ page ];
    if ( !generator || !kp )
        return;

473 474
    // Memory management for TextPages

Enrico Ros's avatar
Enrico Ros committed
475
    generator->generateSyncTextPage( kp );
476
}
477 478 479 480 481 482 483 484 485 486 487 488 489 490

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 ) );
}
491 492 493 494
/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
void KPDFDocument::setNextPage()
{
    // advance page and set viewport on observers
495 496
    if ( (*d->viewportIterator).pageNumber < (int)pages_vector.count() - 1 )
        setViewport( DocumentViewport( (*d->viewportIterator).pageNumber + 1 ) );
497
}
498

499
void KPDFDocument::setPrevPage()
500
{
501
    // go to previous page and set viewport on observers
502 503
    if ( (*d->viewportIterator).pageNumber > 0 )
        setViewport( DocumentViewport( (*d->viewportIterator).pageNumber - 1 ) );
504 505
}
*/
Enrico Ros's avatar
Enrico Ros committed
506
void KPDFDocument::setViewportPage( int page, int excludeId, bool smoothMove )
507 508
{
    // clamp page in range [0 ... numPages-1]
Enrico Ros's avatar
Enrico Ros committed
509
    if ( page < 0 )
510
        page = 0;
511 512
    else if ( page > (int)pages_vector.count() )
        page = pages_vector.count() - 1;
513 514

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

Enrico Ros's avatar
Enrico Ros committed
518
void KPDFDocument::setViewport( const DocumentViewport & viewport, int excludeId, bool smoothMove )
519 520
{
    // if already broadcasted, don't redo it
521
    DocumentViewport & oldViewport = *d->viewportIterator;
522 523 524
    // disabled by enrico on 2005-03-18 (less debug output)
    //if ( viewport == oldViewport )
    //    kdDebug() << "setViewport with the same viewport." << endl;
525

526 527 528 529 530 531 532 533 534 535 536 537 538 539
    // 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();
540

541 542 543 544 545
        // add the item at the end of the queue
        d->viewportIterator = d->viewportHistory.append( viewport );
    }

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

551
    // [MEM] raise position of currently viewed page in allocation queue
552 553
    if ( d->allocatedPixmapsFifo.count() > 1 )
    {
554 555
        const int page = viewport.pageNumber;
        QValueList< AllocatedPixmap * > viewportPixmaps;
556
        QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
557 558
        QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
        while ( aIt != aEnd )
559
        {
560
            if ( (*aIt)->page == page )
561
            {
562
                viewportPixmaps.append( *aIt );
563
                aIt = d->allocatedPixmapsFifo.remove( aIt );
564
                continue;
565
            }
566
            ++aIt;
567
        }
568 569 570
        if ( !viewportPixmaps.isEmpty() )
            d->allocatedPixmapsFifo += viewportPixmaps;
    }
571 572
}

573 574 575 576 577 578 579
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
580
        foreachObserver( notifyViewportChanged( true ) );
581 582 583 584 585 586 587 588 589 590 591 592
    }
}

void KPDFDocument::setNextViewport()
// restore next viewport from the history
{
    QValueList< DocumentViewport >::iterator nextIterator = d->viewportIterator;
    ++nextIterator;
    if ( nextIterator != d->viewportHistory.end() )
    {
        // restore next viewport and notify it to observers
        ++d->viewportIterator;
Enrico Ros's avatar
Enrico Ros committed
593
        foreachObserver( notifyViewportChanged( true ) );
594 595 596
    }
}

597

598 599
bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
                               SearchType type, bool moveViewport, const QColor & color, bool noDialogs )
600
{
601 602 603 604
    // don't perform searches on empty docs
    if ( !generator || pages_vector.isEmpty() )
        return false;

605
    // if searchID search not recorded, create new descriptor and init params
606 607 608
    if ( !d->searches.contains( searchID ) )
    {
        RunningSearch * search = new RunningSearch();
609
        search->continueOnPage = -1;
610 611 612 613
        d->searches[ searchID ] = search;
    }
    RunningSearch * s = d->searches[ searchID ];

614 615 616 617 618 619 620 621
    // 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;
622

623 624 625 626 627 628 629
    // global data for search
    bool foundAMatch = false;
    QValueList< int > pagesToNotify;

    // remove highlights from pages and queue them for notifying changes
    pagesToNotify += s->highlightedPages;
    QValueList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
630
    for ( ; it != end; ++it )
631 632
        pages_vector[ *it ]->deleteHighlights( searchID );
    s->highlightedPages.clear();
633

634 635
    // set hourglass cursor
    QApplication::setOverrideCursor( waitCursor );
636

637
    // 1. ALLDOC - proces all document marking pages
638 639
    if ( type == AllDoc )
    {
640
        // search and highlight 'text' (as a solid phrase) on all pages
641 642 643
        QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
        for ( ; it != end; ++it )
        {
644
            // get page (from the first to the last)
645
            KPDFPage * page = *it;
646 647 648
            int pageNumber = page->number();

            // request search page if needed
649
            if ( !page->hasSearchPage() )
650
                requestTextPage( pageNumber );
651

652 653
            // loop on a page adding highlights for all found items
            bool addedHighlights = false;
654
            NormalizedRect * lastMatch = 0;
655 656 657
            while ( 1 )
            {
                if ( lastMatch )
658
                    lastMatch = page->findText( text, caseSensitive, lastMatch );
659
                else
660
                    lastMatch = page->findText( text, caseSensitive );
661 662 663 664

                if ( !lastMatch )
                    break;

665
                // add highligh rect to the page
666
                page->setHighlight( searchID, lastMatch, color );
667
                addedHighlights = true;
668 669
            }

670 671
            // if added highlights, udpate internals and queue page for notify
            if ( addedHighlights )
672 673
            {
                foundAMatch = true;
674 675 676
                s->highlightedPages.append( pageNumber );
                if ( !pagesToNotify.contains( pageNumber ) )
                    pagesToNotify.append( pageNumber );
677 678
            }
        }
679 680 681 682

        // reset cursor to previous shape
        QApplication::restoreOverrideCursor();

683 684
        // send page lists to update observers (since some filter on bookmarks)
        foreachObserver( notifySetup( pages_vector, false ) );
685
    }
686 687
    // 2. NEXTMATCH - find next matching item (or start from top)
    else if ( type == NextMatch )
688 689 690
    {
        // find out from where to start/resume search from
        int viewportPage = (*d->viewportIterator).pageNumber;
691 692
        int currentPage = fromStart ? 0 : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
        KPDFPage * lastPage = fromStart ? 0 : pages_vector[ currentPage ];
693 694

        // continue checking last SearchPage first (if it is the current page)
695
        NormalizedRect * match = 0;
696
        if ( lastPage && lastPage->number() == s->continueOnPage )
Enrico Ros's avatar
Enrico Ros committed
697
        {
698
            if ( newText )
699
                match = lastPage->findText( text, caseSensitive );
700
            else
701 702
                match = lastPage->findText( text, caseSensitive, &s->continueOnMatch );
            if ( !match )
703
                currentPage++;
Enrico Ros's avatar
Enrico Ros committed
704 705
        }

706
        // if no match found, loop through the whole doc, starting from currentPage
707
        if ( !match )
708 709 710 711 712 713
        {
            const int pageCount = pages_vector.count();
            for ( int i = 0; i < pageCount; i++ )
            {
                if ( currentPage >= pageCount )
                {
714
                    if ( noDialogs || KMessageBox::questionYesNo(0, i18n("End of document reached.\nContinue from the beginning?")) == KMessageBox::Yes )
715 716 717 718
                        currentPage = 0;
                    else
                        break;
                }
719
                // get page
720
                KPDFPage * page = pages_vector[ currentPage ];
721
                // request search page if needed
722 723
                if ( !page->hasSearchPage() )
                    requestTextPage( page->number() );
724 725
                // if found a match on the current page, end the loop
                if ( (match = page->findText( text, caseSensitive )) )
726 727 728 729 730
                    break;
                currentPage++;
            }
        }

731 732 733 734
        // reset cursor to previous shape
        QApplication::restoreOverrideCursor();

        // if a match has been found..
735
        if ( match )
736
        {
737
            // update the RunningSearch structure adding this match..
738
            foundAMatch = true;
739 740 741
            s->continueOnPage = currentPage;
            s->continueOnMatch = *match;
            s->highlightedPages.append( currentPage );
742
            // ..add highlight to the page..
743
            pages_vector[ currentPage ]->setHighlight( searchID, match, color );
744

745
            // ..queue page for notifying changes..
746 747
            if ( !pagesToNotify.contains( currentPage ) )
                pagesToNotify.append( currentPage );
748

749 750 751
            // ..move the viewport to show the searched word centered
            if ( moveViewport )
            {
752
                DocumentViewport searchViewport( currentPage );
Enrico Ros's avatar
Enrico Ros committed
753 754 755
                searchViewport.rePos.enabled = true;
                searchViewport.rePos.normalizedX = (match->left + match->right) / 2.0;
                searchViewport.rePos.normalizedY = (match->top + match->bottom) / 2.0;
756 757
                setViewport( searchViewport, -1, true );
            }
758
        }
759
        else if ( !noDialogs )
760 761
            KMessageBox::information( 0, i18n("No matches found for '%1'.").arg( text ) );
    }
762
    // 3. PREVMATCH //TODO
763
    else if ( type == PrevMatch )
764
    {
765
    }
766 767
    // 4. GOOGLE* - process all document marking pages
    else if ( type == GoogleAll || type == GoogleAny )
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 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
        // search and highlight every word in 'text' on all pages
        bool matchAll = type == GoogleAll;
        QStringList words = QStringList::split( " ", text );
        int wordsCount = words.count(),
            hueStep = (wordsCount > 1) ? (60 / (wordsCount - 1)) : 60,
            baseHue, baseSat, baseVal;
        color.getHsv( &baseHue, &baseSat, &baseVal );
        QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
        for ( ; it != end; ++it )
        {
            // get page (from the first to the last)
            KPDFPage * page = *it;
            int pageNumber = page->number();

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

            // loop on a page adding highlights for all found items
            bool allMatched = wordsCount > 0,
                 anyMatched = false;
            for ( int w = 0; w < wordsCount; w++ )
            {
                QString word = words[ w ];
                int newHue = baseHue - w * hueStep;
                if ( newHue < 0 )
                    newHue += 360;
                QColor wordColor = QColor( newHue, baseSat, baseVal, QColor::Hsv );
                NormalizedRect * lastMatch = 0;
                // add all highlights for current word
                bool wordMatched = false;
                while ( 1 )
                {
                    if ( lastMatch )
                        lastMatch = page->findText( word, caseSensitive, lastMatch );
                    else
                        lastMatch = page->findText( word, caseSensitive );

                    if ( !lastMatch )
                        break;

                    // add highligh rect to the page
                    page->setHighlight( searchID, lastMatch, wordColor );
                    wordMatched = true;
                }
                allMatched = allMatched && wordMatched;
                anyMatched = anyMatched || wordMatched;
            }

            // if not all words are present in page, remove partial highlights
            if ( !allMatched && matchAll )
                page->deleteHighlights( searchID );

            // if page contains all words, udpate internals and queue page for notify
            if ( (allMatched && matchAll) || (anyMatched && !matchAll) )
            {
                foundAMatch = true;
                s->highlightedPages.append( pageNumber );
                if ( !pagesToNotify.contains( pageNumber ) )
                    pagesToNotify.append( pageNumber );
            }