Commit 962f891b authored by Enrico Ros's avatar Enrico Ros
Browse files

For Users:

Display contents faster, waste less memory. Added preloading to forward-
generate pages (if threading enabled and memory profile >= normal). Main
pages are always generated first, then thumbnails come, then preload ones,
etc.. Btw memory and cpu will be happier now.
(1 bug in memory and 1 in preloading still remains.. will be spotted soon)

For Developers:
Generator: dropped moc, api changes, better functions naming.
PDFGenerator and Document: moved requests queue to the Document.
   The generator performs only a single request a time. A flag can be
   queried to know if the generator is ready for starting another pixmap
   generation or not (in case it's generating a pixmap in background).
PixmapRequest: added priority and backgound (async) attributes.
Document: queuing requests by priority in requestPixmaps.
Observer: added defines for priorities used in doc->requestpixmaps.
PageView: delayed find-ahead timer creation (not created if not needed).
   Added preload PixmapRequests to the pixmap requests list.
ThumbnailsList: adapted to pixmapRequest changes and changed naming for
   delayed thumbanils request.

svn path=/trunk/kdegraphics/kpdf/; revision=379820
parent c0029ac3
......@@ -6,24 +6,13 @@ Legend:
MRG - MeRGed from head
(*) - Some parts of this item are already done
In progress:
-> gen / mem: priorities for requests (pageview, thumbnails, pv preloading, th preloading)
-> mem: iterate from older pages to newest ones when freeing memory (not rand)
-> gen / mem: sync Memory Management (in Document) with the Generator, so
generators can be notified when cancelling jobs (use the notification signal
from generator) -> real cleaning
-> gen / mem: check for removing 'canUnloadPixmap' from observers in place of requesting a
list of visible pixmaps. Think at adding visible list to ObserverData and
passing the visible list from observers (to remove visi lists)
-> generator: add preloading
Urgent fixes and items to get ready before 3.4 (special high-priority list):
-> fix: find-as-you-type (start kpdf, /, aaa, backspace, open doc)
In progress:
-> new word highlighting for searches / other highlights (on paper - enrico)
-> cleanup code and update README.png
-> mem: iterate from older pages to newest ones when freeing memory (not rand)
More items (first items will enter 'In progress list' first):
-> cleanup code and update README.png
-> find-as-you-type: use shortcut for 'find next' action (not the default one)
-> show Viewport in ThumbnailsList (blended/contour)
-> Delay TOC (DocumentSynapsis) generation (and move it on thread)
......@@ -88,7 +77,9 @@ More items (first items will enter 'In progress list' first):
-> Albert: Read pdf specification and see if paths with length = 1 are allowed, in case they are allowed see how to fix 97131 without skipping paths with length = 1
Done (newest features come first):
-> type ahead search in pageview (type '/' then the word to search..)
-> ADD: page preloading
-> FIX: smarter memory management / prioritize queries
-> ADD: type ahead search in pageview (type '/' then the word to search..) (JakubS)
-> FIX: scroll page if the the searched string is not visible
-> FIX: use a global Viewport over the document (linked views, real link following, location restoring, etc)
-> FIX: wrong zoom buttons order (BR74248) (check consistancy with kdvi/kviewshell/kghostview/.. (not konq))
......
......@@ -53,7 +53,7 @@ class KPDFDocumentPrivate
// observers / requests stuff
QMap< int, class ObserverData* > observers;
//QValueList< PixmapRequest * > asyncRequestsQueue;
QValueList< PixmapRequest * > pixmapRequestsStack;
// timers (memory checking / info saver)
QTimer * memCheckTimer;
......@@ -122,7 +122,7 @@ bool KPDFDocument::openDocument( const QString & docFile )
KMimeType::Ptr mime = KMimeType::findByPath( docFile );
QString mimeName = mime->name();
if ( mimeName == "application/pdf" )
generator = new PDFGenerator();
generator = new PDFGenerator( this );
else if ( mimeName == "application/postscript" )
kdError() << "PS generator not available" << endl;
else
......@@ -130,9 +130,6 @@ bool KPDFDocument::openDocument( const QString & docFile )
kdWarning() << "Unknown mimetype '" << mimeName << "'." << endl;
return false;
}
// get notification of completed jobs
connect( generator, SIGNAL( contentsChanged( int, int ) ),
this, SLOT( slotGeneratedContents( int, int ) ) );
// 1. load Document (and set busy cursor while loading)
QApplication::setOverrideCursor( waitCursor );
......@@ -181,6 +178,13 @@ void KPDFDocument::closeDocument()
delete generator;
generator = 0;
// 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();
// send an empty list to observers (to free their data)
foreachObserver( notifySetup( QValueVector< KPDFPage * >(), true ) );
......@@ -252,12 +256,12 @@ bool KPDFDocument::isOpened() const
const DocumentInfo * KPDFDocument::documentInfo() const
{
return generator ? generator->documentInfo() : NULL;
return generator ? generator->generateDocumentInfo() : NULL;
}
const DocumentSynopsis * KPDFDocument::documentSynopsis() const
{
return generator ? generator->documentSynopsis() : NULL;
return generator ? generator->generateDocumentSynopsis() : NULL;
}
const KPDFPage * KPDFDocument::page( uint n ) const
......@@ -282,7 +286,7 @@ uint KPDFDocument::pages() const
bool KPDFDocument::okToPrint() const
{
return generator ? generator->allowed( Generator::Print ) : false;
return generator ? generator->isAllowed( Generator::Print ) : false;
}
QString KPDFDocument::getMetaData( const QString & key, const QString & option ) const
......@@ -290,35 +294,66 @@ QString KPDFDocument::getMetaData( const QString & key, const QString & option )
return generator ? generator->getMetaData( key, option ) : QString();
}
void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & requests, bool async )
void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & requests )
{
if ( !generator )
if ( !generator || requests.isEmpty() )
return;
#ifndef NDEBUG
//TODO REMOVE THIS
if ( !d->pixmapRequestsStack.isEmpty() && (*d->pixmapRequestsStack.begin())->priority == 0 )
kdDebug() << "calling requestPixmaps when SYNC generation has not yet finished." << endl;
#endif
// 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 )
sIt = d->pixmapRequestsStack.remove( sIt );
else
++sIt;
}
// 2. [ADD TO STACK] add requests to stack
bool threadingDisabled = !Settings::enableThreading();
bool preloadDisabled = threadingDisabled ||
Settings::memoryLevel() == Settings::EnumMemoryLevel::Low;
QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
for ( ; rIt != rEnd; ++rIt )
{
// set the 'page field' (see PixmapRequest) and check if request is valid
// set the 'page field' (see PixmapRequest) and check if it is valid
PixmapRequest * request = *rIt;
request->page = pages_vector[ request->pageNumber ];
if ( !request->page || request->page->width() < 1 || request->page->height() < 1 )
if ( !(request->page = pages_vector[ request->pageNumber ]) )
continue;
// 1. Update statistics (pageMemory / totalMemory) adding this pixmap
ObserverData * obs = d->observers[ request->id ];
int pageNumber = request->pageNumber;
if ( obs->pageMemory.contains( pageNumber ) )
obs->totalMemory -= obs->pageMemory[ pageNumber ];
int pixmapMemory = 4 * request->width * request->height / 1024;
obs->pageMemory[ pageNumber ] = pixmapMemory;
obs->totalMemory += pixmapMemory;
if ( !request->async )
request->priority = 0;
// 2. Perform pre-cleaning if needed
mCleanupMemory( request->id );
if ( request->async && threadingDisabled )
request->async = false;
// 3. Enqueue to Generator (that takes ownership of request)
generator->requestPixmap( request, Settings::enableThreading() ? async : false );
// add request to the 'stack' at the right place
if ( !request->priority )
// add priority zero requests to the top of the stack
d->pixmapRequestsStack.push_back( request );
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 );
}
}
// 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();
}
void KPDFDocument::requestTextPage( uint page )
......@@ -329,7 +364,7 @@ void KPDFDocument::requestTextPage( uint page )
// Memory management for TextPages
generator->requestTextPage( kp );
generator->generateSyncTextPage( kp );
}
/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
void KPDFDocument::setNextPage()
......@@ -600,6 +635,58 @@ bool KPDFDocument::print( KPrinter &printer )
return generator ? generator->print( printer ) : false;
}
void KPDFDocument::requestDone( PixmapRequest * req ) //FIXME
{
// notify an observer that its pixmap changed
if ( d->observers.contains( req->id ) )
d->observers[ req->id ]->instance->notifyPageChanged( req->pageNumber, DocumentObserver::Pixmap );
// delete request
delete req;
// start a new generation
#ifndef NDEBUG
if ( !generator->canGeneratePixmap() )
kdDebug() << "requestDone with generator not in READY state." << endl;
#endif
sendGeneratorRequest();
}
void KPDFDocument::sendGeneratorRequest()
{
// find a request
PixmapRequest * request = 0;
while ( !d->pixmapRequestsStack.isEmpty() && !request )
{
PixmapRequest * r = d->pixmapRequestsStack.last();
d->pixmapRequestsStack.pop_back();
// request only if page isn't already present
if ( !r->page->hasPixmap( r->id, r->width, r->height ) )
request = r;
else
delete r;
}
// if no request found (or already generated), return
if ( !request )
return;
// FIXME CHECK PREALLOC
// MEM: calc memory that should be freed to allow the new generation
int pixmapMemory = 4 * request->width * request->height / 1024;
// MEM: update statistics counting this pixmap
ObserverData * obs = d->observers[ request->id ];
int pageNumber = request->pageNumber;
if ( obs->pageMemory.contains( pageNumber ) )
obs->totalMemory -= obs->pageMemory[ pageNumber ];
obs->pageMemory[ pageNumber ] = pixmapMemory;
obs->totalMemory += pixmapMemory;
// MEM: cleanup
mCleanupMemory( request->id );
// submit the request to the generator
generator->generatePixmap( request );
}
void KPDFDocument::mCleanupMemory( int observerId )
{
......@@ -906,13 +993,6 @@ void KPDFDocument::slotCheckMemory()
mCleanupMemory( it.key() /*observerId*/ );
}
void KPDFDocument::slotGeneratedContents( int id, int pageNumber )
{
// notify an observer that its pixmap changed
if ( d->observers.contains( id ) )
d->observers[ id ]->instance->notifyPageChanged( pageNumber, DocumentObserver::Pixmap );
}
/** DocumentViewport **/
......@@ -1060,4 +1140,3 @@ DocumentSynopsis::DocumentSynopsis()
}
#include "document.moc"
#include "generator.moc"
......@@ -73,7 +73,7 @@ class KPDFDocument : public QObject // only for a private slot..
// perform actions on document / pages
void setViewportPage( int page, int id = -1 );
void setViewport( const DocumentViewport & viewport, int id = -1 );
void requestPixmaps( const QValueList< PixmapRequest * > & requests, bool asyncronous );
void requestPixmaps( const QValueList< PixmapRequest * > & requests );
void requestTextPage( uint page );
bool findText( const QString & text = "", bool caseSensitive = false, bool findAhead = false );
void findTextAll( const QString & pattern, bool caseSensitive );
......@@ -82,11 +82,15 @@ class KPDFDocument : public QObject // only for a private slot..
bool print( KPrinter &printer );
void unHilightPages(bool filteredOnly = true);
// notifications sent by generator
void requestDone( PixmapRequest * request );
signals:
void linkFind();
void linkGoToPage();
private:
void sendGeneratorRequest();
// memory management related functions
void mCleanupMemory( int observerId );
int mTotalMemory();
......@@ -98,14 +102,12 @@ class KPDFDocument : public QObject // only for a private slot..
void processPageList( bool documentChanged );
Generator * generator;
QString documentFileName;
QValueVector< KPDFPage * > pages_vector;
class KPDFDocumentPrivate * d;
private slots:
void saveDocumentInfo() const;
void slotCheckMemory();
void slotGeneratedContents( int id, int pageNumber );
};
......
......@@ -13,18 +13,15 @@
#include <qobject.h>
#include <qvaluevector.h>
#include <qstring.h>
#include "core/document.h"
class KPrinter;
class KPDFPage;
class KPDFLink;
class KPDFDocument;
class DocumentSynopsis;
class DocumentInfo;
class PixmapRequest;
/* Note: on contents generation and asyncronous queries.
* Many observers may want to request data syncronously or asyncronously.
* - Sync requests. These should be done in-place. Syncronous events in the
* queue have precedence on all the asyncronous ones.
* - Sync requests. These should be done in-place.
* - Async request must be done in real background. That usually means a
* thread, such as QThread derived classes.
* Once contents are available, they must be immediately stored in the
......@@ -44,32 +41,41 @@ class PixmapRequest;
*/
class Generator : public QObject
{
Q_OBJECT
public:
/** virtual methods to reimplement **/
// load a document and fill up the pagesVector
virtual bool loadDocument( const QString & fileName, QValueVector< KPDFPage* > & pagesVector ) = 0;
virtual bool loadDocument( const QString & fileName, QValueVector< KPDFPage * > & pagesVector ) = 0;
// Document description and Table of contents
virtual const DocumentInfo * documentInfo() { return 0L; }
virtual const DocumentSynopsis * documentSynopsis() { return 0L; }
virtual const DocumentInfo * generateDocumentInfo() { return 0L; }
virtual const DocumentSynopsis * generateDocumentSynopsis() { return 0L; }
// DRM handling
enum Permissions { Modify = 1, Copy = 2, Print = 4, AddNotes = 8 };
virtual bool allowed( int /*permisisons*/ ) { return true; }
virtual bool isAllowed( int /*permisisons*/ ) { return true; }
// generator core
virtual bool print( KPrinter& /*printer*/ ) { return false; }
virtual void requestPixmap( PixmapRequest * request, bool asyncronous ) = 0;
virtual void requestTextPage( KPDFPage * page ) = 0;
// page contents generation
virtual bool canGeneratePixmap() = 0;
virtual void generatePixmap( PixmapRequest * request ) = 0;
virtual void generateSyncTextPage( KPDFPage * page ) = 0;
// check configuration and return true if something changed
// print document using already configured kprinter
virtual bool print( KPrinter& /*printer*/ ) { return false; }
// access meta data of the generator
virtual QString getMetaData( const QString &/*key*/, const QString &/*option*/ ) { return QString(); }
// tell generator to re-parse configuration and return true if something changed
virtual bool reparseConfig() { return false; }
// Access meta data of the generator
virtual QString getMetaData( const QString &/*key*/, const QString &/*option*/ ) { return QString(); }
/** 'signals' to send events the KPDFDocument **/
// tell the document that the job has been completed
void signalRequestDone( PixmapRequest * request ) { m_document->requestDone( request ); }
signals:
void contentsChanged( int id, int pageNumber );
/** constructor: takes the Document as a parameter **/
Generator( KPDFDocument * doc ) : m_document( doc ) {};
private:
Generator();
KPDFDocument * m_document;
};
/**
......@@ -77,18 +83,24 @@ class Generator : public QObject
*/
struct PixmapRequest
{
// public data fields
PixmapRequest( int rId, int n, int w, int h, int p, bool a = false )
: id( rId ), pageNumber( n ), width( w ), height( h ),
priority( p ), async( a ), page( 0 ) {};
// observer id
int id;
// page number and size
int pageNumber;
int width;
int height;
// this field is set by the document before passing the
// asyncronous request priority (less is better, 0 is max)
int priority;
// generate the pixmap in a thread and notify observer when done
bool async;
// this field is set by the Docuemnt prior passing the
// request to the generator
KPDFPage * page;
// public constructor: initialize data
PixmapRequest( int rId, int n, int w, int h )
: id( rId ), pageNumber( n ), width( w ), height( h ), page( 0 ) {};
};
#endif
This diff is collapsed.
......@@ -43,26 +43,30 @@ class PDFPixmapGeneratorThread;
class PDFGenerator : public Generator
{
public:
PDFGenerator();
PDFGenerator( KPDFDocument * document );
virtual ~PDFGenerator();
// [INHERITED] load a document and fill up the pagesVector
bool loadDocument( const QString & fileName, QValueVector<KPDFPage*> & pagesVector );
// [INHERITED] document informations
const DocumentInfo * documentInfo();
const DocumentSynopsis * documentSynopsis();
const DocumentInfo * generateDocumentInfo();
const DocumentSynopsis * generateDocumentSynopsis();
// [INHERITED] perform actions on document / pages
bool canGeneratePixmap();
void generatePixmap( PixmapRequest * request );
void generateSyncTextPage( KPDFPage * page );
// [INHERITED] print page using an already configured kprinter
bool print( KPrinter& printer );
void requestPixmap( PixmapRequest * request, bool asyncronous );
void requestTextPage( KPDFPage * page );
// [INHERITED] reply to some metadata requests
QString getMetaData( const QString & key, const QString & option );
// [INHERITED] reparse configuration
bool reparseConfig();
QString getMetaData( const QString & key, const QString & option );
private:
// friend class to access private document related variables
friend class PDFPixmapGeneratorThread;
......@@ -74,9 +78,7 @@ class PDFGenerator : public Generator
void addSynopsisChildren( QDomNode * parent, GList * items );
// private function for creating the transition information
void addTransition( int pageNumber, KPDFPage * page );
// (GT) take the first queued item from the stack and feed it to the thread
void startNewThreadedGeneration();
// (GT) receive data from the generator thread
// (async related) receive data from the generator thread
void customEvent( QCustomEvent * );
// xpdf dependant stuff
......@@ -85,11 +87,12 @@ class PDFGenerator : public Generator
KPDFOutputDev * kpdfOutputDev;
QColor paperColor;
// asyncronous generation related things
// asyncronous generation related stuff
PDFPixmapGeneratorThread * generatorThread;
QValueList< PixmapRequest * > requestsQueue;
// misc variables for document info and synopsis caching
bool ready;
PixmapRequest * pixmapRequest;
bool docInfoDirty;
DocumentInfo docInfo;
bool docSynopsisDirty;
......@@ -109,10 +112,6 @@ class PDFPixmapGeneratorThread : public QThread
// set the request to the thread (it will be reparented)
void startGeneration( PixmapRequest * request );
// get a const reference to the currently processed pixmap
const PixmapRequest * currentRequest() const;
// return wether we can add a new request or not
bool isReady() const;
// end generation
void endGeneration();
......
......@@ -21,6 +21,13 @@
#define THUMBNAILS_ID 4
#define TOC_ID 5
/** PRIORITIES for requests. Globally defined here. **/
#define PAGEVIEW_PRIO 1
#define PAGEVIEW_PRELOAD_PRIO 3
#define THUMBNAILS_PRIO 2
#define THUMBNAILS_PRELOAD_PRIO 4
#define PRESENTATION_PRIO 0
class KPDFPage;
/**
......
......@@ -41,6 +41,11 @@ KPDFPage::KPDFPage( uint page, float w, float h, int r )
m_width = h;
m_height = w;
}
// avoid Division-By-Zero problems in the program
if ( m_width <= 0 )
m_width = 1;
if ( m_height <= 0 )
m_height = 1;
}
KPDFPage::~KPDFPage()
......
......@@ -115,8 +115,8 @@ PageView::PageView( QWidget *parent, KPDFDocument *document )
d->mouseOnRect = false;
d->delayTimer = 0;
d->scrollTimer = 0;
d->findTimer = 0;
d->typeAheadActivated = false;
d->findTimer = new QTimer(this);
d->scrollIncrement = 0;
d->dirtyLayout = false;
d->blockViewport = false;
......@@ -144,11 +144,6 @@ PageView::PageView( QWidget *parent, KPDFDocument *document )
// setCornerWidget( resizeButton );
// resizeButton->setEnabled( false );
// connect(...);
// find ahead timeout timer
connect(d->findTimer, SIGNAL(timeout()), this, SLOT(findTimeout()));
}
PageView::~PageView()
......@@ -504,61 +499,66 @@ void PageView::viewportResizeEvent( QResizeEvent * )
void PageView::keyPressEvent( QKeyEvent * e )
{
e->accept();
// based on khtml/khtmlview.cpp
if(d->typeAheadActivated)
// handle 'find as you type' (based on khtml/khtmlview.cpp)
if( d->typeAheadActivated )
{
// type-ahead find aka find-as-you-type
if(e->key() == Key_BackSpace)
if( e->key() == Key_BackSpace )
{
d->findString = d->findString.left(d->findString.length() - 1);
if(!d->findString.isEmpty())
d->findString = d->findString.left( d->findString.length() - 1 );
if( !d->findString.isEmpty() )
{
findAhead(false);
d->findTimer->start(3000, true);
findAhead( false );
d->findTimer->start( 3000, true );
}
else
{
findTimeout();
d->document->unHilightPages(false);
d->document->unHilightPages( false );
}
return;
}
else if(e->key() == KStdAccel::findNext())
{ // part doesn't get this key event because of the keyboard grab
else if( e->key() == KStdAccel::findNext() )
{
// part doesn't get this key event because of the keyboard grab
d->findTimer->stop(); // restore normal operation during possible messagebox is displayed
releaseKeyboard();
if (d->document->findText())
d->messageWindow->display(i18n("Text found: \"%1\".").arg(d->findString.lower()),
PageViewMessage::Info, 3000);
d->findTimer->start(3000, true);
grabKeyboard();
releaseKeyboard();
if ( d->document->findText() )
d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->findString.lower()),
PageViewMessage::Info, 3000 );
d->findTimer->start( 3000, true );
grabKeyboard();
return;
}
else if(e->key() == Key_Escape || e->key() == Key_Return)
else if( e->key() == Key_Escape || e->key() == Key_Return )
{
findTimeout();
return;
}
else if(e->text().isEmpty() == false)
else if( e->text().isEmpty() == false )
{
d->findString += e->text();
findAhead(true);
d->findTimer->start(3000, true);
findAhead( true );
d->findTimer->start( 3000, true );
return;
}
}
else if(e->key() == '/' && d->document->isOpened())
else if( e->key() == '/' && d->document->isOpened() )
{
d->findString="";
d->findString = QString();
d->messageWindow->display(i18n("Starting -- find text as you type"), PageViewMessage::Info, 3000);
d->typeAheadActivated = true;