document.cpp 10.5 KB
Newer Older
1 2 3 4 5 6 7 8 9
/***************************************************************************
 *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>                  *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

Enrico Ros's avatar
Enrico Ros committed
10
// qt/kde includes
11 12 13
#include <qfile.h>
#include <qmutex.h>
#include <qvaluevector.h>
14
#include <qmap.h>
Enrico Ros's avatar
Enrico Ros committed
15
#include <kdebug.h>
16 17 18
#include <klocale.h>
#include <kfinddialog.h>
#include <kmessagebox.h>
19 20 21

// local includes
#include "PDFDoc.h"
22
#include "QOutputDev.h"
23 24 25 26 27

#include "kpdf_error.h"
#include "document.h"
#include "page.h"

28 29 30 31 32 33
/* Notes:
- FIXME event queuing to avoid flow interruption (!!??) maybe avoided by the
  warning to not call something 'active' inside an observer method.
- TODO implement filtering (on: "incremental search", "annotated pages", 
*/

34
// structure used internally by KPDFDocument for data storage
35
class KPDFDocumentPrivate
36
{
37
public:
38 39 40
    // document related
    QMutex docLock;
    PDFDoc * pdfdoc;
41
    KPDFOutputDev * kpdfOutputDev;
42
    int currentPage;
43
    float currentPosition;
44
    QValueVector< KPDFPage* > pages;
Enrico Ros's avatar
Enrico Ros committed
45

46 47 48 49 50 51 52 53
    // find related
    QString lastSearchText;
    long lastSearchOptions;
    KPDFPage * lastSearchPage;

    // filtering related
    QString filterString;

54
    // observers related (note: won't delete oservers)
55
    QMap< int, KPDFDocumentObserver* > observers;
56 57 58
};

#define foreachObserver( cmd ) {\
59 60
    QMap<int,KPDFDocumentObserver*>::iterator it = d->observers.begin();\
    QMap<int,KPDFDocumentObserver*>::iterator end = d->observers.end();\
61 62 63 64 65 66 67 68 69
    for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }

/*
 * KPDFDocument class
 */
KPDFDocument::KPDFDocument()
{
    d = new KPDFDocumentPrivate;
    d->pdfdoc = 0;
70
    d->currentPage = -1;
71
    d->currentPosition = 0;
72
    d->lastSearchPage = 0;
73 74
    SplashColor paperColor;
    paperColor.rgb8 = splashMakeRGB8( 0xff, 0xff, 0xff );
75
    d->kpdfOutputDev = new KPDFOutputDev( paperColor );
76
}
Enrico Ros's avatar
Enrico Ros committed
77

78 79 80
KPDFDocument::~KPDFDocument()
{
    close();
81
    delete d->kpdfOutputDev;
82 83 84 85 86 87 88 89 90 91 92 93 94
    delete d;
}

bool KPDFDocument::openFile( const QString & docFile )
{
    // docFile is always local so we can use QFile on it
    QFile fileReadTest( docFile );
    if ( !fileReadTest.open( IO_ReadOnly ) )
        return false;
    fileReadTest.close();

    GString *filename = new GString( QFile::encodeName( docFile ) );
    delete d->pdfdoc;
95
    d->pdfdoc = new PDFDoc( filename, 0, 0 );
96 97 98
    deletePages();

    if ( !d->pdfdoc->isOk() )
99 100 101 102 103 104 105 106 107
    {
        delete d->pdfdoc;
        d->pdfdoc = 0;
        return false;
    }

    // clear xpdf errors
    errors::clear();

108
    // initialize output device for rendering current pdf
109
    d->kpdfOutputDev->startDoc( d->pdfdoc->getXRef() );
110 111

    // build Pages (currentPage was set -1 by deletePages)
112 113
    uint pageCount = d->pdfdoc->getNumPages();
    d->pages.resize( pageCount );
114 115 116 117 118 119 120 121
    if ( pageCount > 0 )
    {
        for ( uint i = 0; i < pageCount ; i++ )
            d->pages[i] = new KPDFPage( i, d->pdfdoc->getPageWidth(i+1), d->pdfdoc->getPageHeight(i+1), d->pdfdoc->getPageRotate(i+1) );
        // filter pages, setup observers and set the first page as current
        sendFilteredPageList();
        slotSetCurrentPage( 0 );
    }
Enrico Ros's avatar
Enrico Ros committed
122

123 124 125 126 127 128 129 130 131 132 133
    return true;
}

