/*************************************************************************** * Copyright (C) 2004 by Albert Astals Cid * * * * 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 #include #include #include #include #include #include #include // 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::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::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::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::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::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::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::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::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"