okular/ui/thumbnaillist.cpp
Enrico Ros e5eb63ec71 Redesigned graphic pipeline on PagePainter and hand scaling (10x gain on
typical usage).

PagePainter:
  Added 2 convenience methods for performing fast cropping and scaling
  from qpixmaps to qimages. Split buffered graphic flow from the unbuffered
  one reducing buffers conversions to the minimum. Avoid odd cases in
  rescaling. Detection of annotations boundaries to check for enabling
  buffered painting.
  Sample quick dirty and broken (but not so much..) implementation for
  painting AStamp annotations.
Misc:
  PresentationWidged and ThumbnailWidget: adapted to PagePainter changes.
  (PageView mod has already been committed).

svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=400310
2005-03-24 19:59:11 +00:00

556 lines
18 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. *
***************************************************************************/
// qt/kde includes
#include <qtimer.h>
#include <qpainter.h>
#include <klocale.h>
#include <kurl.h>
#include <kurldrag.h>
#include <kaction.h>
#include <kiconloader.h>
#include <kactioncollection.h>
// local includes
#include "thumbnaillist.h"
#include "pagepainter.h"
#include "searchwidget.h" // for SW_SEARCH_ID
#include "core/document.h"
#include "core/generator.h"
#include "core/page.h"
#include "conf/settings.h"
// ThumbnailWidget represents a single thumbnail in the ThumbnailList
class ThumbnailWidget : public QWidget
{
public:
ThumbnailWidget( QWidget * parent, const KPDFPage * page, ThumbnailList * tl );
// 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 mouseReleaseEvent( QMouseEvent * e );
void paintEvent(QPaintEvent *);
private:
// used to access 'forwardRightClick( .. )' and 'getBookmarkOverlay()'
ThumbnailList * m_tl;
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 ), m_bookmarkOverlay( 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(slotRequestVisiblePixmaps(int, int)) );
}
ThumbnailList::~ThumbnailList()
{
delete m_bookmarkOverlay;
}
//BEGIN DocumentObserver inherited methods
void ThumbnailList::notifySetup( 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_visibleThumbnails.clear();
m_selected = 0;
if ( pages.count() < 1 )
{
resizeContents( 0, 0 );
return;
}
// show pages containing hilighted text or bookmarked ones
//RESTORE THIS int flags = Settings::filterBookmarks() ? KPDFPage::Bookmark : KPDFPage::Highlight;
// if no page matches filter rule, then display all pages
QValueVector< KPDFPage * >::const_iterator pIt = pages.begin(), pEnd = pages.end();
bool skipCheck = true;
for ( ; pIt != pEnd ; ++pIt )
//if ( (*pIt)->attributes() & flags )
if ( (*pIt)->hasHighlights( SW_SEARCH_ID ) )
skipCheck = false;
// generate Thumbnails for the given set of pages
int width = clipper()->width(),
totalHeight = 0;
for ( pIt = pages.begin(); pIt != pEnd ; ++pIt )
//if ( skipCheck || (*pIt)->attributes() & flags )
if ( skipCheck || (*pIt)->hasHighlights( SW_SEARCH_ID ) )
{
ThumbnailWidget * t = new ThumbnailWidget( viewport(), *pIt, this );
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
delayedRequestVisiblePixmaps( 200 );
}
void ThumbnailList::notifyViewportChanged( bool /*smoothMove*/ )
{
// skip notifies for the current page (already selected)
int newPage = m_document->viewport().pageNumber;
if ( m_selected && m_selected->pageNumber() == newPage )
return;
// deselect previous thumbnail
if ( m_selected )
m_selected->setSelected( false );
m_selected = 0;
// select the page with viewport and ensure it's centered in the view
m_vectorIndex = 0;
QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
for ( ; tIt != tEnd; ++tIt )
{
if ( (*tIt)->pageNumber() == newPage )
{
m_selected = *tIt;
m_selected->setSelected( true );
if ( Settings::syncThumbnailsViewport() )
{
int yOffset = QMAX( visibleHeight() / 4, m_selected->height() / 2 );
ensureVisible( 0, childY( m_selected ) + m_selected->height()/2, 0, yOffset );
}
break;
}
m_vectorIndex++;
}
}
void ThumbnailList::notifyPageChanged( int pageNumber, int /*changedFlags*/ )
{
// only handle pixmap changed notifies (the only defined for now)
//if ( !(changedFlags & DocumentObserver::Pixmap) )
// return;
// iterate over visible items: if page(pageNumber) is one of them, repaint it
QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
for ( ; vIt != vEnd; ++vIt )
if ( (*vIt)->pageNumber() == pageNumber )
{
(*vIt)->update();
break;
}
}
void ThumbnailList::notifyContentsCleared( int changedFlags )
{
// if pixmaps were cleared, re-ask them
if ( changedFlags & DocumentObserver::Pixmap )
slotRequestVisiblePixmaps();
}
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;
}
//END DocumentObserver inherited methods
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::forwardRightClick( const KPDFPage * p, const QPoint & t )
{
emit rightClick( p, t );
}
const QPixmap * ThumbnailList::getBookmarkOverlay() const
{
return m_bookmarkOverlay;
}
void ThumbnailList::slotFilterBookmarks( bool filterOn )
{
// save state
Settings::setFilterBookmarks( filterOn );
// ask for the 'notifySetup' with a little trick (on reinsertion the
// document sends the list again)
m_document->removeObserver( this );
m_document->addObserver( this );
}
//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_PageUp )
verticalScrollBar()->subtractPage();
else if ( keyEvent->key() == Key_PageDown )
verticalScrollBar()->addPage();
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->setViewportPage( 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()) )
{
if ( m_document->viewport().pageNumber != t->pageNumber() )
m_document->setViewportPage( 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'
delayedRequestVisiblePixmaps( 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;
// invalidate the bookmark overlay
if ( m_bookmarkOverlay )
{
delete m_bookmarkOverlay;
m_bookmarkOverlay = 0;
}
// update Thumbnails since width has changed or height has increased
delayedRequestVisiblePixmaps( 500 );
}
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 widget events
//BEGIN internal SLOTS
void ThumbnailList::slotRequestVisiblePixmaps( 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() ) )
{
PixmapRequest * p = new PixmapRequest(
THUMBNAILS_ID, t->pageNumber(), t->pixmapWidth(), t->pixmapHeight(), THUMBNAILS_PRIO, true );
requestedPixmaps.push_back( p );
}
}
// actually request pixmaps
if ( !requestedPixmaps.isEmpty() )
m_document->requestPixmaps( requestedPixmaps );
}
void ThumbnailList::slotDelayTimeout()
{
// resize the bookmark overlay
delete m_bookmarkOverlay;
int expectedWidth = contentsWidth() / 4;
if ( expectedWidth > 10 )
m_bookmarkOverlay = new QPixmap( DesktopIcon( "attach", expectedWidth ) );
else
m_bookmarkOverlay = 0;
// request pixmaps
slotRequestVisiblePixmaps();
}
//END internal SLOTS
void ThumbnailList::delayedRequestVisiblePixmaps( int delayMs )
{
if ( !m_delayTimer )
{
m_delayTimer = new QTimer( this );
connect( m_delayTimer, SIGNAL( timeout() ), this, SLOT( slotDelayTimeout() ) );
}
m_delayTimer->start( delayMs, true );
}
/** ThumbnailWidget implementation **/
ThumbnailWidget::ThumbnailWidget( QWidget * parent, const KPDFPage * kp, ThumbnailList * tl )
: QWidget( parent, 0, WNoAutoErase ), m_tl( tl ), 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::mouseReleaseEvent( QMouseEvent * e )
{
if ( e->button() != Qt::RightButton )
return;
m_tl->forwardRightClick( m_page, e->globalPos() );
}
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->hasBookmark();
// 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::Highlights |
PagePainter::Annotations;
PagePainter::paintPageOnPainter( m_page, THUMBNAILS_ID, flags, &p,
m_pixmapWidth, m_pixmapHeight, clipRect );
}
// draw the bookmark overlay on the top-right corner
const QPixmap * bookmarkPixmap = m_tl->getBookmarkOverlay();
if ( isBookmarked && bookmarkPixmap )
{
int pixW = bookmarkPixmap->width(),
pixH = bookmarkPixmap->height();
clipRect = clipRect.intersect( QRect( m_pixmapWidth - pixW, 0, pixW, pixH ) );
if ( clipRect.isValid() )
p.drawPixmap( m_pixmapWidth - pixW, -pixH/8, *bookmarkPixmap );
}
}
}
/** ThumbnailsController implementation **/
#define FILTERB_ID 1
ThumbnailController::ThumbnailController( QWidget * parent, ThumbnailList * list )
: KToolBar( parent, "ThumbsControlBar" )
{
// change toolbar appearance
setMargin( 3 );
setFlat( true );
setIconSize( 16 );
setMovingEnabled( false );
// insert a togglebutton [show only bookmarked pages]
//insertSeparator();
insertButton( "bookmark", FILTERB_ID, SIGNAL( toggled( bool ) ),
list, SLOT( slotFilterBookmarks( bool ) ),
true, i18n( "Show bookmarked pages only" ) );
setToggle( FILTERB_ID );
setButton( FILTERB_ID, Settings::filterBookmarks() );
//insertLineSeparator();
}
#include "thumbnaillist.moc"