void KPDFDocument::close()
{
    //stopRunningJobs()...
    deletePages();
    delete d->pdfdoc;
    d->pdfdoc = 0;
}

134 135

uint KPDFDocument::currentPage() const
136
{
137
    return d->currentPage;
138 139 140 141 142 143 144
}

uint KPDFDocument::pages() const
{
    return d->pdfdoc ? d->pdfdoc->getNumPages() : 0;
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
bool KPDFDocument::atBegin() const
{
    return d->currentPage < 1;
}

bool KPDFDocument::atEnd() const
{
    return d->currentPage >= ((int)d->pages.count() - 1);
}

const KPDFPage * KPDFDocument::page( uint n ) const
{
    return ( n < d->pages.count() ) ? d->pages[n] : 0;
}


161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
void KPDFDocument::addObserver( KPDFDocumentObserver * pObserver )
{
	d->observers[ pObserver->observerId() ] = pObserver;
}

void KPDFDocument::requestPixmap( int id, uint page, int width, int height, bool syn )
{
    KPDFPage * kp = d->pages[page];
    if ( !d->pdfdoc || !kp || kp->width() < 1 || kp->height() < 1 )
        return;

    if ( syn )
    {
        // in-place Pixmap generation for syncronous requests
        if ( !kp->hasPixmap( id, width, height ) )
        {
            // compute dpi used to get an image with desired width and height
            double fakeDpiX = width * 72.0 / kp->width(),
                   fakeDpiY = height * 72.0 / kp->height();
180 181 182 183 184 185

            // setup kpdf output device: text page is generated only if we are at 72dpi.
            // since we can pre-generate the TextPage at the right res.. why not?
            bool genTextPage = !kp->hasSearchPage() && (width == kp->width()) && (height == kp->height());
            d->kpdfOutputDev->setParams( width, height, genTextPage );

186
            d->docLock.lock();
187
            d->pdfdoc->displayPage( d->kpdfOutputDev, page + 1, fakeDpiX, fakeDpiY, 0, true, false/*dolinks*/ );
188 189 190
            d->docLock.unlock();

            kp->setPixmap( id, d->kpdfOutputDev->takePixmap() );
191 192
            if ( genTextPage )
                kp->setSearchPage( d->kpdfOutputDev->takeTextPage() );
193 194 195 196 197 198 199 200 201 202 203

            d->observers[id]->notifyPixmapChanged( page );
        }
    }
    else
    {
        //TODO asyncronous events queuing
    }
}

// BEGIN slots 
204 205 206 207 208 209 210
void KPDFDocument::slotSetCurrentPage( int page )
{
    slotSetCurrentPagePosition( page, 0.0 );
}

void KPDFDocument::slotSetCurrentPagePosition( int page, float position )
{
211
    if ( page == d->currentPage && position == d->currentPosition )
212 213
        return;
    d->currentPage = page;
214
    d->currentPosition = position;
215 216 217 218
    foreachObserver( pageSetCurrent( page, position ) );
    pageChanged();
}

219
void KPDFDocument::slotSetFilter( const QString & pattern )
220
{
221 222 223 224
    d->filterString = pattern;
    if ( pattern.length() > 3 )
        sendFilteredPageList();
}
225

226 227 228 229 230 231
void KPDFDocument::slotFind( const QString & t, long opt )
{
    // reload last options if in 'find next' case
    long options = t.isEmpty() ? d->lastSearchOptions : opt;
    QString text = t.isEmpty() ? d->lastSearchText : t;
    if ( !t.isEmpty() )
232
    {
233 234
        d->lastSearchText = t;
        d->lastSearchOptions = opt;
235 236
    }

237 238 239 240 241 242 243 244 245 246 247
    // check enabled options (only caseSensitive support until now)
    bool caseSensitive = options & KFindDialog::CaseSensitive;

    // continue checking last SearchPage first (if it is the current page)
    KPDFPage * foundPage = 0;
    int currentPage = d->currentPage;
    int pageCount = d->pages.count();
    if ( d->lastSearchPage && (int)d->lastSearchPage->number() == currentPage )
        if ( d->lastSearchPage->hasText( text, caseSensitive, false ) )
            foundPage = d->lastSearchPage;
        else
248
        {
249 250 251
            d->lastSearchPage->hilightLastSearch( false );
            currentPage++;
            pageCount--;
252 253
        }

254 255 256
    if ( !foundPage )
        // loop through the whole document
        for ( int i = 0; i < pageCount; i++ )
Enrico Ros's avatar
Enrico Ros committed
257
        {
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
            if ( currentPage >= pageCount )
            {
                if ( KMessageBox::questionYesNo(0, i18n("End of document reached.\nContinue from the beginning?")) == KMessageBox::Yes )
                    currentPage = 0;
                else
                    break;
            }
            KPDFPage * page = d->pages[ currentPage ];
            if ( !page->hasSearchPage() )
            {
                // build a TextPage using the lightweight KPDFTextDev generator..
                KPDFTextDev td;
                d->docLock.lock();
                d->pdfdoc->displayPage( &td, page->number()+1, 72, 72, 0, true, false );
                d->docLock.unlock();
                // ..and attach it to the page
                page->setSearchPage( td.takeTextPage() );
            }
            if ( page->hasText( text, caseSensitive, true ) )
            {
                foundPage = page;
                break;
            }
            currentPage++;
Enrico Ros's avatar
Enrico Ros committed
282 283
        }

284
    if ( foundPage )
285
    {
286 287 288 289
        d->lastSearchPage = foundPage;
        foundPage->hilightLastSearch( true );
        slotSetCurrentPage( foundPage->number() );
        foreachObserver( notifyPixmapChanged( foundPage->number() ) );
290
    }
291 292
    else
        KMessageBox::information( 0, i18n("No matches found for '%1'.").arg(text) );
293 294
}

295
void KPDFDocument::slotGoToLink( /* QString anchor */ )
296
{
297
}
298
//END slots 
299

300
void KPDFDocument::sendFilteredPageList( bool forceEmpty )
301
{
302 303 304
    // make up a value list of the pages [1,2,3..]
    uint pageCount = d->pages.count();
    QValueList<int> pagesList;
305 306 307
    if ( !forceEmpty )
        for ( uint i = 0; i < pageCount ; i++ )
            pagesList.push_back( i );
308 309 310 311 312 313 314 315 316 317 318

    // send the list to observers
    foreachObserver( pageSetup( pagesList ) );
}

void KPDFDocument::deletePages()
{
    if ( d->pages.isEmpty() )
        return;

    // broadcast an empty page list to observers
319
    sendFilteredPageList( true );
320 321 322 323 324 325

    // delete pages and clear container
    for ( uint i = 0; i < d->pages.count() ; i++ )
        delete d->pages[i];
    d->pages.clear();
    d->currentPage = -1;
326
    d->lastSearchPage = 0;
327 328
}

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
/** TO BE IMPORTED:
	void generateThumbnails(PDFDoc *doc);
	void stopThumbnailGeneration();
protected slots:
	void customEvent(QCustomEvent *e);
private slots:
	void changeSelected(int i);
	void emitClicked(int i);
signals:
	void clicked(int);
private:
	void generateNextThumbnail();
	ThumbnailGenerator *m_tg;

	void resizeThumbnails();
	int m_nextThumbnail;
	bool m_ignoreNext;

DELETE:
if (m_tg)
{
	m_tg->wait();
	delete m_tg;
}

void ThumbnailList::generateThumbnails(PDFDoc *doc)
{
	m_nextThumbnail = 1;
	m_doc = doc;
	generateNextThumbnail();
}

void ThumbnailList::generateNextThumbnail()
{
	if (m_tg)
	{
		m_tg->wait();
		delete m_tg;
	}
	m_tg = new ThumbnailGenerator(m_doc, m_docMutex, m_nextThumbnail, QPaintDevice::x11AppDpiX(), this);
	m_tg->start();
}


void ThumbnailList::stopThumbnailGeneration()
{
	if (m_tg)
	{
		m_ignoreNext = true;
		m_tg->wait();
		delete m_tg;
		m_tg = 0;
	}
}


void ThumbnailList::customEvent(QCustomEvent *e)
{
	if (e->type() == 65432 && !m_ignoreNext)
	{
		QImage *i =  (QImage*)(e -> data());
		
		setThumbnail(m_nextThumbnail, i);
		m_nextThumbnail++;
		if (m_nextThumbnail <= m_doc->getNumPages()) generateNextThumbnail();
		else
		{
			m_tg->wait();
			delete m_tg;
			m_tg = 0;
		}
	}
	m_ignoreNext = false;
}
*/

405
#include "document.moc"