Commit 16908b2f authored by Enrico Ros's avatar Enrico Ros

Changed pixmap requesting methods. Now each request is packed into a

PixmapRequest class. When requesting pixmaps, one or multiple requests are
sent to the Document that (frees memory as in current policy) and send each
PixmapRequest to the current Generator. Added a signal in generators to
notify the Document when a pixmap generation has finished.

PageView, ThumbnailsList, PreviewWidget have been unbroken after the memory
management commit. (mem management seems in pretty good shape..it's smart.)
Added 'visible widgets' list to those classes to speed up searching and
processing on visible widgets only.

Note: asyncronous pixmap requests can now be queued and we're getting very
close to the threaded generator.

Note2: Leakfixes and memory improvements.

Final NOTE: head merging is possible now, as all remaining work can be
considered bugfixes.. API is getting final. It will only change in xpdf
dep stuff, the already undefined Viewport object and some bits in
Generators.

svn path=/branches/kpdf_experiments/kdegraphics/kpdf/; revision=372787
parent c9dc4c5b
......@@ -27,6 +27,7 @@ Higher priority after merge:
More items (first items will enter 'In progress list' first):
*THIS ITEMS ARE CURRENTLY FROZEN SINCE "HEAD-MERGE" LIST IS CLEARED*
-> fix: iterate from older pages to newest ones when freeing memory (not randomly)
-> fix: On continous view mode, if you click to a link that moves you to another
page, then scroll up and click again on the same link it does not work. (by Albert)
-> JJ: convert DocumentInfo to a DomTree containing a few common fields (see the current
......
......@@ -95,14 +95,18 @@ bool KPDFDocument::openDocument( const QString & docFile )
QString mimeName = mime->name();
if ( mimeName == "application/pdf" )
generator = new PDFGenerator();
// else if ( mimeName == "application/postscript" )
// generator = new PSGenerator();
else if ( mimeName == "application/postscript" )
kdError() << "PS generator not available" << endl;
else
{
kdWarning() << "Unknown mimetype '" << mimeName << "'." << endl;
return false;
}
// get notification of completed jobs
connect( generator, SIGNAL( contentsChanged( int, int ) ),
this, SLOT( slotGeneratedContents( int, int ) ) );
// ask generator to open the document and return if unsuccessfull
documentFileName = docFile;
bool openOk = generator->loadDocument( docFile, pages_vector );
if ( !openOk )
......@@ -134,6 +138,15 @@ void KPDFDocument::closeDocument()
// send an empty list to observers (to free their data)
foreachObserver( pageSetup( pages_vector, true ) );
// clear memory management data
QMap< int, ObserverData * >::iterator oIt = d->observers.begin(), oEnd = d->observers.end();
for ( ; oIt != oEnd ; ++oIt )
{
ObserverData * observerData = *oIt;
observerData->pageMemory.clear();
observerData->totalMemory = 0;
}
// delete contents generator
delete generator;
generator = 0;
......@@ -215,65 +228,35 @@ bool KPDFDocument::okToPrint() const
}
void KPDFDocument::requestPixmap( int id, int page, int width, int height, bool syn )
void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & requests, bool syn )
{
KPDFPage * kp = pages_vector[ page ];
if ( !generator || !kp || kp->width() < 1 || kp->height() < 1 )
if ( !generator )
return;
// 1. Update statistics (pageMemory / totalMemory) adding this pixmap
ObserverData * obs = d->observers[ id ];
if ( obs->pageMemory.contains( page ) )
obs->totalMemory -= obs->pageMemory[ page ];
int pixmapMemory = 4 * width * height / 1024;
obs->pageMemory[ page ] = pixmapMemory;
obs->totalMemory += pixmapMemory;
//
int memoryToFree;
switch ( Settings::memoryLevel() )
QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
for ( ; rIt != rEnd; ++rIt )
{
case Settings::EnumMemoryLevel::Low:
memoryToFree = obs->totalMemory;
break;
case Settings::EnumMemoryLevel::Normal:
memoryToFree = obs->totalMemory - mTotalMemory()/4;
printf("%d\n",memoryToFree);
break;
case Settings::EnumMemoryLevel::Aggressive:
memoryToFree = 0;
break;
// set the 'page field' (see PixmapRequest) and check if request is valid
PixmapRequest * request = *rIt;
request->page = pages_vector[ request->pageNumber ];
if ( !request->page || request->page->width() < 1 || request->page->height() < 1 )
continue;
// 1. Update statistics (pageMemory / totalMemory) adding this pixmap
int pageNumber = request->pageNumber;
ObserverData * obs = d->observers[ request->id ];
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;
// 2. Perform pre-cleaning if needed
mCleanupMemory( request->id );
// 3. Enqueue to Generator (that takes ownership of request)
generator->requestPixmap( request, syn );
}
// 2. FREE Memory Loop. remove older data first.
int freed = 0;
if ( memoryToFree > 0 )
{
QMap< int, int >::iterator it = obs->pageMemory.begin(), end = obs->pageMemory.end();
while ( (it != end) && (memoryToFree > 0) )
{
int freeNumber = it.key();
if ( page != freeNumber && obs->observer->canUnloadPixmap( freeNumber ) )
{
// update mem stats
memoryToFree -= it.data();
obs->totalMemory -= it.data();
obs->pageMemory.remove( it );
// delete pixmap
pages_vector[ freeNumber ]->deletePixmap( id );
freed++;
}
++it;
}
}
kdWarning() << "[" << obs->totalMemory << "] Removed " << freed << " pages. " << obs->pageMemory.count() << " pages kept in memory." << endl;
// 3. Request next pixmap to generator
bool pixChanged = generator->requestPixmap( id, kp, width, height, syn );
if ( pixChanged )
d->observers[id]->observer->notifyPixmapChanged( page );
}
void KPDFDocument::requestTextPage( uint page )
......@@ -506,13 +489,63 @@ bool KPDFDocument::print( KPrinter &printer )
}
void KPDFDocument::mCleanupMemory( int observerId )
{
// get observer data for given id
ObserverData * obs = d->observers[ observerId ];
// choose memory parameters based on configuration profile
int memoryToFree = 0;
switch ( Settings::memoryLevel() )
{
case Settings::EnumMemoryLevel::Low:
memoryToFree = obs->totalMemory;
break;
case Settings::EnumMemoryLevel::Normal:
memoryToFree = obs->totalMemory - mTotalMemory()/4;
break;
case Settings::EnumMemoryLevel::Aggressive:
memoryToFree = 0;
break;
}
// free memory. remove older data until we free enough memory
int freed = 0;
if ( memoryToFree > 0 )
{
QMap< int, int >::iterator it = obs->pageMemory.begin(), end = obs->pageMemory.end();
while ( (it != end) && (memoryToFree > 0) )
{
int freeNumber = it.key();
if ( obs->observer->canUnloadPixmap( freeNumber ) )
{
// update mem stats
memoryToFree -= it.data();
obs->totalMemory -= it.data();
obs->pageMemory.remove( it );
// delete pixmap
pages_vector[ freeNumber ]->deletePixmap( observerId );
freed++;
}
++it;
}
}
kdWarning() << "[" << obs->totalMemory << "kB] Removed " << freed << " pages. " << obs->pageMemory.count() << " pages kept in memory." << endl;
}
int KPDFDocument::mTotalMemory()
{
static int cachedValue = 0;
if ( cachedValue )
return cachedValue;
#ifdef __linux__
// if /proc/meminfo doesn't exist, return 128MB
QFile memFile( "/proc/meminfo" );
if ( !memFile.open( IO_ReadOnly ) )
return 131072;
return (cachedValue = 131072);
// read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
// and 'Cached' fields. consider swapped memory as used memory.
......@@ -521,10 +554,10 @@ int KPDFDocument::mTotalMemory()
{
QString entry = readStream.readLine();
if ( entry.startsWith( "MemTotal:" ) )
return entry.section( ' ', -2, -2 ).toInt();
return (cachedValue = entry.section( ' ', -2, -2 ).toInt());
}
#endif
return 131072;
return (cachedValue = 131072);
}
int KPDFDocument::mFreeMemory()
......@@ -622,3 +655,11 @@ void KPDFDocument::unHilightPages()
}
}
}
void KPDFDocument::slotGeneratedContents( int id, int pageNumber )
{
if ( d->observers.contains( id ) )
d->observers[ id ]->observer->notifyPixmapChanged( pageNumber );
}
#include "document.moc"
......@@ -21,6 +21,7 @@ class KPDFLink;
class Generator;
class DocumentInfo;
class DocumentSynopsis;
class PixmapRequest;
/**
* @short Base class for objects being notified when something changes.
......@@ -69,8 +70,9 @@ class KPDFDocumentObserver
* For a better understanding of hieracies @see README.internals.png
* @see KPDFDocumentObserver, KPDFPage
*/
class KPDFDocument
class KPDFDocument : public QObject // only for a private slot..
{
Q_OBJECT
public:
KPDFDocument();
~KPDFDocument();
......@@ -93,8 +95,7 @@ class KPDFDocument
bool okToPrint() const;
// perform actions on document / pages
//void requestPixmaps( int id, const QValueList<int> & pages, int width, int height, bool syncronous = false );
void requestPixmap( int id, int pageNum, int width, int height, bool syncronous = false );
void requestPixmaps( const QValueList< PixmapRequest * > & requests, bool syncronous = false );
void requestTextPage( uint page );
void setCurrentPage( int page, const QRect & viewport = QRect() );
void findText( const QString & text = "", bool caseSensitive = false );
......@@ -105,6 +106,7 @@ class KPDFDocument
private:
// memory management related functions
void mCleanupMemory( int observerId );
int mTotalMemory();
int mFreeMemory();
// more private functions
......@@ -117,6 +119,9 @@ class KPDFDocument
QString documentFileName;
QValueVector< KPDFPage * > pages_vector;
class KPDFDocumentPrivate * d;
private slots:
void slotGeneratedContents( int id, int pageNumber );
};
/**
......
......@@ -19,6 +19,7 @@ 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.
......@@ -57,15 +58,34 @@ class Generator : public QObject
virtual bool allowed( int /*permisisons*/ ) { return true; }
// generator core
virtual bool print( KPrinter& printer ) { return false; }
virtual bool requestPixmap( int id, KPDFPage * page, int width, int height, bool syncronous = false ) = 0;
virtual bool print( KPrinter& /*printer*/ ) { return false; }
virtual void requestPixmap( PixmapRequest * request, bool syncronous = false ) = 0;
virtual void requestTextPage( KPDFPage * page ) = 0;
// check configuration and return true if something changed
virtual bool reparseConfig() { return false; }
signals:
void contentsChanged( const KPDFPage * page );
void contentsChanged( int id, int pageNumber );
};
/**
* @short Describes a pixmap type request.
*/
struct PixmapRequest
{
// public data fields
int id;
int pageNumber;
int width;
int height;
// this field is set by the document before 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
......@@ -259,47 +259,45 @@ bool PDFGenerator::print( KPrinter& printer )
}
}
bool PDFGenerator::requestPixmap( int id, KPDFPage * page, int width, int height, bool syncronous )
void PDFGenerator::requestPixmap( PixmapRequest * request, bool syncronous )
{
//kdDebug() << "id: " << id << " is requesting pixmap for page " << page->number() << " [" << width << " x " << height << "]." << endl;
if ( syncronous )
{
// in-place Pixmap generation for syncronous requests
if ( !page->hasPixmap( id, width, height ) )
{
// compute dpi used to get an image with desired width and height
double fakeDpiX = width * 72.0 / page->width(),
fakeDpiY = height * 72.0 / page->height();
// 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 = !page->hasSearchPage() && (width == page->width()) && (height == page->height());
// generate links and image rects if rendering pages on pageview
bool genRects = id == PAGEVIEW_ID;
kpdfOutputDev->setParams( width, height, genTextPage, genRects, genRects );
docLock.lock();
pdfdoc->displayPage( kpdfOutputDev, page->number() + 1, fakeDpiX, fakeDpiY, 0, true, genRects );
docLock.unlock();
page->setPixmap( id, kpdfOutputDev->takePixmap() );
if ( genTextPage )
page->setSearchPage( kpdfOutputDev->takeTextPage() );
if ( genRects )
page->setRects( kpdfOutputDev->takeRects() );
// pixmap generated
return true;
}
KPDFPage * page = request->page;
if ( page->hasPixmap( request->id, request->width, request->height ) )
return;
// compute dpi used to get an image with desired width and height
double fakeDpiX = request->width * 72.0 / page->width(),
fakeDpiY = request->height * 72.0 / page->height();
// 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 = !page->hasSearchPage() && (request->width == page->width()) &&
(request->height == page->height());
// generate links and image rects if rendering pages on pageview
bool genRects = request->id == PAGEVIEW_ID;
kpdfOutputDev->setParams( request->width, request->height, genTextPage, genRects, genRects );
docLock.lock();
pdfdoc->displayPage( kpdfOutputDev, page->number() + 1, fakeDpiX, fakeDpiY, 0, true, genRects );
docLock.unlock();
page->setPixmap( request->id, kpdfOutputDev->takePixmap() );
if ( genTextPage )
page->setSearchPage( kpdfOutputDev->takeTextPage() );
if ( genRects )
page->setRects( kpdfOutputDev->takeRects() );
// pixmap generated
contentsChanged( request->id, request->pageNumber );
}
else
{
//TODO asyncronous events queuing
return false;
// TODO add the pixmaprequest in a queue ...
}
// no pixmap generated
return false;
}
void PDFGenerator::requestTextPage( KPDFPage * page )
......
......@@ -52,7 +52,7 @@ class PDFGenerator : public Generator
// [INHERITED] perform actions on document / pages
bool print( KPrinter& printer );
bool requestPixmap( int id, KPDFPage * page, int width, int height, bool syncronous = false );
void requestPixmap( PixmapRequest * request, bool syncronous = false );
void requestTextPage( KPDFPage * page );
// [INHERITED] reparse configuration
......@@ -73,6 +73,8 @@ class PDFGenerator : public Generator
PDFDoc * pdfdoc;
KPDFOutputDev * kpdfOutputDev;
QColor paperColor;
//PixmapRequest * currentRequest;
//QValueList< PixmapRequest * > requests;
bool docInfoDirty;
DocumentInfo docInfo;
bool docSynopsisDirty;
......
......@@ -40,6 +40,7 @@
#include "pageviewutils.h"
#include "page.h"
#include "link.h"
#include "generator.h"
#include "settings.h"
#define ROUND(x) (int(x + 0.5))
......@@ -1311,22 +1312,22 @@ void PageView::slotRequestVisiblePixmaps( int newLeft, int newTop )
// for each item, check if it intersects the viewport
d->visibleItems.clear();
QValueList< PixmapRequest * > requestedPixmaps;
QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
for ( ; iIt != iEnd; ++iIt )
{
PageViewItem * item = *iIt;
if ( viewportRect.intersects( item->geometry() ) )
d->visibleItems.push_back( item );
PageViewItem * i = *iIt;
if ( !viewportRect.intersects( i->geometry() ) )
continue;
d->visibleItems.push_back( i );
if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) )
requestedPixmaps.push_back( new PixmapRequest( PAGEVIEW_ID, i->pageNumber(), i->width(), i->height() ) );
}
// actually request pixmaps
QValueList< PageViewItem * >::iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end();
for ( ; vIt != vEnd; ++vIt )
{
PageViewItem * item = *vIt;
if ( !item->page()->hasPixmap( PAGEVIEW_ID, item->width(), item->height() ) )
d->document->requestPixmap( PAGEVIEW_ID, item->pageNumber(), item->width(), item->height(), true );
}
if ( !requestedPixmaps.isEmpty() )
d->document->requestPixmaps( requestedPixmaps, true );
}
void PageView::slotAutoScoll()
......
......@@ -25,6 +25,7 @@
// local includes
#include "presentationwidget.h"
#include "document.h" // for PRESENTATION_ID
#include "generator.h"
#include "page.h"
......@@ -113,7 +114,7 @@ void PresentationWidget::pageSetup( const QValueVector<KPDFPage*> & pageSet, boo
{
PresentationFrame * frame = new PresentationFrame();
frame->page = *setIt;
frame->transType = Glitter; //TODO get transition from the document
frame->transType = NoTrans; //TODO get transition from the document
frame->transDir = Left;
// calculate frame geometry keeping constant aspect ratio
float pageRatio = frame->page->ratio();
......@@ -144,6 +145,12 @@ void PresentationWidget::pageSetup( const QValueVector<KPDFPage*> & pageSet, boo
m_metaStrings += i18n( "Click to begin" );
}
bool PresentationWidget::canUnloadPixmap( int pageNumber )
{
// can unload all pixmaps except for the currently visible one
return pageNumber != m_frameIndex;
}
void PresentationWidget::notifyPixmapChanged( int pageNumber )
{
// check if it's the last requested pixmap. if so update the widget.
......@@ -462,7 +469,11 @@ void PresentationWidget::slotNextPage()
// if pixmap not inside the KPDFPage we request it and wait for
// notifyPixmapChanged call or else we proceed to pixmap generation
if ( !frame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) )
m_document->requestPixmap( PRESENTATION_ID, m_frameIndex, pixW, pixH, true );
{
QValueList< PixmapRequest * > request;
request.push_back( new PixmapRequest( PRESENTATION_ID, m_frameIndex, pixW, pixH ) );
m_document->requestPixmaps( request, true );
}
else
generatePage();
}
......@@ -487,7 +498,11 @@ void PresentationWidget::slotPrevPage()
// if pixmap not inside the KPDFPage we request it and wait for
// notifyPixmapChanged call or else we can proceed to pixmap generation
if ( !frame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) )
m_document->requestPixmap( PRESENTATION_ID, m_frameIndex, pixW, pixH, true );
{
QValueList< PixmapRequest * > request;
request.push_back( new PixmapRequest( PRESENTATION_ID, m_frameIndex, pixW, pixH ) );
m_document->requestPixmaps( request, true );
}
else
generatePage();
}
......
......@@ -40,6 +40,7 @@ class PresentationWidget : public QWidget, public KPDFDocumentObserver
// inherited from KPDFDocumentObserver
uint observerId() const { return PRESENTATION_ID; }
void pageSetup( const QValueVector<KPDFPage*> & pages, bool documentChanged );
bool canUnloadPixmap( int pageNumber );
void notifyPixmapChanged( int pageNumber );
protected:
......
......@@ -16,6 +16,7 @@
#include <kactioncollection.h>
#include "thumbnaillist.h"
#include "generator.h"
#include "page.h"
// ThumbnailWidget represents a single thumbnail in the ThumbnailList
......@@ -76,29 +77,12 @@ ThumbnailList::ThumbnailList( QWidget *parent, KPDFDocument *document )
//BEGIN KPDFDocumentObserver inherited methods
void ThumbnailList::notifyPixmapChanged( int pageNumber )
{
QValueVector<ThumbnailWidget *>::iterator thumbIt = m_thumbnails.begin(), thumbEnd = m_thumbnails.end();
for (; thumbIt != thumbEnd; ++thumbIt)
if ( (*thumbIt)->pageNumber() == pageNumber )
{
(*thumbIt)->update();
break;
}
}
void ThumbnailList::notifyPixmapsCleared()
{
slotRequestPixmaps();
}
void ThumbnailList::pageSetup( const QValueVector<KPDFPage*> & pages, bool /*documentChanged*/ )
{
// delete all the Thumbnails
QValueVector<ThumbnailWidget *>::iterator thumbIt = m_thumbnails.begin();
QValueVector<ThumbnailWidget *>::iterator thumbEnd = m_thumbnails.end();
for ( ; thumbIt != thumbEnd; ++thumbIt )
delete *thumbIt;
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
delete *tIt;
m_thumbnails.clear();
m_selected = 0;
......@@ -118,10 +102,10 @@ void ThumbnailList::pageSetup( const QValueVector<KPDFPage*> & pages, bool /*doc
ThumbnailWidget *t;
int width = clipper()->width(),
totalHeight = 0;
QValueVector<KPDFPage*>::const_iterator pageIt = pages.begin();
QValueVector<KPDFPage*>::const_iterator pageEnd = pages.end();
QValueVector<KPDFPage*>::const_iterator pageIt = pages.begin(), pageEnd = pages.end();
for (; pageIt != pageEnd ; ++pageIt)
if ( skipCheck || ( (*pageIt)->attributes() & KPDFPage::Highlight ) ) {
if ( skipCheck || ( (*pageIt)->attributes() & KPDFPage::Highlight ) )
{
t = new ThumbnailWidget( viewport(), *pageIt );
t->setFocusProxy( this );
// add to the scrollview
......@@ -150,13 +134,12 @@ void ThumbnailList::pageSetCurrent( int pageNumber, const QRect & /*viewport*/ )
// select next page
m_vectorIndex = 0;
QValueVector<ThumbnailWidget *>::iterator thumbIt = m_thumbnails.begin();
QValueVector<ThumbnailWidget *>::iterator thumbEnd = m_thumbnails.end();
for (; thumbIt != thumbEnd; ++thumbIt)
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
{
if ( (*thumbIt)->pageNumber() == pageNumber )
if ( (*tIt)->pageNumber() == pageNumber )
{
m_selected = *thumbIt;
m_selected = *tIt;
m_selected->setSelected( true );
ensureVisible( 0, childY( m_selected ) + m_selected->height()/2, 0, visibleHeight()/2 );
//non-centered version: ensureVisible( 0, itemTop + itemHeight/2, 0, itemHeight/2 );
......@@ -166,23 +149,49 @@ void ThumbnailList::pageSetCurrent( int pageNumber, const QRect & /*viewport*/ )
}
}
bool ThumbnailList::canUnloadPixmap( int pageNumber )
{
// if the thubnail 'pageNumber' is one of the visible ones, forbid unloading
QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
for ( ; vIt != vEnd; ++vIt )
if ( (*vIt)->pageNumber() == pageNumber )
return false;
// if hidden permit unloading
return true;
}
void ThumbnailList::notifyPixmapChanged( int pageNumber )
{
QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
for ( ; vIt != vEnd; ++vIt )
if ( (*vIt)->pageNumber() == pageNumber )
{
(*vIt)->update();
break;
}