okular/kpdf/thumbnaillist.cpp
Enrico Ros 160d6a4f7e Async generation fixes. I hope all segfaults and mutex overlocking have
been fixed. If someone finds one of those, please tell me.
Here are thread generator pros/cons:
- generation is ~20% slower
+ thumbnaillist or pageview scrolling is fast (never blocked anymore)
+ all UI feels so better and responsive
So:
o thumbnaillist will be all ASYNC.
o pageview will use ASYNC for _preloading_ next pages and a mixed A/S for
  scrolling/viewing the document
p previewwidget and others (searches, etc) are SYNC as obvious.
Btw now async generation is enabled everywhere.

svn path=/branches/kpdf_experiments/kdegraphics/kpdf/; revision=373810
2004-12-28 18:50:11 +00:00

424 lines
13 KiB
C++

/***************************************************************************
* Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es> *
* *
* 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. *
***************************************************************************/
#include <qtimer.h>
#include <qpainter.h>
#include <klocale.h>
#include <kurl.h>
#include <kurldrag.h>
#include <kaction.h>
#include <kactioncollection.h>
#include "thumbnaillist.h"
#include "generator.h"
#include "page.h"
// ThumbnailWidget represents a single thumbnail in the ThumbnailList
class ThumbnailWidget : public QWidget
{
public:
ThumbnailWidget( QWidget * parent, const KPDFPage * page );
// set internal parameters to fit the page in the given width
void resizeFitWidth( int width );
// set thumbnail's selected state
void setSelected( bool selected );
// query methods
int heightHint() const { return m_pixmapHeight + m_labelHeight + 4; }
int pixmapWidth() const { return m_pixmapWidth; }
int pixmapHeight() const { return m_pixmapHeight; }
int pageNumber() const { return m_page->number(); }
const KPDFPage * page() const { return m_page; }
protected:
void paintEvent(QPaintEvent *);
private:
const KPDFPage * m_page;
bool m_selected;
int m_pixmapWidth, m_pixmapHeight;
int m_labelHeight, m_labelNumber;
};
/** ThumbnailList implementation **/
ThumbnailList::ThumbnailList( QWidget *parent, KPDFDocument *document )
: QScrollView( parent, "KPDF::Thumbnails", WNoAutoErase | WStaticContents ),
m_document( document ), m_selected( 0 ), m_delayTimer( 0 )
{
// set scrollbars
setHScrollBarMode( QScrollView::AlwaysOff );
setVScrollBarMode( QScrollView::AlwaysOn );
// dealing with large areas so enable clipper
enableClipper( true );
// widget setup: can be focused by tab and mouse click (not wheel)
viewport()->setFocusProxy( this );
viewport()->setFocusPolicy( StrongFocus );
setResizePolicy( Manual );
setAcceptDrops( true );
setDragAutoScroll( false );
// set contents background to the 'base' color
viewport()->setPaletteBackgroundColor( palette().active().base() );
setFrameStyle( StyledPanel | Raised );
connect( this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotRequestPixmaps(int, int)) );
}
//BEGIN KPDFDocumentObserver inherited methods
void ThumbnailList::pageSetup( const QValueVector<KPDFPage*> & pages, bool /*documentChanged*/ )
{
// delete all the Thumbnails
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
delete *tIt;
m_thumbnails.clear();
m_selected = 0;
if ( pages.count() < 1 )
{
resizeContents( 0, 0 );
return;
}
//FIXME change this quick fix (lines that follows). Check if filtering:
bool skipCheck = true;
for ( uint i = 0; i < pages.count(); i++ )
if ( pages[i]->attributes() & KPDFPage::Highlight )
skipCheck = false;
// generate Thumbnails for the given set of pages
ThumbnailWidget *t;
int width = clipper()->width(),
totalHeight = 0;
QValueVector<KPDFPage*>::const_iterator pageIt = pages.begin(), pageEnd = pages.end();
for (; pageIt != pageEnd ; ++pageIt)
if ( skipCheck || ( (*pageIt)->attributes() & KPDFPage::Highlight ) )
{
t = new ThumbnailWidget( viewport(), *pageIt );
t->setFocusProxy( this );
// add to the scrollview
addChild( t, 0, totalHeight );
// add to the internal queue
m_thumbnails.push_back( t );
// update total height (asking widget its own height)
t->resizeFitWidth( width );
totalHeight += t->heightHint() + 4;
t->show();
}
// update scrollview's contents size (sets scrollbars limits)
resizeContents( width, totalHeight );
// request for thumbnail generation
requestPixmaps( 200 );
}
void ThumbnailList::pageSetCurrent( int pageNumber, const QRect & /*viewport*/ )
{
// deselect previous thumbnail
if ( m_selected )
m_selected->setSelected( false );
m_selected = 0;
// select next page
m_vectorIndex = 0;
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
{
if ( (*tIt)->pageNumber() == pageNumber )
{
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 );
break;
}
m_vectorIndex++;
}
}
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;
}
}
void ThumbnailList::notifyPixmapsCleared()
{
slotRequestPixmaps();
}
void ThumbnailList::updateWidgets()
{
// find all widgets that intersects the viewport and update them
QRect viewportRect( contentsX(), contentsY(), visibleWidth(), visibleHeight() );
QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
for ( ; vIt != vEnd; ++vIt )
{
ThumbnailWidget * t = *vIt;
QRect widgetRect( childX( t ), childY( t ), t->width(), t->height() );
// update only the exposed area of the widget (saves pixels..)
QRect relativeRect = viewportRect.intersect( widgetRect );
if ( !relativeRect.isValid() )
continue;
relativeRect.moveBy( -widgetRect.left(), -widgetRect.top() );
t->update( relativeRect );
}
}
void ThumbnailList::dragEnterEvent( QDragEnterEvent * ev )
{
ev->accept();
}
void ThumbnailList::dropEvent( QDropEvent * ev )
{
KURL::List lst;
if ( KURLDrag::decode( ev, lst ) )
emit urlDropped( lst.first() );
}
//END KPDFDocumentObserver inherited methods
//BEGIN widget events
void ThumbnailList::keyPressEvent( QKeyEvent * keyEvent )
{
if ( m_thumbnails.count() < 1 )
return keyEvent->ignore();
int nextPage = -1;
if ( keyEvent->key() == Key_Up )
{
if ( !m_selected )
nextPage = 0;
else if ( m_vectorIndex > 0 )
nextPage = m_thumbnails[ m_vectorIndex - 1 ]->pageNumber();
}
else if ( keyEvent->key() == Key_Down )
{
if ( !m_selected )
nextPage = 0;
else if ( m_vectorIndex < (int)m_thumbnails.count() - 1 )
nextPage = m_thumbnails[ m_vectorIndex + 1 ]->pageNumber();
}
else if ( keyEvent->key() == Key_Home )
nextPage = m_thumbnails[ 0 ]->pageNumber();
else if ( keyEvent->key() == Key_End )
nextPage = m_thumbnails[ m_thumbnails.count() - 1 ]->pageNumber();
if ( nextPage == -1 )
return keyEvent->ignore();
keyEvent->accept();
if ( m_selected )
m_selected->setSelected( false );
m_selected = 0;
m_document->setCurrentPage( nextPage );
}
void ThumbnailList::contentsMousePressEvent( QMouseEvent * e )
{
if ( e->button() != Qt::LeftButton )
return;
int clickY = e->y();
QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
for ( ; vIt != vEnd; ++vIt )
{
ThumbnailWidget * t = *vIt;
int childTop = childY(t);
if ( clickY > childTop && clickY < (childTop + t->height()) )
{
m_document->setCurrentPage( t->pageNumber() );
break;
}
}
}
void ThumbnailList::viewportResizeEvent( QResizeEvent * e )
{
if ( m_thumbnails.count() < 1 || width() < 1 )
return;
// if width changed resize all the Thumbnails, reposition them to the
// right place and recalculate the contents area
if ( e->size().width() != e->oldSize().width() )
{
// runs the timer avoiding a thumbnail regeneration by 'contentsMoving'
requestPixmaps( 2000 );
// resize and reposition items
int totalHeight = 0,
newWidth = e->size().width();
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
{
ThumbnailWidget *t = *tIt;
moveChild( t, 0, totalHeight );
t->resizeFitWidth( newWidth );
totalHeight += t->heightHint() + 4;
}
// update scrollview's contents size (sets scrollbars limits)
resizeContents( newWidth, totalHeight );
// ensure selected item remains visible
if ( m_selected )
ensureVisible( 0, childY( m_selected ) + m_selected->height()/2, 0, visibleHeight()/2 );
}
else if ( e->size().height() <= e->oldSize().height() )
return;
// update Thumbnails since width has changed or height has increased
requestPixmaps( 500 );
}
//END widget events
//BEGIN internal SLOTS
void ThumbnailList::slotRequestPixmaps( int /*newContentsX*/, int newContentsY )
{
// if an update is already scheduled or the widget is hidden, don't proceed
if ( (m_delayTimer && m_delayTimer->isActive()) || !isShown() )
return;
int vHeight = visibleHeight(),
vOffset = newContentsY == -1 ? contentsY() : newContentsY;
// scroll from the top to the last visible thumbnail
m_visibleThumbnails.clear();
QValueList< PixmapRequest * > requestedPixmaps;
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
{
ThumbnailWidget * t = *tIt;
int top = childY( t ) - vOffset;
if ( top > vHeight )
break;
if ( top + t->height() < 0 )
continue;
// add ThumbnailWidget to visible list
m_visibleThumbnails.push_back( t );
// if pixmap not present add it to requests
if ( !t->page()->hasPixmap( THUMBNAILS_ID, t->pixmapWidth(), t->pixmapHeight() ) )
requestedPixmaps.push_back( new PixmapRequest( THUMBNAILS_ID, t->pageNumber(), t->pixmapWidth(), t->pixmapHeight() ) );
}
// actually request pixmaps
if ( !requestedPixmaps.isEmpty() )
m_document->requestPixmaps( requestedPixmaps, true /*ASYNC*/ );
}
//END internal SLOTS
void ThumbnailList::requestPixmaps( int delayMs )
{
if ( !m_delayTimer )
{
m_delayTimer = new QTimer( this );
connect( m_delayTimer, SIGNAL( timeout() ), this, SLOT( slotRequestPixmaps() ) );
}
m_delayTimer->start( delayMs, true );
}
/** ThumbnailWidget implementation **/
ThumbnailWidget::ThumbnailWidget( QWidget * parent, const KPDFPage * kp )
: QWidget( parent, 0, WNoAutoErase ), m_page( kp ),
m_selected( false ), m_pixmapWidth( 10 ), m_pixmapHeight( 10 )
{
m_labelNumber = m_page->number() + 1;
m_labelHeight = QFontMetrics( font() ).height();
}
void ThumbnailWidget::resizeFitWidth( int width )
{
m_pixmapWidth = width - 4;
m_pixmapHeight = (int)(m_page->ratio() * m_pixmapWidth);
resize( width, heightHint() );
}
void ThumbnailWidget::setSelected( bool selected )
{
// update selected state
if ( m_selected != selected )
{
m_selected = selected;
update( 0, m_pixmapHeight + 4, width(), m_labelHeight );
}
}
void ThumbnailWidget::paintEvent( QPaintEvent * e )
{
int width = m_pixmapWidth + 4;
QRect clipRect = e->rect();
if ( !clipRect.isValid() )
return;
QPainter p( this );
// draw the bottom label
if ( clipRect.bottom() > m_pixmapHeight + 3 )
{
QColor fillColor = m_selected ? palette().active().highlight() : palette().active().base();
p.fillRect( 0, m_pixmapHeight + 4, width, m_labelHeight, fillColor );
p.drawText( 0, m_pixmapHeight + 4, width, m_labelHeight, Qt::AlignCenter, QString::number( m_labelNumber ) );
}
// draw page outline and pixmap
if ( clipRect.top() < m_pixmapHeight + 4 )
{
// if page is bookmarked draw a colored border
bool isBookmarked = m_page->attributes() & KPDFPage::Bookmark;
// draw the inner rect
p.setPen( isBookmarked ? QColor( 0xFF8000 ) : Qt::black );
p.drawRect( 1, 1, m_pixmapWidth + 2, m_pixmapHeight + 2 );
// draw the clear rect
p.setPen( isBookmarked ? QColor( 0x804000 ) : palette().active().base() );
p.drawRect( 0, 0, m_pixmapWidth + 4, m_pixmapHeight + 4 );
// draw the bottom and right shadow edges
if ( !isBookmarked )
{
p.setPen( Qt::gray );
p.drawLine( 5, m_pixmapHeight + 3, m_pixmapWidth + 3, m_pixmapHeight + 3 );
p.drawLine( m_pixmapWidth + 3, 5, m_pixmapWidth + 3, m_pixmapHeight + 3 );
}
// draw the page using the shared PagePainter class
p.translate( 2, 2 );
clipRect.moveBy( -2, -2 );
clipRect = clipRect.intersect( QRect( 0, 0, m_pixmapWidth, m_pixmapHeight ) );
if ( clipRect.isValid() )
{
int flags = PagePainter::Accessibility | PagePainter::Highlight;
PagePainter::paintPageOnPainter( m_page, THUMBNAILS_ID, flags, &p,
clipRect, m_pixmapWidth, m_pixmapHeight );
}
}
}
#include "thumbnaillist.moc"