diff options
Diffstat (limited to 'kpdf/ui')
-rw-r--r-- | kpdf/ui/Makefile.am | 17 | ||||
-rw-r--r-- | kpdf/ui/minibar.cpp | 436 | ||||
-rw-r--r-- | kpdf/ui/minibar.h | 61 | ||||
-rw-r--r-- | kpdf/ui/pagepainter.cpp | 252 | ||||
-rw-r--r-- | kpdf/ui/pagepainter.h | 36 | ||||
-rw-r--r-- | kpdf/ui/pageview.cpp | 2114 | ||||
-rw-r--r-- | kpdf/ui/pageview.h | 148 | ||||
-rw-r--r-- | kpdf/ui/pageviewutils.cpp | 207 | ||||
-rw-r--r-- | kpdf/ui/pageviewutils.h | 72 | ||||
-rw-r--r-- | kpdf/ui/presentationwidget.cpp | 1330 | ||||
-rw-r--r-- | kpdf/ui/presentationwidget.h | 108 | ||||
-rw-r--r-- | kpdf/ui/propertiesdialog.cpp | 94 | ||||
-rw-r--r-- | kpdf/ui/propertiesdialog.h | 23 | ||||
-rw-r--r-- | kpdf/ui/searchwidget.cpp | 135 | ||||
-rw-r--r-- | kpdf/ui/searchwidget.h | 50 | ||||
-rw-r--r-- | kpdf/ui/thumbnaillist.cpp | 575 | ||||
-rw-r--r-- | kpdf/ui/thumbnaillist.h | 121 | ||||
-rw-r--r-- | kpdf/ui/toc.cpp | 175 | ||||
-rw-r--r-- | kpdf/ui/toc.h | 43 |
19 files changed, 5997 insertions, 0 deletions
diff --git a/kpdf/ui/Makefile.am b/kpdf/ui/Makefile.am new file mode 100644 index 00000000..16a4ad50 --- /dev/null +++ b/kpdf/ui/Makefile.am @@ -0,0 +1,17 @@ +INCLUDES = -I$(srcdir)/.. -I$(top_builddir)/kpdf $(all_includes) + +METASOURCES = AUTO + +libkpdfui_la_SOURCES = pagepainter.cpp pageview.cpp pageviewutils.cpp \ + minibar.cpp thumbnaillist.cpp searchwidget.cpp \ + toc.cpp propertiesdialog.cpp presentationwidget.cpp + +noinst_LTLIBRARIES = libkpdfui.la + +pagepainter.lo: ../conf/settings.h +pageview.lo: ../conf/settings.h +pageviewutils.lo: ../conf/settings.h +presentationwidget.lo: ../conf/settings.h +searchwidget.lo: ../conf/settings.h +thumbnaillist.lo: ../conf/settings.h + diff --git a/kpdf/ui/minibar.cpp b/kpdf/ui/minibar.cpp new file mode 100644 index 00000000..1a259add --- /dev/null +++ b/kpdf/ui/minibar.cpp @@ -0,0 +1,436 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> * + * Copyright (C) 2006 by Albert Astals Cid <aacid@kde.org> * + * * + * 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 <qapplication.h> +#include <qpushbutton.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qlayout.h> +#include <qvalidator.h> +#include <qpainter.h> +#include <kiconloader.h> +#include <kaccelmanager.h> +#include <kdeversion.h> + +// local includes +#include "core/document.h" +#include "minibar.h" + +// [private widget] show progress +class ProgressWidget : public QWidget +{ + public: + ProgressWidget( MiniBar * parent ); + void setProgress( float percentage ); + + protected: + void mouseMoveEvent( QMouseEvent * e ); + void mousePressEvent( QMouseEvent * e ); + void wheelEvent( QWheelEvent * e ); + void paintEvent( QPaintEvent * e ); + + private: + MiniBar * m_miniBar; + float m_progressPercentage; +}; + +// [private widget] lineEdit for entering/validating page numbers +class PagesEdit : public QLineEdit +{ + public: + PagesEdit( MiniBar * parent ); + void setPagesNumber( int pages ); + void setText( const QString & ); + + protected: + void focusInEvent( QFocusEvent * e ); + void focusOutEvent( QFocusEvent * e ); + void mousePressEvent( QMouseEvent * e ); + void wheelEvent( QWheelEvent * e ); + + private: + MiniBar * m_miniBar; + bool m_eatClick; + QString backString; + QIntValidator * m_validator; +}; + +// [private widget] a flat qpushbutton that enlights on hover +class HoverButton : public QPushButton +{ + public: + HoverButton( QWidget * parent ); + + protected: + void paintEvent( QPaintEvent * e ); + void enterEvent( QPaintEvent * e ); + void leaveEvent( QPaintEvent * e ); +}; + + +/** MiniBar **/ + +MiniBar::MiniBar( QWidget * parent, KPDFDocument * document ) + : QFrame( parent, "miniBar" ), m_document( document ), + m_currentPage( -1 ) +{ + // left spacer + QHBoxLayout * horLayout = new QHBoxLayout( this ); + QSpacerItem * spacerL = new QSpacerItem( 20, 10, QSizePolicy::Expanding ); + horLayout->addItem( spacerL ); + + // central 2r by 3c grid layout that contains all components + QGridLayout * gridLayout = new QGridLayout( 0, 3,5, 2,1 ); + // top spacer 6x6 px +// QSpacerItem * spacerTop = new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ); +// gridLayout->addMultiCell( spacerTop, 0, 0, 0, 4 ); + // center progress widget + m_progressWidget = new ProgressWidget( this ); + gridLayout->addMultiCellWidget( m_progressWidget, 0, 0, 0, 4 ); + // bottom: left prev_page button + m_prevButton = new HoverButton( this ); + m_prevButton->setIconSet( SmallIconSet( QApplication::reverseLayout() ? "1rightarrow" : "1leftarrow" ) ); + gridLayout->addWidget( m_prevButton, 1, 0 ); + // bottom: left lineEdit (current page box) + m_pagesEdit = new PagesEdit( this ); + gridLayout->addWidget( m_pagesEdit, 1, 1 ); + // bottom: central '/' label + gridLayout->addWidget( new QLabel( "/", this ), 1, 2 ); + // bottom: right button + m_pagesButton = new HoverButton( this ); + gridLayout->addWidget( m_pagesButton, 1, 3 ); + // bottom: right next_page button + m_nextButton = new HoverButton( this ); + m_nextButton->setIconSet( SmallIconSet( QApplication::reverseLayout() ? "1leftarrow" : "1rightarrow" ) ); + gridLayout->addWidget( m_nextButton, 1, 4 ); + horLayout->addLayout( gridLayout ); + + // right spacer + QSpacerItem * spacerR = new QSpacerItem( 20, 10, QSizePolicy::Expanding ); + horLayout->addItem( spacerR ); + + // customize own look + setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); + + // connect signals from child widgets to internal handlers / signals bouncers + connect( m_pagesEdit, SIGNAL( returnPressed() ), this, SLOT( slotChangePage() ) ); + connect( m_pagesButton, SIGNAL( clicked() ), this, SIGNAL( gotoPage() ) ); + connect( m_prevButton, SIGNAL( clicked() ), this, SIGNAL( prevPage() ) ); + connect( m_nextButton, SIGNAL( clicked() ), this, SIGNAL( nextPage() ) ); + + // widget starts hidden (will be shown after opening a document) + parent->hide(); +} + +MiniBar::~MiniBar() +{ + m_document->removeObserver( this ); +} + +void MiniBar::notifySetup( const QValueVector< KPDFPage * > & pageVector, bool changed ) +{ + // only process data when document changes + if ( !changed ) + return; + + // if document is closed or has no pages, hide widget + int pages = pageVector.count(); + if ( pages < 1 ) + { + m_currentPage = -1; + static_cast<QWidget*>( parent() )->hide(); + return; + } + + // resize width of widgets + int numberWidth = 10 + fontMetrics().width( QString::number( pages ) ); + m_pagesEdit->setMinimumWidth( numberWidth ); + m_pagesEdit->setMaximumWidth( 2 * numberWidth ); + m_pagesButton->setMinimumWidth( numberWidth ); + m_pagesButton->setMaximumWidth( 2 * numberWidth ); + + // resize height of widgets + int fixedHeight = fontMetrics().height() + 2; + if ( fixedHeight < 18 ) + fixedHeight = 18; + m_pagesEdit->setFixedHeight( fixedHeight ); + m_pagesButton->setFixedHeight( fixedHeight ); + m_prevButton->setFixedHeight( fixedHeight ); + m_nextButton->setFixedHeight( fixedHeight ); + + // update child widgets + m_pagesEdit->setPagesNumber( pages ); + m_pagesButton->setText( QString::number( pages ) ); + m_prevButton->setEnabled( false ); + m_nextButton->setEnabled( false ); + static_cast<QWidget*>( parent() )->show(); +} + +void MiniBar::notifyViewportChanged( bool /*smoothMove*/ ) +{ + // get current page number + int page = m_document->viewport().pageNumber; + int pages = m_document->pages(); + + // if the document is opened and page is changed + if ( page != m_currentPage && pages > 0 ) + { + // update percentage + m_currentPage = page; + float percentage = pages < 2 ? 1.0 : (float)page / (float)(pages - 1); + m_progressWidget->setProgress( percentage ); + // update prev/next button state + m_prevButton->setEnabled( page > 0 ); + m_nextButton->setEnabled( page < ( pages - 1 ) ); + // update text on widgets + m_pagesEdit->setText( QString::number( page + 1 ) ); + } +} + +void MiniBar::resizeEvent( QResizeEvent * e ) +{ + // auto-hide 'prev' and 'next' buttons if not enough space + const QSize & myHint = minimumSizeHint(); + bool shown = m_prevButton->isVisible() && m_nextButton->isVisible(); + if ( shown && e->size().width() < myHint.width() ) + { + m_prevButton->hide(); + m_nextButton->hide(); + updateGeometry(); + } + else if ( !shown ) + { + int histeresis = m_prevButton->sizeHint().width() * 2 + 2; + if ( e->size().width() > (myHint.width() + histeresis) ) + { + m_prevButton->show(); + m_nextButton->show(); + updateGeometry(); + } + } +} + +void MiniBar::slotChangePage() +{ + // get text from the lineEdit + QString pageNumber = m_pagesEdit->text(); + + // convert it to page number and go to that page + bool ok; + int number = pageNumber.toInt( &ok ) - 1; + if ( ok && number >= 0 && number < (int)m_document->pages() && + number != m_currentPage ) + { + m_document->setViewportPage( number ); + m_pagesEdit->clearFocus(); + } +} + +void MiniBar::slotGotoNormalizedPage( float index ) +{ + // figure out page number and go to that page + int number = (int)( index * (float)m_document->pages() ); + if ( number >= 0 && number < (int)m_document->pages() && + number != m_currentPage ) + m_document->setViewportPage( number ); +} + +void MiniBar::slotEmitNextPage() +{ + // emit signal + nextPage(); +} + +void MiniBar::slotEmitPrevPage() +{ + // emit signal + prevPage(); +} + + + +/** ProgressWidget **/ + +ProgressWidget::ProgressWidget( MiniBar * parent ) + : QWidget( parent, "progress", WNoAutoErase ), + m_miniBar( parent ), m_progressPercentage( -1 ) +{ + setFixedHeight( 4 ); + setMouseTracking( true ); +} + +void ProgressWidget::setProgress( float percentage ) +{ + m_progressPercentage = percentage; + update(); +} + +void ProgressWidget::mouseMoveEvent( QMouseEvent * e ) +{ + if ( e->state() == Qt::LeftButton && width() > 0 ) + m_miniBar->slotGotoNormalizedPage( (float)( QApplication::reverseLayout() ? width() - e->x() : e->x() ) / (float)width() ); +} + +void ProgressWidget::mousePressEvent( QMouseEvent * e ) +{ + if ( e->button() == Qt::LeftButton && width() > 0 ) + m_miniBar->slotGotoNormalizedPage( (float)( QApplication::reverseLayout() ? width() - e->x() : e->x() ) / (float)width() ); +} + +void ProgressWidget::wheelEvent( QWheelEvent * e ) +{ + if ( e->delta() > 0 ) + m_miniBar->slotEmitNextPage(); + else + m_miniBar->slotEmitPrevPage(); +} + +void ProgressWidget::paintEvent( QPaintEvent * e ) +{ + if ( m_progressPercentage < 0.0 ) + return; + + // find out the 'fill' and the 'clear' rectangles + int w = width(), + h = height(), + l = (int)( (float)w * m_progressPercentage ); + QRect cRect = ( QApplication::reverseLayout() ? QRect( 0, 0, w - l, h ) : QRect( l, 0, w - l, h ) ).intersect( e->rect() ); + QRect fRect = ( QApplication::reverseLayout() ? QRect( w - l, 0, l, h ) : QRect( 0, 0, l, h ) ).intersect( e->rect() ); + + // paint rects and a separator line + QPainter p( this ); + if ( cRect.isValid() ) + p.fillRect( cRect, palette().active().highlightedText() ); + if ( fRect.isValid() ) + p.fillRect( fRect, palette().active().highlight() ); + if ( l && l != w ) + { + p.setPen( palette().active().highlight().dark( 120 ) ); + int delta = QApplication::reverseLayout() ? w - l : l; + p.drawLine( delta, 0, delta, h ); + } + // draw a frame-like outline + //p.setPen( palette().active().mid() ); + //p.drawRect( 0,0, w, h ); +} + + +/** PagesEdit **/ + +PagesEdit::PagesEdit( MiniBar * parent ) + : QLineEdit( parent ), m_miniBar( parent ), m_eatClick( false ) +{ + // customize look + setFrameShadow( QFrame::Raised ); + focusOutEvent( 0 ); + + // use an integer validator + m_validator = new QIntValidator( 1, 1, this ); + setValidator( m_validator ); + + // customize text properties + setAlignment( Qt::AlignCenter ); + setMaxLength( 4 ); +} + +void PagesEdit::setPagesNumber( int pages ) +{ + m_validator->setTop( pages ); +} + +void PagesEdit::setText( const QString & text ) +{ + // store a copy of the string + backString = text; + // call default handler if hasn't focus + if ( !hasFocus() ) + QLineEdit::setText( text ); +} + +void PagesEdit::focusInEvent( QFocusEvent * e ) +{ + // select all text + selectAll(); + if ( e->reason() == QFocusEvent::Mouse ) + m_eatClick = true; + // change background color to the default 'edit' color + setLineWidth( 2 ); + setPaletteBackgroundColor( Qt::white ); + // call default handler + QLineEdit::focusInEvent( e ); +} + +void PagesEdit::focusOutEvent( QFocusEvent * e ) +{ + // change background color to a dark tone + setLineWidth( 1 ); + setPaletteBackgroundColor( palette().active().background().light( 105 ) ); + // restore text + QLineEdit::setText( backString ); + // call default handler + QLineEdit::focusOutEvent( e ); +} + +void PagesEdit::mousePressEvent( QMouseEvent * e ) +{ + // if this click got the focus in, don't process the event + if ( !m_eatClick ) + QLineEdit::mousePressEvent( e ); + m_eatClick = false; +} + +void PagesEdit::wheelEvent( QWheelEvent * e ) +{ + if ( e->delta() > 0 ) + m_miniBar->slotEmitNextPage(); + else + m_miniBar->slotEmitPrevPage(); +} + + +/** HoverButton **/ + +HoverButton::HoverButton( QWidget * parent ) + : QPushButton( parent ) +{ + setMouseTracking( true ); +#if KDE_IS_VERSION(3,3,90) + KAcceleratorManager::setNoAccel( this ); +#endif +} + +void HoverButton::enterEvent( QPaintEvent * e ) +{ + update(); + QPushButton::enterEvent( e ); +} + +void HoverButton::leaveEvent( QPaintEvent * e ) +{ + update(); + QPushButton::leaveEvent( e ); +} + +void HoverButton::paintEvent( QPaintEvent * e ) +{ + if ( hasMouse() ) + { + QPushButton::paintEvent( e ); + } + else + { + QPainter p( this ); + p.fillRect(e->rect(), parentWidget() ? parentWidget()->palette().brush(QPalette::Active, QColorGroup::Background) : paletteBackgroundColor()); + drawButtonLabel( &p ); + } +} + +#include "minibar.moc" diff --git a/kpdf/ui/minibar.h b/kpdf/ui/minibar.h new file mode 100644 index 00000000..7c815e5e --- /dev/null +++ b/kpdf/ui/minibar.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> * + * Copyright (C) 2006 by Albert Astals Cid <aacid@kde.org> * + * * + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_MINIBAR_H_ +#define _KPDF_MINIBAR_H_ + +#include <qframe.h> +#include "core/observer.h" + +class KPDFDocument; +class PagesEdit; +class HoverButton; +class ProgressWidget; + +/** + * @short A widget to display page number and change current page. + */ +class MiniBar : public QFrame, public DocumentObserver +{ + Q_OBJECT + public: + MiniBar( QWidget *parent, KPDFDocument * document ); + ~MiniBar(); + + // [INHERITED] from DocumentObserver + uint observerId() const { return MINIBAR_ID; } + void notifySetup( const QValueVector< KPDFPage * > & pages, bool ); + void notifyViewportChanged( bool smoothMove ); + + signals: + void gotoPage(); + void prevPage(); + void nextPage(); + + public slots: + void slotChangePage(); + void slotGotoNormalizedPage( float normIndex ); + void slotEmitNextPage(); + void slotEmitPrevPage(); + + protected: + void resizeEvent( QResizeEvent * ); + + private: + KPDFDocument * m_document; + PagesEdit * m_pagesEdit; + HoverButton * m_prevButton; + HoverButton * m_pagesButton; + HoverButton * m_nextButton; + ProgressWidget * m_progressWidget; + int m_currentPage; +}; + +#endif diff --git a/kpdf/ui/pagepainter.cpp b/kpdf/ui/pagepainter.cpp new file mode 100644 index 00000000..f68df254 --- /dev/null +++ b/kpdf/ui/pagepainter.cpp @@ -0,0 +1,252 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> * + * * + * 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 <qrect.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qimage.h> +#include <qapplication.h> +#include <kimageeffect.h> + +// local includes +#include "pagepainter.h" +#include "core/page.h" +#include "conf/settings.h" + +void PagePainter::paintPageOnPainter( const KPDFPage * page, int id, int flags, + QPainter * destPainter, const QRect & limits, int width, int height ) +{ + QPixmap * pixmap = 0; + + // if a pixmap is present for given id, use it + if ( page->m_pixmaps.contains( id ) ) + pixmap = page->m_pixmaps[ id ]; + + // else find the closest match using pixmaps of other IDs (great optim!) + else if ( !page->m_pixmaps.isEmpty() && width != -1 ) + { + int minDistance = -1; + QMap< int,QPixmap * >::const_iterator it = page->m_pixmaps.begin(), end = page->m_pixmaps.end(); + for ( ; it != end; ++it ) + { + int pixWidth = (*it)->width(), + distance = pixWidth > width ? pixWidth - width : width - pixWidth; + if ( minDistance == -1 || distance < minDistance ) + { + pixmap = *it; + minDistance = distance; + } + } + } + + // if have no pixmap, draw blank page with gray cross and exit + if ( !pixmap ) + { + QColor color = Qt::white; + if ( KpdfSettings::changeColors() ) + { + switch ( KpdfSettings::renderMode() ) + { + case KpdfSettings::EnumRenderMode::Inverted: + color = Qt::black; + break; + case KpdfSettings::EnumRenderMode::Paper: + color = KpdfSettings::paperColor(); + break; + case KpdfSettings::EnumRenderMode::Recolor: + color = KpdfSettings::recolorBackground(); + break; + default: ; + } + } + destPainter->fillRect( limits, color ); + + // draw a cross (to that the pixmap as not yet been loaded) + // helps a lot on pages that take much to render + destPainter->setPen( Qt::gray ); + destPainter->drawLine( 0, 0, width-1, height-1 ); + destPainter->drawLine( 0, height-1, width-1, 0 ); + // idea here: draw a hourglass (or kpdf icon :-) on top-left corner + return; + } + + // find out what to paint over the pixmap (manipulations / overlays) + bool paintAccessibility = (flags & Accessibility) && KpdfSettings::changeColors() && (KpdfSettings::renderMode() != KpdfSettings::EnumRenderMode::Paper); + bool paintHighlights = (flags & Highlights) && !page->m_highlights.isEmpty(); + bool enhanceLinks = (flags & EnhanceLinks) && KpdfSettings::highlightLinks(); + bool enhanceImages = (flags & EnhanceImages) && KpdfSettings::highlightImages(); + // check if there are really some highlightRects to paint + if ( paintHighlights ) + { + // precalc normalized 'limits rect' for intersection + double nXMin = (double)limits.left() / (double)width, + nXMax = (double)limits.right() / (double)width, + nYMin = (double)limits.top() / (double)height, + nYMax = (double)limits.bottom() / (double)height; + // if no rect intersects limits, disable paintHighlights + paintHighlights = false; + QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end(); + for ( ; hIt != hEnd; ++hIt ) + { + if ( (*hIt)->intersects( nXMin, nYMin, nXMax, nYMax ) ) + { + paintHighlights = true; + break; + } + } + } + + // use backBuffer if 'pixmap direct manipulation' is needed + bool backBuffer = paintAccessibility || paintHighlights; + QPixmap * backPixmap = 0; + QPainter * p = destPainter; + if ( backBuffer ) + { + // let's paint using a buffered painter + backPixmap = new QPixmap( limits.width(), limits.height() ); + p = new QPainter( backPixmap ); + p->translate( -limits.left(), -limits.top() ); + } + + // 1. fast blit the pixmap if it has the right size.. + if ( pixmap->width() == width && pixmap->height() == height ) + p->drawPixmap( limits.topLeft(), *pixmap, limits ); + // ..else set a scale matrix to the painter and paint a quick 'zoomed' pixmap + else + { + p->save(); + // TODO paint only the needed part (note: hope that Qt4 transforms are faster) + p->scale( width / (double)pixmap->width(), height / (double)pixmap->height() ); + p->drawPixmap( 0,0, *pixmap, 0,0, pixmap->width(), pixmap->height() ); + p->restore(); + } + + // 2. mangle pixmap: convert it to 32-bit qimage and perform pixel-level manipulations + if ( backBuffer ) + { + QImage backImage = backPixmap->convertToImage(); + // 2.1. modify pixmap following accessibility settings + if ( paintAccessibility ) + { + switch ( KpdfSettings::renderMode() ) + { + case KpdfSettings::EnumRenderMode::Inverted: + // Invert image pixels using QImage internal function + backImage.invertPixels(false); + break; + case KpdfSettings::EnumRenderMode::Recolor: + // Recolor image using KImageEffect::flatten with dither:0 + KImageEffect::flatten( backImage, KpdfSettings::recolorForeground(), KpdfSettings::recolorBackground() ); + break; + case KpdfSettings::EnumRenderMode::BlackWhite: + // Manual Gray and Contrast + unsigned int * data = (unsigned int *)backImage.bits(); + int val, pixels = backImage.width() * backImage.height(), + con = KpdfSettings::bWContrast(), thr = 255 - KpdfSettings::bWThreshold(); + for( int i = 0; i < pixels; ++i ) + { + val = qGray( data[i] ); + if ( val > thr ) + val = 128 + (127 * (val - thr)) / (255 - thr); + else if ( val < thr ) + val = (128 * val) / thr; + if ( con > 2 ) + { + val = con * ( val - thr ) / 2 + thr; + if ( val > 255 ) + val = 255; + else if ( val < 0 ) + val = 0; + } + data[i] = qRgba( val, val, val, 255 ); + } + break; + } + } + // 2.2. highlight rects in page + if ( paintHighlights ) + { + // draw highlights that are inside the 'limits' paint region + QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end(); + for ( ; hIt != hEnd; ++hIt ) + { + HighlightRect * r = *hIt; + QRect highlightRect = r->geometry( width, height ); + if ( highlightRect.isValid() && highlightRect.intersects( limits ) ) + { + // find out the rect to highlight on pixmap + highlightRect = highlightRect.intersect( limits ); + highlightRect.moveBy( -limits.left(), -limits.top() ); + + // highlight composition (product: highlight color * destcolor) + unsigned int * data = (unsigned int *)backImage.bits(); + int val, newR, newG, newB, + rh = r->color.red(), + gh = r->color.green(), + bh = r->color.blue(), + offset = highlightRect.top() * backImage.width(); + for( int y = highlightRect.top(); y <= highlightRect.bottom(); ++y ) + { + for( int x = highlightRect.left(); x <= highlightRect.right(); ++x ) + { + val = data[ x + offset ]; + newR = (qRed(val) * rh) / 255; + newG = (qGreen(val) * gh) / 255; + newB = (qBlue(val) * bh) / 255; + data[ x + offset ] = qRgba( newR, newG, newB, 255 ); + } + offset += backImage.width(); + } + } + } + } + backPixmap->convertFromImage( backImage ); + } + + // 3. visually enchance links and images if requested + if ( enhanceLinks || enhanceImages ) + { + QColor normalColor = QApplication::palette().active().highlight(); + QColor lightColor = normalColor.light( 140 ); + // enlarging limits for intersection is like growing the 'rectGeometry' below + QRect limitsEnlarged = limits; + limitsEnlarged.addCoords( -2, -2, 2, 2 ); + // draw rects that are inside the 'limits' paint region as opaque rects + QValueList< ObjectRect * >::const_iterator lIt = page->m_rects.begin(), lEnd = page->m_rects.end(); + for ( ; lIt != lEnd; ++lIt ) + { + ObjectRect * rect = *lIt; + if ( (enhanceLinks && rect->objectType() == ObjectRect::Link) || + (enhanceImages && rect->objectType() == ObjectRect::Image) ) + { + QRect rectGeometry = rect->geometry( width, height ); + if ( rectGeometry.intersects( limitsEnlarged ) ) + { + // expand rect and draw inner border + rectGeometry.addCoords( -1,-1,1,1 ); + p->setPen( lightColor ); + p->drawRect( rectGeometry ); + // expand rect to draw outer border + rectGeometry.addCoords( -1,-1,1,1 ); + p->setPen( normalColor ); + p->drawRect( rectGeometry ); + } + } + } + } + + // 4. if was backbuffering, copy the backPixmap to destination + if ( backBuffer ) + { + delete p; + destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap ); + delete backPixmap; + } +} diff --git a/kpdf/ui/pagepainter.h b/kpdf/ui/pagepainter.h new file mode 100644 index 00000000..21ef7629 --- /dev/null +++ b/kpdf/ui/pagepainter.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> * + * * + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_PAGEPAINTER_H_ +#define _KPDF_PAGEPAINTER_H_ + +class KPDFPage; +class QPainter; +class QRect; + +/** + * @short Paints a KPDFPage to an open painter using given flags. + */ +class PagePainter +{ + public: + // list of flags passed to the painting function. by OR-ing those flags + // you can decide wether or not to permit drawing of a certain feature. + enum PagePainterFlags { Accessibility = 1, EnhanceLinks = 2, + EnhanceImages = 4, Highlights = 8 }; + + // draw (using painter 'p') the 'page' requested by 'id' using features + // in 'flags'. 'limits' is the bounding rect of the paint operation, + // 'width' and 'height' the expected size of page contents (used only + // to pick up an alternative pixmap if the pixmap of 'id' is missing. + static void paintPageOnPainter( const KPDFPage * page, int id, int flags, + QPainter * p, const QRect & limits, int width = -1, int height = -1 ); +}; + +#endif diff --git a/kpdf/ui/pageview.cpp b/kpdf/ui/pageview.cpp new file mode 100644 index 00000000..247f1b1b --- /dev/null +++ b/kpdf/ui/pageview.cpp @@ -0,0 +1,2114 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * Copyright (C) 2004-2006 by Albert Astals Cid <tsdgeos@terra.es> * + * * + * With portions of code from kpdf/kpdf_pagewidget.cc by: * + * Copyright (C) 2002 by Wilco Greven <greven@kde.org> * + * Copyright (C) 2003 by Christophe Devriese * + * <Christophe.Devriese@student.kuleuven.ac.be> * + * Copyright (C) 2003 by Laurent Montel <montel@kde.org> * + * Copyright (C) 2003 by Dirk Mueller <mueller@kde.org> * + * Copyright (C) 2004 by James Ots <kde@jamesots.com> * + * * + * 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 <qcursor.h> +#include <qpainter.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qpushbutton.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <dcopclient.h> +#include <kcursor.h> +#include <kiconloader.h> +#include <kurldrag.h> +#include <kaction.h> +#include <kstdaccel.h> +#include <kactioncollection.h> +#include <kpopupmenu.h> +#include <klocale.h> +#include <kfiledialog.h> +#include <kimageeffect.h> +#include <kimageio.h> +#include <kapplication.h> +#include <kdebug.h> + +// system includes +#include <math.h> +#include <stdlib.h> + +// local includes +#include "pageview.h" +#include "pageviewutils.h" +#include "pagepainter.h" +#include "core/document.h" +#include "core/page.h" +#include "core/link.h" +#include "core/generator.h" +#include "conf/settings.h" + +#define ROUND(x) (int(x + 0.5)) + +// definition of searchID for this class +#define PAGEVIEW_SEARCH_ID 2 + +// structure used internally by PageView for data storage +class PageViewPrivate +{ +public: + // the document, pageviewItems and the 'visible cache' + KPDFDocument * document; + QValueVector< PageViewItem * > items; + QValueList< PageViewItem * > visibleItems; + + // view layout (columns and continuous in Settings), zoom and mouse + PageView::ZoomMode zoomMode; + float zoomFactor; + PageView::MouseMode mouseMode; + QPoint mouseGrabPos; + QPoint mousePressPos; + int mouseMidStartY; + bool mouseOnRect; + QRect mouseSelectionRect; + QColor selectionRectColor; + + // type ahead find + bool typeAheadActive; + QString typeAheadString; + QTimer * findTimeoutTimer; + // viewport move + bool viewportMoveActive; + QTime viewportMoveTime; + QPoint viewportMoveDest; + QTimer * viewportMoveTimer; + // auto scroll + int scrollIncrement; + QTimer * autoScrollTimer; + // other stuff + QTimer * delayResizeTimer; + bool dirtyLayout; + bool blockViewport; // prevents changes to viewport + bool blockPixmapsRequest; // prevent pixmap requests + PageViewMessage * messageWindow; // in pageviewutils.h + PageViewTip * tip; + + // drag scroll + QPoint dragScrollVector; + QTimer dragScrollTimer; + + // actions + KToggleAction * aMouseNormal; + KToggleAction * aMouseSelect; + KToggleAction * aMouseEdit; + KSelectAction * aZoom; + KToggleAction * aZoomFitWidth; + KToggleAction * aZoomFitPage; + KToggleAction * aZoomFitText; + KToggleAction * aViewTwoPages; + KToggleAction * aViewContinuous; + KAction * aPrevAction; +}; + + + +class PageViewTip : public QToolTip +{ + public: + PageViewTip( PageView * view ) + : QToolTip( view->viewport() ), m_view( view ) + { + } + + ~PageViewTip() + { + remove( m_view->viewport() ); + } + + + protected: + void maybeTip( const QPoint &p ); + + private: + PageView * m_view; +}; + +void PageViewTip::maybeTip( const QPoint &_p ) +{ + QPoint p( _p.x() + m_view->contentsX(), _p.y() + m_view->contentsY() ); + PageViewItem * pageItem = m_view->pickItemOnPoint( p.x(), p.y() ); + if ( pageItem && m_view->d->mouseMode == PageView::MouseNormal ) + { + double nX = (double)(p.x() - pageItem->geometry().left()) / (double)pageItem->width(), + nY = (double)(p.y() - pageItem->geometry().top()) / (double)pageItem->height(); + + // if over a ObjectRect (of type Link) change cursor to hand + const ObjectRect * object = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); + if ( object ) + { + // set tooltip over link's rect + KPDFLink *link = (KPDFLink *)object->pointer(); + QString strtip = link->linkTip(); + if ( !strtip.isEmpty() ) + { + QRect linkRect = object->geometry( pageItem->width(), pageItem->height() ); + linkRect.moveBy( - m_view->contentsX() + pageItem->geometry().left(), - m_view->contentsY() + pageItem->geometry().top() ); + tip( linkRect, strtip ); + } + } + } +} + + + +/* PageView. What's in this file? -> quick overview. + * Code weight (in rows) and meaning: + * 160 - constructor and creating actions plus their connected slots (empty stuff) + * 70 - DocumentObserver inherited methodes (important) + * 550 - events: mouse, keyboard, drag/drop + * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes + * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. + * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, + * and many insignificant stuff like this comment :-) + */ +PageView::PageView( QWidget *parent, KPDFDocument *document ) + : QScrollView( parent, "KPDF::pageView", WStaticContents | WNoAutoErase ) +{ + // create and initialize private storage structure + d = new PageViewPrivate(); + d->document = document; + d->zoomMode = (PageView::ZoomMode)KpdfSettings::zoomMode(); + d->zoomFactor = KpdfSettings::zoomFactor(); + d->mouseMode = MouseNormal; + d->mouseMidStartY = -1; + d->mouseOnRect = false; + d->typeAheadActive = false; + d->findTimeoutTimer = 0; + d->viewportMoveActive = false; + d->viewportMoveTimer = 0; + d->scrollIncrement = 0; + d->autoScrollTimer = 0; + d->delayResizeTimer = 0; + d->dirtyLayout = false; + d->blockViewport = false; + d->blockPixmapsRequest = false; + d->messageWindow = new PageViewMessage(this); + d->tip = new PageViewTip( this ); + d->aPrevAction = 0; + + // widget setup: setup focus, accept drops and track mouse + viewport()->setFocusProxy( this ); + viewport()->setFocusPolicy( StrongFocus ); + //viewport()->setPaletteBackgroundColor( Qt::white ); + viewport()->setBackgroundMode( Qt::NoBackground ); + setResizePolicy( Manual ); + setAcceptDrops( true ); + setDragAutoScroll( false ); + viewport()->setMouseTracking( true ); + + // conntect the padding of the viewport to pixmaps requests + connect( this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotRequestVisiblePixmaps(int, int)) ); + connect( &d->dragScrollTimer, SIGNAL(timeout()), this, SLOT(slotDragScroll()) ); + + // set a corner button to resize the view to the page size +// QPushButton * resizeButton = new QPushButton( viewport() ); +// resizeButton->setPixmap( SmallIcon("crop") ); +// setCornerWidget( resizeButton ); +// resizeButton->setEnabled( false ); + // connect(...); + setInputMethodEnabled( true ); + + // schedule the welcome message + QTimer::singleShot( 0, this, SLOT( slotShowWelcome() ) ); +} + +PageView::~PageView() +{ + // delete all widgets + QValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end(); + for ( ; dIt != dEnd; ++dIt ) + delete *dIt; + delete d->tip; + d->tip = 0; + d->document->removeObserver( this ); + delete d; +} + +void PageView::setupActions( KActionCollection * ac ) +{ + // Zoom actions ( higher scales takes lots of memory! ) + d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, this, SLOT( slotZoom() ), ac, "zoom_to" ); + d->aZoom->setEditable( true ); +#if KDE_IS_VERSION(3,4,89) + d->aZoom->setMaxComboViewCount( 13 ); +#endif + updateZoomText(); + + KStdAction::zoomIn( this, SLOT( slotZoomIn() ), ac, "zoom_in" ); + + KStdAction::zoomOut( this, SLOT( slotZoomOut() ), ac, "zoom_out" ); + + d->aZoomFitWidth = new KToggleAction( i18n("Fit to Page &Width"), "view_fit_width", 0, ac, "zoom_fit_width" ); + connect( d->aZoomFitWidth, SIGNAL( toggled( bool ) ), SLOT( slotFitToWidthToggled( bool ) ) ); + + d->aZoomFitPage = new KToggleAction( i18n("Fit to &Page"), "view_fit_window", 0, ac, "zoom_fit_page" ); + connect( d->aZoomFitPage, SIGNAL( toggled( bool ) ), SLOT( slotFitToPageToggled( bool ) ) ); + + d->aZoomFitText = new KToggleAction( i18n("Fit to &Text"), "viewmagfit", 0, ac, "zoom_fit_text" ); + connect( d->aZoomFitText, SIGNAL( toggled( bool ) ), SLOT( slotFitToTextToggled( bool ) ) ); + + // View-Layout actions + d->aViewTwoPages = new KToggleAction( i18n("&Two Pages"), "view_left_right", 0, ac, "view_twopages" ); + connect( d->aViewTwoPages, SIGNAL( toggled( bool ) ), SLOT( slotTwoPagesToggled( bool ) ) ); + d->aViewTwoPages->setChecked( KpdfSettings::viewColumns() > 1 ); + + d->aViewContinuous = new KToggleAction( i18n("&Continuous"), "view_text", 0, ac, "view_continuous" ); + connect( d->aViewContinuous, SIGNAL( toggled( bool ) ), SLOT( slotContinuousToggled( bool ) ) ); + d->aViewContinuous->setChecked( KpdfSettings::viewContinuous() ); + + // Mouse-Mode actions + d->aMouseNormal = new KRadioAction( i18n("&Browse Tool"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" ); + d->aMouseNormal->setExclusiveGroup( "MouseType" ); + d->aMouseNormal->setChecked( true ); + + KToggleAction * mz = new KRadioAction( i18n("&Zoom Tool"), "viewmag", 0, this, SLOT( slotSetMouseZoom() ), ac, "mouse_zoom" ); + mz->setExclusiveGroup( "MouseType" ); + + d->aMouseSelect = new KRadioAction( i18n("&Select Tool"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" ); + d->aMouseSelect->setExclusiveGroup( "MouseType" ); + +/* d->aMouseEdit = new KRadioAction( i18n("Draw"), "edit", 0, this, SLOT( slotSetMouseDraw() ), ac, "mouse_draw" ); + d->aMouseEdit->setExclusiveGroup("MouseType"); + d->aMouseEdit->setEnabled( false ); // implement feature before removing this line*/ + + // Other actions + KAction * su = new KAction( i18n("Scroll Up"), 0, this, SLOT( slotScrollUp() ), ac, "view_scroll_up" ); + su->setShortcut( "Shift+Up" ); + + KAction * sd = new KAction( i18n("Scroll Down"), 0, this, SLOT( slotScrollDown() ), ac, "view_scroll_down" ); + sd->setShortcut( "Shift+Down" ); +} + +bool PageView::canFitPageWidth() +{ + return d->zoomMode != ZoomFitWidth; +} + +void PageView::fitPageWidth( int /*page*/ ) +{ + d->aZoom->setCurrentItem(0); + slotZoom(); +} + +//BEGIN DocumentObserver inherited methods +void PageView::notifySetup( const QValueVector< KPDFPage * > & pageSet, bool documentChanged ) +{ + // reuse current pages if nothing new + if ( ( pageSet.count() == d->items.count() ) && !documentChanged ) + { + int count = pageSet.count(); + for ( int i = 0; (i < count) && !documentChanged; i++ ) + if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) + documentChanged = true; + if ( !documentChanged ) + return; + } + + // delete all widgets (one for each page in pageSet) + QValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end(); + for ( ; dIt != dEnd; ++dIt ) + delete *dIt; + d->items.clear(); + d->visibleItems.clear(); + + // create children widgets + QValueVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end(); + for ( ; setIt != setEnd; ++setIt ) + d->items.push_back( new PageViewItem( *setIt ) ); + + if ( pageSet.count() > 0 ) + // TODO for Enrico: Check if doing always the slotRelayoutPages() is not + // suboptimal in some cases, i'd say it is not but a recheck will not hurt + // Need slotRelayoutPages() here instead of d->dirtyLayout = true + // because opening a pdf from another pdf will not trigger a viewportchange + // so pages are never relayouted + QTimer::singleShot(0, this, SLOT(slotRelayoutPages())); + else + { + // update the mouse cursor when closing because we may have close through a link and + // want the cursor to come back to the normal cursor + updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) ); + resizeContents( 0, 0 ); + } + + // OSD to display pages + if ( documentChanged && pageSet.count() > 0 && KpdfSettings::showOSD() ) + d->messageWindow->display( + i18n(" Loaded a one-page document.", + " Loaded a %n-page document.", + pageSet.count() ), + PageViewMessage::Info, 4000 ); +} + +void PageView::notifyViewportChanged( bool smoothMove ) +{ + // if we are the one changing viewport, skip this nofity + if ( d->blockViewport ) + return; + + // block setViewport outgoing calls + d->blockViewport = true; + + // find PageViewItem matching the viewport description + const DocumentViewport & vp = d->document->viewport(); + PageViewItem * item = 0; + QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); + for ( ; iIt != iEnd; ++iIt ) + if ( (*iIt)->pageNumber() == vp.pageNumber ) + { + item = *iIt; + break; + } + if ( !item ) + { + kdDebug() << "viewport has no matching item!" << endl; + d->blockViewport = false; + return; + } + + // relayout in "Single Pages" mode or if a relayout is pending + d->blockPixmapsRequest = true; + if ( !KpdfSettings::viewContinuous() || d->dirtyLayout ) + slotRelayoutPages(); + + // restore viewport center or use default {x-center,v-top} alignment + const QRect & r = item->geometry(); + int newCenterX = r.left(), + newCenterY = r.top(); + if ( vp.rePos.enabled ) + { + if (vp.rePos.pos == DocumentViewport::Center) + { + newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() ); + newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() ); + } + else + { + // TopLeft + newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() + viewport()->width() / 2 ); + newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() + viewport()->height() / 2 ); + } + } + else + { + newCenterX += r.width() / 2; + newCenterY += visibleHeight() / 2 - 10; + } + + // if smooth movement requested, setup parameters and start it + if ( smoothMove ) + { + d->viewportMoveActive = true; + d->viewportMoveTime.start(); + d->viewportMoveDest.setX( newCenterX ); + d->viewportMoveDest.setY( newCenterY ); + if ( !d->viewportMoveTimer ) + { + d->viewportMoveTimer = new QTimer( this ); + connect( d->viewportMoveTimer, SIGNAL( timeout() ), + this, SLOT( slotMoveViewport() ) ); + } + d->viewportMoveTimer->start( 25 ); + verticalScrollBar()->setEnabled( false ); + horizontalScrollBar()->setEnabled( false ); + } + else + center( newCenterX, newCenterY ); + d->blockPixmapsRequest = false; + + // request visible pixmaps in the current viewport and recompute it + slotRequestVisiblePixmaps(); + + // enable setViewport calls + d->blockViewport = false; + + // update zoom text if in a ZoomFit/* zoom mode + if ( d->zoomMode != ZoomFixed ) + updateZoomText(); + + // since the page has moved below cursor, update it + updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) ); +} + +void PageView::notifyPageChanged( int pageNumber, int changedFlags ) +{ + // only handle pixmap / highlight changes notifies + if ( changedFlags & DocumentObserver::Bookmark ) + return; + + // iterate over visible items: if page(pageNumber) is one of them, repaint it + QValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); + for ( ; iIt != iEnd; ++iIt ) + if ( (*iIt)->pageNumber() == pageNumber ) + { + // update item's rectangle plus the little outline + QRect expandedRect = (*iIt)->geometry(); + expandedRect.addCoords( -1, -1, 3, 3 ); + updateContents( expandedRect ); + + // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor + if ( cursor().shape() != Qt::SizeVerCursor ) + { + // since the page has been regenerated below cursor, update it + updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) ); + } + break; + } +} + +void PageView::notifyContentsCleared( int changedFlags ) +{ + // if pixmaps were cleared, re-ask them + if ( changedFlags & DocumentObserver::Pixmap ) + slotRequestVisiblePixmaps(); +} + +bool PageView::canUnloadPixmap( int pageNumber ) +{ + // if the item is visible, forbid unloading + QValueList< PageViewItem * >::iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end(); + for ( ; vIt != vEnd; ++vIt ) + if ( (*vIt)->pageNumber() == pageNumber ) + return false; + // if hidden premit unloading + return true; +} +//END DocumentObserver inherited methods + + +void PageView::showText( const QString &text, int ms ) +{ + d->messageWindow->display(text, PageViewMessage::Info, ms ); +} + + +//BEGIN widget events +void PageView::viewportPaintEvent( QPaintEvent * pe ) +{ + // create the rect into contents from the clipped screen rect + QRect viewportRect = viewport()->rect(); + QRect contentsRect = pe->rect().intersect( viewportRect ); + contentsRect.moveBy( contentsX(), contentsY() ); + if ( !contentsRect.isValid() ) + return; + + // create the screen painter. a pixel painted ar contentsX,contentsY + // appears to the top-left corner of the scrollview. + QPainter screenPainter( viewport(), true ); + screenPainter.translate( -contentsX(), -contentsY() ); + + // selectionRect is the normalized mouse selection rect + QRect selectionRect = d->mouseSelectionRect; + if ( !selectionRect.isNull() ) + selectionRect = selectionRect.normalize(); + // selectionRectInternal without the border + QRect selectionRectInternal = selectionRect; + selectionRectInternal.addCoords( 1, 1, -1, -1 ); + // color for blending + QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? + d->selectionRectColor : Qt::red; + + // subdivide region into rects + QMemArray<QRect> allRects = pe->region().rects(); + uint numRects = allRects.count(); + + // preprocess rects area to see if it worths or not using subdivision + uint summedArea = 0; + for ( uint i = 0; i < numRects; i++ ) + { + const QRect & r = allRects[i]; + summedArea += r.width() * r.height(); + } + // very elementary check: SUMj(Region[j].area) is less than boundingRect.area + bool useSubdivision = summedArea < (0.7 * contentsRect.width() * contentsRect.height()); + if ( !useSubdivision ) + numRects = 1; + + // iterate over the rects (only one loop if not using subdivision) + for ( uint i = 0; i < numRects; i++ ) + { + if ( useSubdivision ) + { + // set 'contentsRect' to a part of the sub-divided region + contentsRect = allRects[i].normalize().intersect( viewportRect ); + contentsRect.moveBy( contentsX(), contentsY() ); + if ( !contentsRect.isValid() ) + continue; + } + + // note: this check will take care of all things requiring alpha blending (not only selection) + bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); + + if ( wantCompositing && KpdfSettings::enableCompositing() ) + { + // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) + QPixmap doubleBuffer( contentsRect.size() ); + QPainter pixmapPainter( &doubleBuffer ); + pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); + + // 1) Layer 0: paint items and clear bg on unpainted rects + paintItems( &pixmapPainter, contentsRect ); + // 2) Layer 1: pixmap manipulated areas + // 3) Layer 2: paint (blend) transparent selection + if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && + !selectionRectInternal.contains( contentsRect ) ) + { + QRect blendRect = selectionRectInternal.intersect( contentsRect ); + // skip rectangles covered by the selection's border + if ( blendRect.isValid() ) + { + // grab current pixmap into a new one to colorize contents + QPixmap blendedPixmap( blendRect.width(), blendRect.height() ); + copyBlt( &blendedPixmap, 0,0, &doubleBuffer, + blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), + blendRect.width(), blendRect.height() ); + // blend selBlendColor into the background pixmap + QImage blendedImage = blendedPixmap.convertToImage(); + KImageEffect::blend( selBlendColor.dark(140), blendedImage, 0.2 ); + // copy the blended pixmap back to its place + pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedImage ); + } + // draw border (red if the selection is too small) + pixmapPainter.setPen( selBlendColor ); + pixmapPainter.drawRect( selectionRect ); + } + // 4) Layer 3: overlays + if ( KpdfSettings::debugDrawBoundaries() ) + { + pixmapPainter.setPen( Qt::blue ); + pixmapPainter.drawRect( contentsRect ); + } + + // finish painting and draw contents + pixmapPainter.end(); + screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); + } + else + { + // 1) Layer 0: paint items and clear bg on unpainted rects + paintItems( &screenPainter, contentsRect ); + // 2) Layer 1: opaque manipulated ares (filled / contours) + // 3) Layer 2: paint opaque selection + if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && + !selectionRectInternal.contains( contentsRect ) ) + { + screenPainter.setPen( palette().active().highlight().dark(110) ); + screenPainter.drawRect( selectionRect ); + } + // 4) Layer 3: overlays + if ( KpdfSettings::debugDrawBoundaries() ) + { + screenPainter.setPen( Qt::red ); + screenPainter.drawRect( contentsRect ); + } + } + } +} + +void PageView::viewportResizeEvent( QResizeEvent * ) +{ + // start a timer that will refresh the pixmap after 0.5s + if ( !d->delayResizeTimer ) + { + d->delayResizeTimer = new QTimer( this ); + connect( d->delayResizeTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) ); + } + d->delayResizeTimer->start( 333, true ); +} + +void PageView::keyPressEvent( QKeyEvent * e ) +{ + e->accept(); + + // if performing a selection or dyn zooming, disable keys handling + if ( ( !d->mouseSelectionRect.isNull() && e->key() != Qt::Key_Escape ) || d->mouseMidStartY != -1 ) + return; + + // handle 'find as you type' (based on khtml/khtmlview.cpp) + if( d->typeAheadActive ) + { + // backspace: remove a char and search or terminates search + if( e->key() == Key_BackSpace ) + { + if( d->typeAheadString.length() > 1 ) + { + d->typeAheadString = d->typeAheadString.left( d->typeAheadString.length() - 1 ); + bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, true, false, + KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true ); + QString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\"."); + d->messageWindow->display( status.arg(d->typeAheadString.lower()), + found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 ); + d->findTimeoutTimer->start( 3000, true ); + } + else + { + findAheadStop(); + d->document->resetSearch( PAGEVIEW_SEARCH_ID ); + } + } + // F3: go to next occurrency + else if( e->key() == KStdAccel::findNext() ) + { + // part doesn't get this key event because of the keyboard grab + d->findTimeoutTimer->stop(); // restore normal operation during possible messagebox is displayed + // it is needed to grab the keyboard becase people may have Space assigned to a + // accel and without grabbing the keyboard you can not vim-search for space + // because it activates the accel + releaseKeyboard(); + if ( d->document->continueSearch( PAGEVIEW_SEARCH_ID ) ) + d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->typeAheadString.lower()), + PageViewMessage::Find, 3000 ); + d->findTimeoutTimer->start( 3000, true ); + // it is needed to grab the keyboard becase people may have Space assigned to a + // accel and without grabbing the keyboard you can not vim-search for space + // because it activates the accel + grabKeyboard(); + } + // esc and return: end search + else if( e->key() == Key_Escape || e->key() == Key_Return ) + { + findAheadStop(); + } + // other key: add to text and search + else if( !e->text().isEmpty() ) + { + d->typeAheadString += e->text(); + doTypeAheadSearch(); + } + return; + } + else if( e->key() == '/' && d->document->isOpened() && d->document->supportsSearching() ) + { + // stop scrolling the page (if doing it) + if ( d->autoScrollTimer ) + { + d->scrollIncrement = 0; + d->autoScrollTimer->stop(); + } + // start type-adeas search + d->typeAheadString = QString(); + d->messageWindow->display( i18n("Starting -- find text as you type"), PageViewMessage::Find, 3000 ); + d->typeAheadActive = true; + if ( !d->findTimeoutTimer ) + { + // create the timer on demand + d->findTimeoutTimer = new QTimer( this ); + connect( d->findTimeoutTimer, SIGNAL( timeout() ), this, SLOT( findAheadStop() ) ); + } + d->findTimeoutTimer->start( 3000, true ); + // it is needed to grab the keyboard becase people may have Space assigned to a + // accel and without grabbing the keyboard you can not vim-search for space + // because it activates the accel + grabKeyboard(); + return; + } + + // if viewport is moving, disable keys handling + if ( d->viewportMoveActive ) + return; + + // move/scroll page by using keys + switch ( e->key() ) + { + case Key_Up: + case Key_PageUp: + case Key_Backspace: + // if in single page mode and at the top of the screen, go to previous page + if ( KpdfSettings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minValue() ) + { + if ( e->key() == Key_Up ) + verticalScrollBar()->subtractLine(); + else + verticalScrollBar()->subtractPage(); + } + else if ( d->document->currentPage() > 0 ) + { + // more optimized than document->setPrevPage and then move view to bottom + DocumentViewport newViewport = d->document->viewport(); + newViewport.pageNumber -= 1; + newViewport.rePos.enabled = true; + newViewport.rePos.normalizedY = 1.0; + d->document->setViewport( newViewport ); + } + break; + case Key_Down: + case Key_PageDown: + case Key_Space: + // if in single page mode and at the bottom of the screen, go to next page + if ( KpdfSettings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maxValue() ) + { + if ( e->key() == Key_Down ) + verticalScrollBar()->addLine(); + else + verticalScrollBar()->addPage(); + } + else if ( d->document->currentPage() < d->items.count() - 1 ) + { + // more optmized than document->setNextPage and then move view to top + DocumentViewport newViewport = d->document->viewport(); + newViewport.pageNumber += 1; + newViewport.rePos.enabled = true; + newViewport.rePos.normalizedY = 0.0; + d->document->setViewport( newViewport ); + } + break; + case Key_Left: + horizontalScrollBar()->subtractLine(); + break; + case Key_Right: + horizontalScrollBar()->addLine(); + break; + case Qt::Key_Escape: + selectionClear(); + d->mousePressPos = QPoint(); + if ( d->aPrevAction ) + { + d->aPrevAction->activate(); + d->aPrevAction = 0; + } + break; + case Key_Shift: + case Key_Control: + if ( d->autoScrollTimer ) + { + if ( d->autoScrollTimer->isActive() ) + d->autoScrollTimer->stop(); + else + slotAutoScoll(); + return; + } + // else fall trhough + default: + e->ignore(); + return; + } + // if a known key has been pressed, stop scrolling the page + if ( d->autoScrollTimer ) + { + d->scrollIncrement = 0; + d->autoScrollTimer->stop(); + } +} + +void PageView::imEndEvent( QIMEvent * e ) +{ + if( d->typeAheadActive ) + { + if( !e->text().isEmpty() ) + { + d->typeAheadString += e->text(); + doTypeAheadSearch(); + e->accept(); + } + } +} + +void PageView::contentsMouseMoveEvent( QMouseEvent * e ) +{ + // don't perform any mouse action when no document is shown + if ( d->items.isEmpty() ) + return; + + // don't perform any mouse action when viewport is autoscrolling + if ( d->viewportMoveActive ) + return; + + // if holding mouse mid button, perform zoom + if ( (e->state() & MidButton) && d->mouseMidStartY >= 0 ) + { + int deltaY = d->mouseMidStartY - e->globalPos().y(); + d->mouseMidStartY = e->globalPos().y(); + d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); + updateZoom( ZoomRefreshCurrent ); + // uncomment following line to force a complete redraw + viewport()->repaint( false ); + return; + } + + bool leftButton = e->state() & LeftButton, + rightButton = e->state() & RightButton; + switch ( d->mouseMode ) + { + case MouseNormal: + if ( leftButton ) + { + // drag page + if ( !d->mouseGrabPos.isNull() ) + { + // scroll page by position increment + QPoint delta = d->mouseGrabPos - e->globalPos(); + scrollBy( delta.x(), delta.y() ); + d->mouseGrabPos = e->globalPos(); + } + } + else if ( rightButton && !d->mousePressPos.isNull() ) + { + // if mouse moves 5 px away from the press point, switch to 'selection' + int deltaX = d->mousePressPos.x() - e->globalPos().x(), + deltaY = d->mousePressPos.y() - e->globalPos().y(); + if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) + { + d->aPrevAction = d->aMouseNormal; + d->aMouseSelect->activate(); + QColor selColor = palette().active().highlight().light( 120 ); + selectionStart( e->x() + deltaX, e->y() + deltaY, selColor, false ); + selectionEndPoint( e->x(), e->y() ); + break; + } + } + else + { + // only hovering the page, so update the cursor + updateCursor( e->pos() ); + } + break; + + case MouseZoom: + case MouseSelect: + // set second corner of selection + if ( !d->mousePressPos.isNull() && ( leftButton || d->aPrevAction ) ) + selectionEndPoint( e->x(), e->y() ); + break; + + case MouseEdit: // ? update graphics ? + break; + } +} + +void PageView::contentsMousePressEvent( QMouseEvent * e ) +{ + // don't perform any mouse action when no document is shown + if ( d->items.isEmpty() ) + return; + + // if performing a selection or dyn zooming, disable mouse press + if ( !d->mouseSelectionRect.isNull() || d->mouseMidStartY != -1 || + d->viewportMoveActive ) + return; + + // if the page is scrolling, stop it + if ( d->autoScrollTimer ) + { + d->scrollIncrement = 0; + d->autoScrollTimer->stop(); + } + + // if pressing mid mouse button while not doing other things, begin 'comtinous zoom' mode + if ( e->button() & MidButton ) + { + d->mouseMidStartY = e->globalPos().y(); + setCursor( KCursor::sizeVerCursor() ); + return; + } + + // update press / 'start drag' mouse position + d->mousePressPos = e->globalPos(); + + // handle mode dependant mouse press actions + bool leftButton = e->button() & LeftButton, + rightButton = e->button() & RightButton; + switch ( d->mouseMode ) + { + case MouseNormal: // drag start / click / link following + if ( leftButton ) + { + d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos; + if ( !d->mouseOnRect ) + setCursor( KCursor::sizeAllCursor() ); + } + break; + + case MouseZoom: // set first corner of the zoom rect + if ( leftButton ) + selectionStart( e->x(), e->y(), palette().active().highlight(), false ); + else if ( rightButton ) + updateZoom( ZoomOut ); + break; + + case MouseSelect: // set first corner of the selection rect + if ( leftButton ) + { + QColor selColor = palette().active().highlight().light( 120 ); + selectionStart( e->x(), e->y(), selColor, false ); + } + break; + + case MouseEdit: // ..to do.. + break; + } +} + +void PageView::contentsMouseReleaseEvent( QMouseEvent * e ) +{ + // stop the drag scrolling + d->dragScrollTimer.stop(); + + // don't perform any mouse action when no document is shown + if ( d->items.isEmpty() ) + { + // ..except for right Clicks (emitted even it viewport is empty) + if ( e->button() == RightButton ) + emit rightClick( 0, e->globalPos() ); + return; + } + + // don't perform any mouse action when viewport is autoscrolling + if ( d->viewportMoveActive ) + return; + + // handle mode indepent mid buttom zoom + bool midButton = e->button() & MidButton; + if ( midButton && d->mouseMidStartY > 0 ) + { + d->mouseMidStartY = -1; + // while drag-zooming we could have gone over a link + updateCursor( e->pos() ); + return; + } + + bool leftButton = e->button() & LeftButton, + rightButton = e->button() & RightButton; + switch ( d->mouseMode ) + { + case MouseNormal:{ + // return the cursor to its normal state after dragging + if ( cursor().shape() == Qt::SizeAllCursor ) + updateCursor( e->pos() ); + + PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() ); + + // if the mouse has not moved since the press, that's a -click- + if ( leftButton && pageItem && d->mousePressPos == e->globalPos()) + { + double nX = (double)(e->x() - pageItem->geometry().left()) / (double)pageItem->width(), + nY = (double)(e->y() - pageItem->geometry().top()) / (double)pageItem->height(); + const ObjectRect * linkRect, * imageRect; + linkRect = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); + if ( linkRect ) + { + // handle click over a link + const KPDFLink * link = static_cast< const KPDFLink * >( linkRect->pointer() ); + d->document->processLink( link ); + } + else + { + // a link can move us to another page or even to another document, there's no point in trying to process the click on the image once we have processes the click on the link + imageRect = pageItem->page()->hasObject( ObjectRect::Image, nX, nY ); + if ( imageRect ) + { + // handle click over a image + } + // Enrico and me have decided this is not worth the trouble it generates + // else + // { + // if not on a rect, the click selects the page + // d->document->setViewportPage( pageItem->pageNumber(), PAGEVIEW_ID ); + // } + } + } + else if ( rightButton ) + { + // right click (if not within 5 px of the press point, the mode + // had been already changed to 'Selection' instead of 'Normal') + emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); + } + }break; + + case MouseZoom: + // if a selection rect has been defined, zoom into it + if ( leftButton && !d->mouseSelectionRect.isNull() ) + { + QRect selRect = d->mouseSelectionRect.normalize(); + if ( selRect.width() <= 8 && selRect.height() <= 8 ) + { + selectionClear(); + break; + } + + // find out new zoom ratio and normalized view center (relative to the contentsRect) + double zoom = QMIN( (double)visibleWidth() / (double)selRect.width(), (double)visibleHeight() / (double)selRect.height() ); + double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentsWidth()); + double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentsHeight()); + + // zoom up to 400% + if ( d->zoomFactor <= 4.0 || zoom <= 1.0 ) + { + d->zoomFactor *= zoom; + viewport()->setUpdatesEnabled( false ); + updateZoom( ZoomRefreshCurrent ); + viewport()->setUpdatesEnabled( true ); + } + + // recenter view and update the viewport + center( (int)(nX * contentsWidth()), (int)(nY * contentsHeight()) ); + updateContents(); + + // hide message box and delete overlay window + selectionClear(); + } + break; + + case MouseSelect:{ + + if (d->mouseSelectionRect.isNull() && rightButton) + { + PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() ); + emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); + } + + // if a selection is defined, display a popup + if ( (!leftButton && !d->aPrevAction) || (leftButton && d->aPrevAction) || + d->mouseSelectionRect.isNull() ) + break; + + QRect selectionRect = d->mouseSelectionRect.normalize(); + if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) + { + selectionClear(); + if ( d->aPrevAction ) + { + d->aPrevAction->activate(); + d->aPrevAction = 0; + } + break; + } + + // grab text in selection by extracting it from all intersected pages + QString selectedText; + QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); + for ( ; iIt != iEnd; ++iIt ) + { + PageViewItem * item = *iIt; + const QRect & itemRect = item->geometry(); + if ( selectionRect.intersects( itemRect ) ) + { + // request the textpage if there isn't one + const KPDFPage * kpdfPage = item->page(); + if ( !kpdfPage->hasSearchPage() ) + d->document->requestTextPage( kpdfPage->number() ); + // grab text in the rect that intersects itemRect + QRect relativeRect = selectionRect.intersect( itemRect ); + relativeRect.moveBy( -itemRect.left(), -itemRect.top() ); + NormalizedRect normRect( relativeRect, item->width(), item->height() ); + selectedText += kpdfPage->getText( normRect ); + } + } + + // popup that ask to copy:text and copy/save:image + KPopupMenu menu( this ); + if ( !selectedText.isEmpty() ) + { + menu.insertTitle( i18n( "Text (1 character)", "Text (%n characters)", selectedText.length() ) ); + menu.insertItem( SmallIcon("editcopy"), i18n( "Copy to Clipboard" ), 1 ); + if ( !d->document->isAllowed( KPDFDocument::AllowCopy ) ) + menu.setItemEnabled( 1, false ); + if ( KpdfSettings::useKTTSD() ) + menu.insertItem( SmallIcon("kttsd"), i18n( "Speak Text" ), 2 ); + } + menu.insertTitle( i18n( "Image (%1 by %2 pixels)" ).arg( selectionRect.width() ).arg( selectionRect.height() ) ); + menu.insertItem( SmallIcon("image"), i18n( "Copy to Clipboard" ), 3 ); + menu.insertItem( SmallIcon("filesave"), i18n( "Save to File..." ), 4 ); + int choice = menu.exec( e->globalPos() ); + // IMAGE operation choosen + if ( choice > 2 ) + { + // renders page into a pixmap + QPixmap copyPix( selectionRect.width(), selectionRect.height() ); + QPainter copyPainter( ©Pix ); + copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); + paintItems( ©Painter, selectionRect ); + + if ( choice == 3 ) + { + // [2] copy pixmap to clipboard + QClipboard *cb = QApplication::clipboard(); + cb->setPixmap( copyPix, QClipboard::Clipboard ); + if ( cb->supportsSelection() ) + cb->setPixmap( copyPix, QClipboard::Selection ); + d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard." ).arg( copyPix.width() ).arg( copyPix.height() ) ); + } + else if ( choice == 4 ) + { + // [3] save pixmap to file + QString fileName = KFileDialog::getSaveFileName( QString::null, "image/png image/jpeg", this ); + if ( fileName.isNull() ) + d->messageWindow->display( i18n( "File not saved." ), PageViewMessage::Warning ); + else + { + QString type( KImageIO::type( fileName ) ); + if ( type.isNull() ) + type = "PNG"; + copyPix.save( fileName, type.latin1() ); + d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file." ).arg( copyPix.width() ).arg( copyPix.height() ).arg( type ) ); + } + } + } + // TEXT operation choosen + else + { + if ( choice == 1 ) + { + // [1] copy text to clipboard + QClipboard *cb = QApplication::clipboard(); + cb->setText( selectedText, QClipboard::Clipboard ); + if ( cb->supportsSelection() ) + cb->setText( selectedText, QClipboard::Selection ); + } + else if ( choice == 2 ) + { + // [2] speech selection using KTTSD + DCOPClient * client = DCOPClient::mainClient(); + // Albert says is this ever necessary? + // we already attached on Part constructor + // if ( !client->isAttached() ) + // client->attach(); + // If KTTSD not running, start it. + if (!client->isApplicationRegistered("kttsd")) + { + QString error; + if (KApplication::startServiceByDesktopName("kttsd", QStringList(), &error)) + { + d->messageWindow->display( i18n("Starting KTTSD Failed: %1").arg(error) ); + KpdfSettings::setUseKTTSD(false); + KpdfSettings::writeConfig(); + } + } + if ( KpdfSettings::useKTTSD() ) + { + // serialize the text to speech (selectedText) and the + // preferred reader ("" is the default voice) ... + QByteArray data; + QDataStream arg( data, IO_WriteOnly ); + arg << selectedText; + arg << QString(); + QCString replyType; + QByteArray replyData; + // ..and send it to KTTSD + if (client->call( "kttsd", "KSpeech", "setText(QString,QString)", data, replyType, replyData, true )) + { + QByteArray data2; + QDataStream arg2(data2, IO_WriteOnly); + arg2 << 0; + client->send("kttsd", "KSpeech", "startText(uint)", data2 ); + } + } + } + } + + // clear widget selection and invalidate rect + selectionClear(); + + // restore previous action if came from it using right button + if ( d->aPrevAction ) + { + d->aPrevAction->activate(); + d->aPrevAction = 0; + } + }break; + + case MouseEdit: // ? apply [tool] ? + break; + } + + // reset mouse press / 'drag start' position + d->mousePressPos = QPoint(); +} + +void PageView::wheelEvent( QWheelEvent *e ) +{ + // don't perform any mouse action when viewport is autoscrolling + if ( d->viewportMoveActive ) + return; + + if ( !d->document->isOpened() ) + { + QScrollView::wheelEvent( e ); + return; + } + + int delta = e->delta(), + vScroll = verticalScrollBar()->value(); + e->accept(); + if ( (e->state() & ControlButton) == ControlButton ) { + if ( e->delta() < 0 ) + slotZoomOut(); + else + slotZoomIn(); + } + else if ( delta <= -120 && !KpdfSettings::viewContinuous() && vScroll == verticalScrollBar()->maxValue() ) + { + // go to next page + if ( d->document->currentPage() < d->items.count() - 1 ) + { + // more optmized than document->setNextPage and then move view to top + DocumentViewport newViewport = d->document->viewport(); + newViewport.pageNumber += 1; + newViewport.rePos.enabled = true; + newViewport.rePos.normalizedY = 0.0; + d->document->setViewport( newViewport ); + } + } + else if ( delta >= 120 && !KpdfSettings::viewContinuous() && vScroll == verticalScrollBar()->minValue() ) + { + // go to prev page + if ( d->document->currentPage() > 0 ) + { + // more optmized than document->setPrevPage and then move view to bottom + DocumentViewport newViewport = d->document->viewport(); + newViewport.pageNumber -= 1; + newViewport.rePos.enabled = true; + newViewport.rePos.normalizedY = 1.0; + d->document->setViewport( newViewport ); + } + } + else + QScrollView::wheelEvent( e ); + + QPoint cp = viewportToContents(e->pos()); + updateCursor(cp); +} + +void PageView::dragEnterEvent( QDragEnterEvent * ev ) +{ + ev->accept(); +} + +void PageView::dropEvent( QDropEvent * ev ) +{ + KURL::List lst; + if ( KURLDrag::decode( ev, lst ) ) + emit urlDropped( lst.first() ); +} +//END widget events + +void PageView::paintItems( QPainter * p, const QRect & contentsRect ) +{ + // when checking if an Item is contained in contentsRect, instead of + // growing PageViewItems rects (for keeping outline into account), we + // grow the contentsRect + QRect checkRect = contentsRect; + checkRect.addCoords( -3, -3, 1, 1 ); + + // create a region from wich we'll subtract painted rects + QRegion remainingArea( contentsRect ); + + //QValueVector< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); + QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); + for ( ; iIt != iEnd; ++iIt ) + { + // check if a piece of the page intersects the contents rect + if ( !(*iIt)->geometry().intersects( checkRect ) ) + continue; + + PageViewItem * item = *iIt; + QRect pixmapGeometry = item->geometry(); + + // translate the painter so we draw top-left pixmap corner in 0,0 + p->save(); + p->translate( pixmapGeometry.left(), pixmapGeometry.top() ); + + // item pixmap and outline geometry + QRect outlineGeometry = pixmapGeometry; + outlineGeometry.addCoords( -1, -1, 3, 3 ); + + // draw the page outline (little black border and 2px shadow) + if ( !pixmapGeometry.contains( contentsRect ) ) + { + int pixmapWidth = pixmapGeometry.width(), + pixmapHeight = pixmapGeometry.height(); + // draw simple outline + p->setPen( Qt::black ); + p->drawRect( -1, -1, pixmapWidth + 2, pixmapHeight + 2 ); + // draw bottom/right gradient + int levels = 2; + int r = Qt::gray.red() / (levels + 2), + g = Qt::gray.green() / (levels + 2), + b = Qt::gray.blue() / (levels + 2); + for ( int i = 0; i < levels; i++ ) + { + p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) ); + p->drawLine( i, i + pixmapHeight + 1, i + pixmapWidth + 1, i + pixmapHeight + 1 ); + p->drawLine( i + pixmapWidth + 1, i, i + pixmapWidth + 1, i + pixmapHeight ); + p->setPen( Qt::gray ); + p->drawLine( -1, i + pixmapHeight + 1, i - 1, i + pixmapHeight + 1 ); + p->drawLine( i + pixmapWidth + 1, -1, i + pixmapWidth + 1, i - 1 ); + } + } + + // draw the pixmap (note: this modifies the painter) + if ( contentsRect.intersects( pixmapGeometry ) ) + { + QRect pixmapRect = contentsRect.intersect( pixmapGeometry ); + pixmapRect.moveBy( -pixmapGeometry.left(), -pixmapGeometry.top() ); + int flags = PagePainter::Accessibility | PagePainter::EnhanceLinks | + PagePainter::EnhanceImages | PagePainter::Highlights; + PagePainter::paintPageOnPainter( item->page(), PAGEVIEW_ID, flags, p, pixmapRect, + pixmapGeometry.width(), pixmapGeometry.height() ); + } + + // remove painted area from 'remainingArea' and restore painter + remainingArea -= outlineGeometry.intersect( contentsRect ); + p->restore(); + } + + // paint with background color the unpainted area + QMemArray<QRect> backRects = remainingArea.rects(); + uint backRectsNumber = backRects.count(); + for ( uint jr = 0; jr < backRectsNumber; jr++ ) + p->fillRect( backRects[ jr ], Qt::gray ); +} + +void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) +{ + const KPDFPage * kpdfPage = item->page(); + double width = kpdfPage->width(), + height = kpdfPage->height(), + zoom = d->zoomFactor; + + if ( d->zoomMode == ZoomFixed ) + { + width *= zoom; + height *= zoom; + item->setWHZ( (int)width, (int)height, d->zoomFactor ); + } + else if ( d->zoomMode == ZoomFitWidth ) + { + height = kpdfPage->ratio() * colWidth; + item->setWHZ( colWidth, (int)height, (double)colWidth / width ); + d->zoomFactor = (double)colWidth / width; + } + else if ( d->zoomMode == ZoomFitPage ) + { + double scaleW = (double)colWidth / (double)width; + double scaleH = (double)rowHeight / (double)height; + zoom = QMIN( scaleW, scaleH ); + item->setWHZ( (int)(zoom * width), (int)(zoom * height), zoom ); + d->zoomFactor = zoom; + } +#ifndef NDEBUG + else + kdDebug() << "calling updateItemSize with unrecognized d->zoomMode!" << endl; +#endif +} + +PageViewItem * PageView::pickItemOnPoint( int x, int y ) +{ + PageViewItem * item = 0; + QValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); + for ( ; iIt != iEnd; ++iIt ) + { + PageViewItem * i = *iIt; + const QRect & r = i->geometry(); + if ( x < r.right() && x > r.left() && y < r.bottom() ) + { + if ( y > r.top() ) + item = i; + break; + } + } + return item; +} + +void PageView::selectionStart( int x, int y, const QColor & color, bool /*aboveAll*/ ) +{ + d->mouseSelectionRect.setRect( x, y, 1, 1 ); + d->selectionRectColor = color; + // ensures page doesn't scroll + if ( d->autoScrollTimer ) + { + d->scrollIncrement = 0; + d->autoScrollTimer->stop(); + } +} + +void PageView::selectionEndPoint( int x, int y ) +{ + if (x < contentsX()) d->dragScrollVector.setX(x - contentsX()); + else if (contentsX() + viewport()->width() < x) d->dragScrollVector.setX(x - contentsX() - viewport()->width()); + else d->dragScrollVector.setX(0); + + if (y < contentsY()) d->dragScrollVector.setY(y - contentsY()); + else if (contentsY() + viewport()->height() < y) d->dragScrollVector.setY(y - contentsY() - viewport()->height()); + else d->dragScrollVector.setY(0); + + if (d->dragScrollVector != QPoint(0, 0)) + { + if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); + } + else d->dragScrollTimer.stop(); + + // clip selection to the viewport + QRect viewportRect( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + x = QMAX( QMIN( x, viewportRect.right() ), viewportRect.left() ); + y = QMAX( QMIN( y, viewportRect.bottom() ), viewportRect.top() ); + // if selection changed update rect + if ( d->mouseSelectionRect.right() != x || d->mouseSelectionRect.bottom() != y ) + { + // send incremental paint events + QRect oldRect = d->mouseSelectionRect.normalize(); + d->mouseSelectionRect.setRight( x ); + d->mouseSelectionRect.setBottom( y ); + QRect newRect = d->mouseSelectionRect.normalize(); + // generate diff region: [ OLD.unite(NEW) - OLD.intersect(NEW) ] + QRegion compoundRegion = QRegion( oldRect ).unite( newRect ); + if ( oldRect.intersects( newRect ) ) + { + QRect intersection = oldRect.intersect( newRect ); + intersection.addCoords( 1, 1, -1, -1 ); + if ( intersection.width() > 20 && intersection.height() > 20 ) + compoundRegion -= intersection; + } + // tassellate region with rects and enqueue paint events + QMemArray<QRect> rects = compoundRegion.rects(); + for ( uint i = 0; i < rects.count(); i++ ) + updateContents( rects[i] ); + } +} + +void PageView::selectionClear() +{ + updateContents( d->mouseSelectionRect.normalize() ); + d->mouseSelectionRect.setCoords( 0, 0, -1, -1 ); +} + +void PageView::updateZoom( ZoomMode newZoomMode ) +{ + if ( newZoomMode == ZoomFixed ) + { + if ( d->aZoom->currentItem() == 0 ) + newZoomMode = ZoomFitWidth; + else if ( d->aZoom->currentItem() == 1 ) + newZoomMode = ZoomFitPage; + } + + float newFactor = d->zoomFactor; + KAction * checkedZoomAction = 0; + switch ( newZoomMode ) + { + case ZoomFixed:{ //ZoomFixed case + QString z = d->aZoom->currentText(); + newFactor = KGlobal::locale()->readNumber( z.remove( z.find( '%' ), 1 ) ) / 100.0; + }break; + case ZoomIn: + newFactor += (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1; + newZoomMode = ZoomFixed; + break; + case ZoomOut: + newFactor -= (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1; + newZoomMode = ZoomFixed; + break; + case ZoomFitWidth: + checkedZoomAction = d->aZoomFitWidth; + break; + case ZoomFitPage: + checkedZoomAction = d->aZoomFitPage; + break; + case ZoomFitText: + checkedZoomAction = d->aZoomFitText; + break; + case ZoomRefreshCurrent: + newZoomMode = ZoomFixed; + d->zoomFactor = -1; + break; + } + if ( newFactor > 4.0 ) + newFactor = 4.0; + if ( newFactor < 0.1 ) + newFactor = 0.1; + + if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) + { + // rebuild layout and update the whole viewport + d->zoomMode = newZoomMode; + d->zoomFactor = newFactor; + // be sure to block updates to document's viewport + bool prevState = d->blockViewport; + d->blockViewport = true; + slotRelayoutPages(); + d->blockViewport = prevState; + // request pixmaps + slotRequestVisiblePixmaps(); + // update zoom text + updateZoomText(); + // update actions checked state + d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); + d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); + d->aZoomFitText->setChecked( checkedZoomAction == d->aZoomFitText ); + + // save selected zoom factor + KpdfSettings::setZoomMode(newZoomMode); + KpdfSettings::setZoomFactor(newFactor); + KpdfSettings::writeConfig(); + } +} + +void PageView::updateZoomText() +{ + // use current page zoom as zoomFactor if in ZoomFit/* mode + if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) + d->zoomFactor = d->items[ QMAX( 0, (int)d->document->currentPage() ) ]->zoomFactor(); + float newFactor = d->zoomFactor; + d->aZoom->clear(); + + // add items that describe fit actions + QStringList translated; + translated << i18n("Fit Width") << i18n("Fit Page"); // << i18n("Fit Text"); + + // add percent items + QString double_oh( "00" ); + const float zoomValue[10] = { 0.125, 0.25, 0.333, 0.5, 0.667, 0.75, 1, 1.25, 1.50, 2 }; + int idx = 0, + selIdx = 2; // use 3 if "fit text" present + bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio + while ( idx < 10 || !inserted ) + { + float value = idx < 10 ? zoomValue[ idx ] : newFactor; + if ( !inserted && newFactor < (value - 0.0001) ) + value = newFactor; + else + idx ++; + if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) + inserted = true; + if ( !inserted ) + selIdx++; + QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 2 ) ); + localValue.remove( KGlobal::locale()->decimalSymbol() + double_oh ); + translated << QString( "%1%" ).arg( localValue ); + } + d->aZoom->setItems( translated ); + + // select current item in list + if ( d->zoomMode == ZoomFitWidth ) + selIdx = 0; + else if ( d->zoomMode == ZoomFitPage ) + selIdx = 1; + else if ( d->zoomMode == ZoomFitText ) + selIdx = 2; + d->aZoom->setCurrentItem( selIdx ); +} + +void PageView::updateCursor( const QPoint &p ) +{ + // detect the underlaying page (if present) + PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); + if ( pageItem && d->mouseMode == MouseNormal ) + { + double nX = (double)(p.x() - pageItem->geometry().left()) / (double)pageItem->width(), + nY = (double)(p.y() - pageItem->geometry().top()) / (double)pageItem->height(); + + // if over a ObjectRect (of type Link) change cursor to hand + d->mouseOnRect = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); + if ( d->mouseOnRect ) + setCursor( KCursor::handCursor() ); + else + setCursor( KCursor::arrowCursor() ); + } + else + { + // if there's no page over the cursor and we were showing the pointingHandCursor + // go back to the normal one + d->mouseOnRect = false; + setCursor( KCursor::arrowCursor() ); + } +} + +void PageView::doTypeAheadSearch() +{ + bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, false, false, + KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true ); + QString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\"."); + d->messageWindow->display( status.arg(d->typeAheadString.lower()), + found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 ); + d->findTimeoutTimer->start( 3000, true ); +} + +//BEGIN private SLOTS +void PageView::slotRelayoutPages() +// called by: notifySetup, viewportResizeEvent, slotTwoPagesToggled, slotContinuousToggled, updateZoom +{ + // set an empty container if we have no pages + int pageCount = d->items.count(); + if ( pageCount < 1 ) + { + resizeContents( 0, 0 ); + return; + } + + // if viewport was auto-moving, stop it + if ( d->viewportMoveActive ) + { + d->viewportMoveActive = false; + d->viewportMoveTimer->stop(); + verticalScrollBar()->setEnabled( true ); + horizontalScrollBar()->setEnabled( true ); + } + + // common iterator used in this method and viewport parameters + QValueVector< PageViewItem * >::iterator iIt, iEnd = d->items.end(); + int viewportWidth = visibleWidth(), + viewportHeight = visibleHeight(), + fullWidth = 0, + fullHeight = 0; + QRect viewportRect( contentsX(), contentsY(), viewportWidth, viewportHeight ); + + // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately + if ( KpdfSettings::viewContinuous() ) + { + // Here we find out column's width and row's height to compute a table + // so we can place widgets 'centered in virtual cells'. + int nCols = KpdfSettings::viewColumns(), + nRows = (int)ceil( (float)pageCount / (float)nCols ), + * colWidth = new int[ nCols ], + * rowHeight = new int[ nRows ], + cIdx = 0, + rIdx = 0; + for ( int i = 0; i < nCols; i++ ) + colWidth[ i ] = viewportWidth / nCols; + for ( int i = 0; i < nRows; i++ ) + rowHeight[ i ] = 0; + + // 1) find the maximum columns width and rows height for a grid in + // which each page must well-fit inside a cell + for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) + { + PageViewItem * item = *iIt; + // update internal page size (leaving a little margin in case of Fit* modes) + updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 8 ); + // find row's maximum height and column's max width + if ( item->width() + 6 > colWidth[ cIdx ] ) + colWidth[ cIdx ] = item->width() + 6; + if ( item->height() > rowHeight[ rIdx ] ) + rowHeight[ rIdx ] = item->height(); + // update col/row indices + if ( ++cIdx == nCols ) + { + cIdx = 0; + rIdx++; + } + } + + // 2) arrange widgets inside cells + int insertX = 0, + insertY = 4; // 2 + 4*d->zoomFactor ? + cIdx = 0; + rIdx = 0; + for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) + { + PageViewItem * item = *iIt; + int cWidth = colWidth[ cIdx ], + rHeight = rowHeight[ rIdx ]; + // center widget inside 'cells' + item->moveTo( insertX + (cWidth - item->width()) / 2, + insertY + (rHeight - item->height()) / 2 ); + // advance col/row index + insertX += cWidth; + if ( ++cIdx == nCols ) + { + cIdx = 0; + rIdx++; + insertX = 0; + insertY += rHeight + 15; // 5 + 15*d->zoomFactor ? + } + } + + fullHeight = cIdx ? (insertY + rowHeight[ rIdx ] + 10) : insertY; + for ( int i = 0; i < nCols; i++ ) + fullWidth += colWidth[ i ]; + + delete [] colWidth; + delete [] rowHeight; + } + else // viewContinuous is FALSE + { + PageViewItem * currentItem = d->items[ QMAX( 0, (int)d->document->currentPage() ) ]; + + // setup varialbles for a 1(row) x N(columns) grid + int nCols = KpdfSettings::viewColumns(), + * colWidth = new int[ nCols ], + cIdx = 0; + fullHeight = viewportHeight; + for ( int i = 0; i < nCols; i++ ) + colWidth[ i ] = viewportWidth / nCols; + + // 1) find out maximum area extension for the pages + for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) + { + PageViewItem * item = *iIt; + if ( item == currentItem || (cIdx > 0 && cIdx < nCols) ) + { + // update internal page size (leaving a little margin in case of Fit* modes) + updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 8 ); + // find row's maximum height and column's max width + if ( item->width() + 6 > colWidth[ cIdx ] ) + colWidth[ cIdx ] = item->width() + 6; + if ( item->height() + 8 > fullHeight ) + fullHeight = item->height() + 8; + cIdx++; + } + } + + // 2) hide all widgets except the displayable ones and dispose those + int insertX = 0; + cIdx = 0; + for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) + { + PageViewItem * item = *iIt; + if ( item == currentItem || (cIdx > 0 && cIdx < nCols) ) + { + // center widget inside 'cells' + item->moveTo( insertX + (colWidth[ cIdx ] - item->width()) / 2, + (fullHeight - item->height()) / 2 ); + // advance col index + insertX += colWidth[ cIdx ]; + cIdx++; + } else + item->setGeometry( 0, 0, -1, -1 ); + } + + for ( int i = 0; i < nCols; i++ ) + fullWidth += colWidth[ i ]; + + delete [] colWidth; + } + + // 3) reset dirty state + d->dirtyLayout = false; + + // 4) update scrollview's contents size and recenter view + bool wasUpdatesEnabled = viewport()->isUpdatesEnabled(); + if ( fullWidth != contentsWidth() || fullHeight != contentsHeight() ) + { + // disable updates and resize the viewportContents + if ( wasUpdatesEnabled ) + viewport()->setUpdatesEnabled( false ); + resizeContents( fullWidth, fullHeight ); + // restore previous viewport if defined and updates enabled + if ( wasUpdatesEnabled ) + { + const DocumentViewport & vp = d->document->viewport(); + if ( vp.pageNumber >= 0 ) + { + int prevX = contentsX(), + prevY = contentsY(); + const QRect & geometry = d->items[ vp.pageNumber ]->geometry(); + double nX = vp.rePos.enabled ? vp.rePos.normalizedX : 0.5, + nY = vp.rePos.enabled ? vp.rePos.normalizedY : 0.0; + center( geometry.left() + ROUND( nX * (double)geometry.width() ), + geometry.top() + ROUND( nY * (double)geometry.height() ) ); + // center() usually moves the viewport, that requests pixmaps too. + // if that doesn't happen we have to request them by hand + if ( prevX == contentsX() && prevY == contentsY() ) + slotRequestVisiblePixmaps(); + } + // or else go to center page + else + center( fullWidth / 2, 0 ); + viewport()->setUpdatesEnabled( true ); + } + } + + // 5) update the whole viewport if updated enabled + if ( wasUpdatesEnabled ) + updateContents(); +} + +void PageView::slotRequestVisiblePixmaps( int newLeft, int newTop ) +{ + // if requests are blocked (because raised by an unwanted event), exit + if ( d->blockPixmapsRequest || d->viewportMoveActive ) + return; + + // precalc view limits for intersecting with page coords inside the lOOp + bool isEvent = newLeft != -1 && newTop != -1 && !d->blockViewport; + QRect viewportRect( isEvent ? newLeft : contentsX(), + isEvent ? newTop : contentsY(), + visibleWidth(), visibleHeight() ); + + // some variables used to determine the viewport + int nearPageNumber = -1; + double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0, + viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0, + focusedX = 0.5, + focusedY = 0.0, + minDistance = -1.0; + + // iterate over all items + d->visibleItems.clear(); + QValueList< PixmapRequest * > requestedPixmaps; + QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); + for ( ; iIt != iEnd; ++iIt ) + { + PageViewItem * i = *iIt; + + // if the item doesn't intersect the viewport, skip it + if ( !viewportRect.intersects( i->geometry() ) ) + continue; + + // add the item to the 'visible list' + d->visibleItems.push_back( i ); + + // if the item has not the right pixmap, add a request for it + if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) ) + { + PixmapRequest * p = new PixmapRequest( + PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRIO, true ); + requestedPixmaps.push_back( p ); + } + + // look for the item closest to viewport center and the relative + // position between the item and the viewport center + if ( isEvent ) + { + const QRect & geometry = i->geometry(); + // compute distance between item center and viewport center + double distance = hypot( (geometry.left() + geometry.right()) / 2 - viewportCenterX, + (geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); + if ( distance >= minDistance && nearPageNumber != -1 ) + continue; + nearPageNumber = i->pageNumber(); + minDistance = distance; + if ( geometry.height() > 0 && geometry.width() > 0 ) + { + focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); + focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); + } + } + } + + // if preloading is enabled, add the pages before and after in preloading + if ( !d->visibleItems.isEmpty() && + KpdfSettings::memoryLevel() != KpdfSettings::EnumMemoryLevel::Low && + KpdfSettings::enableThreading() ) + { + // as the requests are done in the order as they appear in the list, + // request first the next page and then the previous + + // add the page after the 'visible series' in preload + int tailRequest = d->visibleItems.last()->pageNumber() + 1; + if ( tailRequest < (int)d->items.count() ) + { + PageViewItem * i = d->items[ tailRequest ]; + // request the pixmap if not already present + if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) && i->width() > 0 ) + requestedPixmaps.push_back( new PixmapRequest( + PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRELOAD_PRIO, true ) ); + } + + // add the page before the 'visible series' in preload + int headRequest = d->visibleItems.first()->pageNumber() - 1; + if ( headRequest >= 0 ) + { + PageViewItem * i = d->items[ headRequest ]; + // request the pixmap if not already present + if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) && i->width() > 0 ) + requestedPixmaps.push_back( new PixmapRequest( + PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRELOAD_PRIO, true ) ); + } + } + + // send requests to the document + if ( !requestedPixmaps.isEmpty() ) + d->document->requestPixmaps( requestedPixmaps ); + + // if this functions was invoked by viewport events, send update to document + if ( isEvent && nearPageNumber != -1 ) + { + // determine the document viewport + DocumentViewport newViewport( nearPageNumber ); + newViewport.rePos.enabled = true; + newViewport.rePos.normalizedX = focusedX; + newViewport.rePos.normalizedY = focusedY; + // set the viewport to other observers + d->document->setViewport( newViewport , PAGEVIEW_ID); + } +} + +void PageView::slotMoveViewport() +{ + // converge to viewportMoveDest in 1 second + int diffTime = d->viewportMoveTime.elapsed(); + if ( diffTime >= 667 || !d->viewportMoveActive ) + { + center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); + d->viewportMoveTimer->stop(); + d->viewportMoveActive = false; + slotRequestVisiblePixmaps(); + verticalScrollBar()->setEnabled( true ); + horizontalScrollBar()->setEnabled( true ); + return; + } + + // move the viewport smoothly (kmplot: p(x)=x+x*(1-x)*(1-x)) + float convergeSpeed = (float)diffTime / 667.0, + x = ((float)visibleWidth() / 2.0) + contentsX(), + y = ((float)visibleHeight() / 2.0) + contentsY(), + diffX = (float)d->viewportMoveDest.x() - x, + diffY = (float)d->viewportMoveDest.y() - y; + convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); + center( (int)(x + diffX * convergeSpeed), + (int)(y + diffY * convergeSpeed ) ); +} + +void PageView::slotAutoScoll() +{ + // the first time create the timer + if ( !d->autoScrollTimer ) + { + d->autoScrollTimer = new QTimer( this ); + connect( d->autoScrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScoll() ) ); + } + + // if scrollIncrement is zero, stop the timer + if ( !d->scrollIncrement ) + { + d->autoScrollTimer->stop(); + return; + } + + // compute delay between timer ticks and scroll amount per tick + int index = abs( d->scrollIncrement ) - 1; // 0..9 + const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; + const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; + d->autoScrollTimer->changeInterval( scrollDelay[ index ] ); + scrollBy( 0, d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ] ); +} + +void PageView::slotDragScroll() +{ + scrollBy(d->dragScrollVector.x(), d->dragScrollVector.y()); + QPoint p = viewportToContents( mapFromGlobal( QCursor::pos() ) ); + selectionEndPoint( p.x(), p.y() ); +} + +void PageView::findAheadStop() +{ + d->typeAheadActive = false; + d->typeAheadString = ""; + d->messageWindow->display( i18n("Find stopped."), PageViewMessage::Find, 1000 ); + // it is needed to grab the keyboard becase people may have Space assigned to a + // accel and without grabbing the keyboard you can not vim-search for space + // because it activates the accel + releaseKeyboard(); +} + +void PageView::slotShowWelcome() +{ + // show initial welcome text + d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 ); +} + +void PageView::slotZoom() +{ + setFocus(); + updateZoom( ZoomFixed ); +} + +void PageView::slotZoomIn() +{ + updateZoom( ZoomIn ); +} + +void PageView::slotZoomOut() +{ + updateZoom( ZoomOut ); +} + +void PageView::slotFitToWidthToggled( bool on ) +{ + if ( on ) updateZoom( ZoomFitWidth ); +} + +void PageView::slotFitToPageToggled( bool on ) +{ + if ( on ) updateZoom( ZoomFitPage ); +} + +void PageView::slotFitToTextToggled( bool on ) +{ + if ( on ) updateZoom( ZoomFitText ); +} + +void PageView::slotTwoPagesToggled( bool on ) +{ + uint newColumns = on ? 2 : 1; + if ( KpdfSettings::viewColumns() != newColumns ) + { + KpdfSettings::setViewColumns( newColumns ); + KpdfSettings::writeConfig(); + if ( d->document->pages() > 0 ) + slotRelayoutPages(); + } +} + +void PageView::slotContinuousToggled( bool on ) +{ + if ( KpdfSettings::viewContinuous() != on ) + { + KpdfSettings::setViewContinuous( on ); + KpdfSettings::writeConfig(); + if ( d->document->pages() > 0 ) + slotRelayoutPages(); + } +} + +void PageView::slotSetMouseNormal() +{ + d->mouseMode = MouseNormal; + d->messageWindow->hide(); +} + +void PageView::slotSetMouseZoom() +{ + d->mouseMode = MouseZoom; + d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), PageViewMessage::Info, -1 ); +} + +void PageView::slotSetMouseSelect() +{ + d->mouseMode = MouseSelect; + d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), PageViewMessage::Info, -1 ); +} + +void PageView::slotSetMouseDraw() +{ + d->mouseMode = MouseEdit; + d->aMouseEdit->setChecked( true ); + d->messageWindow->hide(); +} + +void PageView::slotScrollUp() +{ + if ( d->scrollIncrement < -9 ) + return; + d->scrollIncrement--; + slotAutoScoll(); + setFocus(); +} + +void PageView::slotScrollDown() +{ + if ( d->scrollIncrement > 9 ) + return; + d->scrollIncrement++; + slotAutoScoll(); + setFocus(); +} +//END private SLOTS + +#include "pageview.moc" diff --git a/kpdf/ui/pageview.h b/kpdf/ui/pageview.h new file mode 100644 index 00000000..f6e40991 --- /dev/null +++ b/kpdf/ui/pageview.h @@ -0,0 +1,148 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es> * + * * + * With portions of code from kpdf/kpdf_pagewidget.h by: * + * Copyright (C) 2002 by Wilco Greven <greven@kde.org> * + * Copyright (C) 2003 by Christophe Devriese * + * <Christophe.Devriese@student.kuleuven.ac.be> * + * Copyright (C) 2003 by Laurent Montel <montel@kde.org> * + * Copyright (C) 2003 by Kurt Pfeifle <kpfeifle@danka.de> * + * * + * 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. * + ***************************************************************************/ +// This file follows coding style described in kdebase/kicker/HACKING + +#ifndef _KPDF_PAGEVIEW_H_ +#define _KPDF_PAGEVIEW_H_ + +#include <qscrollview.h> +#include <qvaluevector.h> +#include "core/observer.h" + +class KURL; +class KActionCollection; + +class KPDFDocument; +class PageViewItem; +class PageViewPrivate; +class PageViewTip; + +/** + * @short The main view. Handles zoom and continuous mode.. oh, and page + * @short display of course :-) + * ... + */ +class PageView : public QScrollView, public DocumentObserver +{ + Q_OBJECT + + friend class PageViewTip; + + public: + PageView( QWidget *parent, KPDFDocument *document ); + ~PageView(); + + // Zoom mode ( last 4 are internally used only! ) + enum ZoomMode { ZoomFixed, ZoomFitWidth, ZoomFitPage, ZoomFitText, + ZoomIn, ZoomOut, ZoomRefreshCurrent }; + enum MouseMode { MouseNormal, MouseZoom, MouseSelect, MouseEdit }; + + // create actions that interact with this widget + void setupActions( KActionCollection * collection ); + + // used from RMB menu + bool canFitPageWidth(); + void fitPageWidth( int page ); + + // inherited from DocumentObserver + uint observerId() const { return PAGEVIEW_ID; } + void notifySetup( const QValueVector< KPDFPage * > & pages, bool documentChanged ); + void notifyViewportChanged( bool smoothMove ); + void notifyPageChanged( int pageNumber, int changedFlags ); + void notifyContentsCleared( int changedFlags ); + bool canUnloadPixmap( int pageNum ); + + void showText( const QString &text, int ms ); + + signals: + void urlDropped( const KURL& ); + void rightClick( const KPDFPage *, const QPoint & ); + + protected: + // main draw loop, draws pageViews on viewport + void viewportPaintEvent( QPaintEvent * pe ); + void viewportResizeEvent( QResizeEvent* ); + + // mouse / keyboard events + void keyPressEvent( QKeyEvent* ); + void imEndEvent( QIMEvent * ); + void contentsMouseMoveEvent( QMouseEvent* ); + void contentsMousePressEvent( QMouseEvent* ); + void contentsMouseReleaseEvent( QMouseEvent* ); + void wheelEvent( QWheelEvent* ); + + // drag and drop related events + void dragEnterEvent( QDragEnterEvent* ); + void dropEvent( QDropEvent* ); + + private: + // draw items on the opened qpainter + void paintItems( QPainter * p, const QRect & clipRect ); + // update item width and height using current zoom parameters + void updateItemSize( PageViewItem * item, int columnWidth, int rowHeight ); + // return the widget placed on a certain point or 0 if clicking on empty space + PageViewItem * pickItemOnPoint( int x, int y ); + // start / modify / clear selection rectangle + void selectionStart( int x, int y, const QColor & color, bool aboveAll = false ); + void selectionEndPoint( int x, int y ); + void selectionClear(); + // update internal zoom values and end in a slotRelayoutPages(); + void updateZoom( ZoomMode newZm ); + // update the text on the label using global zoom value or current page's one + void updateZoomText(); + // updates cursor + void updateCursor( const QPoint &p ); + // does the type ahead search + void doTypeAheadSearch(); + + // don't want to expose classes in here + class PageViewPrivate * d; + + private slots: + // activated either directly or via QTimer on the viewportResizeEvent + void slotRelayoutPages(); + // activated either directly or via the contentsMoving(int,int) signal + void slotRequestVisiblePixmaps( int left = -1, int top = -1 ); + // activated by the viewport move timer + void slotMoveViewport(); + // activated by the autoscroll timer (Shift+Up/Down keys) + void slotAutoScoll(); + // activated by the dragScroll timer + void slotDragScroll(); + // type-ahead find timeout + void findAheadStop(); + // show the welcome message + void slotShowWelcome(); + + // connected to local actions (toolbar, menu, ..) + void slotZoom(); + void slotZoomIn(); + void slotZoomOut(); + void slotFitToWidthToggled( bool ); + void slotFitToPageToggled( bool ); + void slotFitToTextToggled( bool ); + void slotTwoPagesToggled( bool ); + void slotContinuousToggled( bool ); + void slotSetMouseNormal(); + void slotSetMouseZoom(); + void slotSetMouseSelect(); + void slotSetMouseDraw(); + void slotScrollUp(); + void slotScrollDown(); +}; + +#endif diff --git a/kpdf/ui/pageviewutils.cpp b/kpdf/ui/pageviewutils.cpp new file mode 100644 index 00000000..b9d84137 --- /dev/null +++ b/kpdf/ui/pageviewutils.cpp @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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 <qbitmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qtimer.h> +#include <kapplication.h> +#include <kimageeffect.h> +#include <kiconloader.h> + +// local includes +#include "pageviewutils.h" +#include "core/page.h" +#include "conf/settings.h" + +PageViewMessage::PageViewMessage( QWidget * parent ) + : QWidget( parent, "pageViewMessage" ), m_timer( 0 ) +{ + setFocusPolicy( NoFocus ); + setBackgroundMode( NoBackground ); + setPaletteBackgroundColor(kapp->palette().color(QPalette::Active, QColorGroup::Background)); + // if the layout is LtR, we can safely place it in the right position + if ( !QApplication::reverseLayout() ) + move( 10, 10 ); + resize( 0, 0 ); + hide(); +} + +void PageViewMessage::display( const QString & message, Icon icon, int durationMs ) +// give to Caesar what Caesar owns: code taken from Amarok's osd.h/.cpp +// "redde (reddite, pl.) cesari quae sunt cesaris", just btw. ;) +{ + if ( !KpdfSettings::showOSD() ) + { + hide(); + return; + } + + // determine text rectangle + QRect textRect = fontMetrics().boundingRect( message ); + textRect.moveBy( -textRect.left(), -textRect.top() ); + textRect.addCoords( 0, 0, 2, 2 ); + int width = textRect.width(), + height = textRect.height(), + textXOffset = 0, + iconXOffset = 0, + shadowOffset = 1; + + // load icon (if set) and update geometry + QPixmap symbol; + if ( icon != None ) + { + switch ( icon ) + { + case Find: + symbol = SmallIcon( "viewmag" ); + break; + case Error: + symbol = SmallIcon( "messagebox_critical" ); + break; + case Warning: + symbol = SmallIcon( "messagebox_warning" ); + break; + default: + symbol = SmallIcon( "messagebox_info" ); + break; + } + if ( QApplication::reverseLayout() ) + { + iconXOffset = 2 + textRect.width(); + } + else + { + textXOffset = 2 + symbol.width(); + } + width += 2 + symbol.width(); + height = QMAX( height, symbol.height() ); + } + QRect geometry( 0, 0, width + 10, height + 8 ); + + // resize pixmap, mask and widget + static QBitmap mask; + mask.resize( geometry.size() ); + m_pixmap.resize( geometry.size() ); + resize( geometry.size() ); + + // create and set transparency mask + QPainter maskPainter( &mask); + mask.fill( Qt::black ); + maskPainter.setBrush( Qt::white ); + maskPainter.drawRoundRect( geometry, 1600 / geometry.width(), 1600 / geometry.height() ); + setMask( mask ); + + // draw background + QPainter bufferPainter( &m_pixmap ); + bufferPainter.setPen( Qt::black ); + bufferPainter.setBrush( paletteBackgroundColor() ); + bufferPainter.drawRoundRect( geometry, 1600 / geometry.width(), 1600 / geometry.height() ); + + // draw icon if present + if ( !symbol.isNull() ) + bufferPainter.drawPixmap( 5 + iconXOffset, 4, symbol, 0, 0, symbol.width(), symbol.height() ); + + // draw shadow and text + int yText = geometry.height() - height / 2; + bufferPainter.setPen( paletteBackgroundColor().dark( 115 ) ); + bufferPainter.drawText( 5 + textXOffset + shadowOffset, yText + 1, message ); + bufferPainter.setPen( foregroundColor() ); + bufferPainter.drawText( 5 + textXOffset, yText, message ); + + // if the layout is RtL, we can move it to the right place only after we + // know how much size it will take + if ( QApplication::reverseLayout() ) + move( parentWidget()->width() - geometry.width() - 10, 10 ); + + // show widget and schedule a repaint + show(); + update(); + + // close the message window after given mS + if ( durationMs > 0 ) + { + if ( !m_timer ) + { + m_timer = new QTimer( this ); + connect( m_timer, SIGNAL( timeout() ), SLOT( hide() ) ); + } + m_timer->start( durationMs, true ); + } else if ( m_timer ) + m_timer->stop(); +} + +void PageViewMessage::paintEvent( QPaintEvent * e ) +{ + QPainter p( this ); + p.drawPixmap( e->rect().topLeft(), m_pixmap, e->rect() ); +} + +void PageViewMessage::mousePressEvent( QMouseEvent * /*e*/ ) +{ + if ( m_timer ) + m_timer->stop(); + hide(); +} + + + +PageViewItem::PageViewItem( const KPDFPage * page ) + : m_page( page ), m_zoomFactor( 1.0 ) +{ +} + +const KPDFPage * PageViewItem::page() const +{ + return m_page; +} + +int PageViewItem::pageNumber() const +{ + return m_page->number(); +} + +const QRect& PageViewItem::geometry() const +{ + return m_geometry; +} + +int PageViewItem::width() const +{ + return m_geometry.width(); +} + +int PageViewItem::height() const +{ + return m_geometry.height(); +} + +double PageViewItem::zoomFactor() const +{ + return m_zoomFactor; +} + +void PageViewItem::setGeometry( int x, int y, int width, int height ) +{ + m_geometry.setRect( x, y, width, height ); +} + +void PageViewItem::setWHZ( int w, int h, double z ) +{ + m_geometry.setWidth( w ); + m_geometry.setHeight( h ); + m_zoomFactor = z; +} + +void PageViewItem::moveTo( int x, int y ) +{ + m_geometry.moveLeft( x ); + m_geometry.moveTop( y ); +} diff --git a/kpdf/ui/pageviewutils.h b/kpdf/ui/pageviewutils.h new file mode 100644 index 00000000..bde9b8d3 --- /dev/null +++ b/kpdf/ui/pageviewutils.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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. * + ***************************************************************************/ + +#ifndef _PAGEVIEW_UTILS_H +#define _PAGEVIEW_UTILS_H + +#include <qwidget.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qrect.h> + +class QTimer; + +class PageView; +class KPDFPage; + +/** + * @short PageViewItem represents graphically a kpdfpage into the PageView. + * + * It has methods for settings Item's geometry and other visual properties such + * as the individual zoom factor. + */ +class PageViewItem +{ + public: + PageViewItem( const KPDFPage * page ); + + const KPDFPage * page() const; + int pageNumber() const; + const QRect& geometry() const; + int width() const; + int height() const; + double zoomFactor() const; + + void setGeometry( int x, int y, int width, int height ); + void setWHZ( int w, int h, double zoom ); + void moveTo( int x, int y ); + + private: + const KPDFPage * m_page; + double m_zoomFactor; + QRect m_geometry; +}; + + +/** + * @short A widget that displays messages in the top-left corner. + */ +class PageViewMessage : public QWidget +{ + public: + PageViewMessage( QWidget * parent ); + + enum Icon { None, Info, Warning, Error, Find }; + void display( const QString & message, Icon icon = Info, int durationMs = 4000 ); + + protected: + void paintEvent( QPaintEvent * e ); + void mousePressEvent( QMouseEvent * e ); + + private: + QPixmap m_pixmap; + QTimer * m_timer; +}; + +#endif diff --git a/kpdf/ui/presentationwidget.cpp b/kpdf/ui/presentationwidget.cpp new file mode 100644 index 00000000..c57e2f95 --- /dev/null +++ b/kpdf/ui/presentationwidget.cpp @@ -0,0 +1,1330 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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 <qimage.h> +#include <qpainter.h> +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qtooltip.h> +#include <kaccel.h> +#include <kactioncollection.h> +#include <kapplication.h> +#include <kcursor.h> +#include <ktoolbar.h> +#include <kdebug.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kimageeffect.h> +#include <kmessagebox.h> +#include <kwin.h> + +// system includes +#include <stdlib.h> +#include <math.h> + +// local includes +#include "presentationwidget.h" +#include "pagepainter.h" +#include "core/generator.h" +#include "core/page.h" +#include "core/link.h" +#include "conf/settings.h" + + +// comment this to disable the top-right progress indicator +#define ENABLE_PROGRESS_OVERLAY + + +// a frame contains a pointer to the page object, its geometry and the +// transition effect to the next frame +struct PresentationFrame +{ + const KPDFPage * page; + QRect geometry; +}; + + +PresentationWidget::PresentationWidget( QWidget * parent, KPDFDocument * doc ) + : QDialog( parent, "presentationWidget", true, WDestructiveClose | WStyle_NoBorder), + m_pressedLink( 0 ), m_handCursor( false ), m_document( doc ), m_frameIndex( -1 ) +{ + // set look and geometry + setBackgroundMode( Qt::NoBackground ); + + m_width = -1; + + m_accel = new KAccel( this, this, "presentationmode-accel" ); + + // show widget and take control + showFullScreen(); + + // misc stuff + setMouseTracking( true ); + m_transitionTimer = new QTimer( this ); + connect( m_transitionTimer, SIGNAL( timeout() ), this, SLOT( slotTransitionStep() ) ); + m_overlayHideTimer = new QTimer( this ); + connect( m_overlayHideTimer, SIGNAL( timeout() ), this, SLOT( slotHideOverlay() ) ); + m_nextPageTimer = new QTimer( this ); + connect( m_nextPageTimer, SIGNAL( timeout() ), this, SLOT( slotNextPage() ) ); + + // handle cursor appearance as specified in configuration + if ( KpdfSettings::slidesCursor() == KpdfSettings::EnumSlidesCursor::HiddenDelay ) + { + KCursor::setAutoHideCursor( this, true ); + KCursor::setHideCursorDelay( 3000 ); + } + else if ( KpdfSettings::slidesCursor() == KpdfSettings::EnumSlidesCursor::Hidden ) + { + setCursor( KCursor::blankCursor() ); + } +} + +PresentationWidget::~PresentationWidget() +{ + // remove this widget from document observer + m_document->removeObserver( this ); + + // delete frames + QValueVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); + for ( ; fIt != fEnd; ++fIt ) + delete *fIt; +} + +void PresentationWidget::setupActions( KActionCollection * ac ) +{ + m_accel->insert( "previous_page", ac->action( "previous_page" )->shortcut(), this, SLOT( slotPrevPage() ), false, true ); + m_accel->insert( "next_page", ac->action( "next_page" )->shortcut(), this, SLOT( slotNextPage() ), false, true ); + m_accel->insert( "first_page", ac->action( "first_page" )->shortcut(), this, SLOT( slotFirstPage() ), false, true ); + m_accel->insert( "last_page", ac->action( "last_page" )->shortcut(), this, SLOT( slotLastPage() ), false, true ); + m_accel->insert( "presentation", ac->action( "presentation" )->shortcut(), this, SLOT( close() ), false, true ); +} + +void PresentationWidget::notifySetup( const QValueVector< KPDFPage * > & pageSet, bool /*documentChanged*/ ) +{ + // delete previous frames (if any (shouldn't be)) + QValueVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); + for ( ; fIt != fEnd; ++fIt ) + delete *fIt; + if ( !m_frames.isEmpty() ) + kdWarning() << "Frames setup changed while a Presentation is in progress." << endl; + m_frames.clear(); + + // create the new frames + QValueVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end(); + float screenRatio = (float)m_height / (float)m_width; + for ( ; setIt != setEnd; ++setIt ) + { + PresentationFrame * frame = new PresentationFrame(); + frame->page = *setIt; + // calculate frame geometry keeping constant aspect ratio + float pageRatio = frame->page->ratio(); + int pageWidth = m_width, + pageHeight = m_height; + if ( pageRatio > screenRatio ) + pageWidth = (int)( (float)pageHeight / pageRatio ); + else + pageHeight = (int)( (float)pageWidth * pageRatio ); + frame->geometry.setRect( (m_width - pageWidth) / 2, + (m_height - pageHeight) / 2, + pageWidth, pageHeight ); + // add the frame to the vector + m_frames.push_back( frame ); + } + + // get metadata from the document + m_metaStrings.clear(); + const DocumentInfo * info = m_document->documentInfo(); + if ( info ) + { + if ( !info->get( "title" ).isNull() ) + m_metaStrings += i18n( "Title: %1" ).arg( info->get( "title" ) ); + if ( !info->get( "author" ).isNull() ) + m_metaStrings += i18n( "Author: %1" ).arg( info->get( "author" ) ); + } + m_metaStrings += i18n( "Pages: %1" ).arg( m_document->pages() ); + m_metaStrings += i18n( "Click to begin" ); +} + +void PresentationWidget::notifyViewportChanged( bool /*smoothMove*/ ) +{ + // discard notifications if displaying the summary + if ( m_frameIndex == -1 && KpdfSettings::slidesShowSummary() ) + return; + + // display the current page + changePage( m_document->viewport().pageNumber ); + + // auto advance to the next page if set + if ( KpdfSettings::slidesAdvance() ) + m_nextPageTimer->start( KpdfSettings::slidesAdvanceTime() * 1000 ); +} + +void PresentationWidget::notifyPageChanged( int pageNumber, int changedFlags ) +{ + // check if it's the last requested pixmap. if so update the widget. + if ( (changedFlags & DocumentObserver::Pixmap) && pageNumber == m_frameIndex ) + generatePage(); +} + +bool PresentationWidget::canUnloadPixmap( int pageNumber ) +{ + // can unload all pixmaps except for the currently visible one + return pageNumber != m_frameIndex; +} + + +// <widget events> +/* This hack was here to fix 103718 but it's no longer necessary on KDE 3.5 and Lubos asked me to remove it +bool PresentationWidget::event ( QEvent * e ) +{ + if (e -> type() == QEvent::WindowDeactivate) KWin::clearState(winId(), NET::StaysOnTop); + else if (e -> type() == QEvent::WindowActivate) KWin::setState(winId(), NET::StaysOnTop); + return QDialog::event(e); +} +*/ + +void PresentationWidget::keyPressEvent( QKeyEvent * e ) +{ + if (m_width == -1) return; + + if ( e->key() == Key_Left || e->key() == Key_Backspace || e->key() == Key_Prior ) + slotPrevPage(); + else if ( e->key() == Key_Right || e->key() == Key_Space || e->key() == Key_Next ) + slotNextPage(); + else if ( e->key() == Key_Home ) + slotFirstPage(); + else if ( e->key() == Key_End ) + slotLastPage(); + else if ( e->key() == Key_Escape ) + { + if ( m_topBar->isShown() ) + m_topBar->hide(); + else + close(); + } +} + +void PresentationWidget::wheelEvent( QWheelEvent * e ) +{ + // performance note: don't remove the clipping + int div = e->delta() / 120; + if ( div > 0 ) + { + if ( div > 3 ) + div = 3; + while ( div-- ) + slotPrevPage(); + } + else if ( div < 0 ) + { + if ( div < -3 ) + div = -3; + while ( div++ ) + slotNextPage(); + } +} + +void PresentationWidget::mousePressEvent( QMouseEvent * e ) +{ + // pressing left button + if ( e->button() == Qt::LeftButton ) + { + // if pressing on a link, skip other checks + if ( ( m_pressedLink = getLink( e->x(), e->y() ) ) ) + return; + + // handle clicking on top-right overlay + if ( m_overlayGeometry.contains( e->pos() ) ) + { + overlayClick( e->pos() ); + return; + } + + // if no other actions, go to next page + slotNextPage(); + } + // pressing right button + else if ( e->button() == Qt::RightButton ) + slotPrevPage(); +} + +void PresentationWidget::mouseReleaseEvent( QMouseEvent * e ) +{ + // if releasing on the same link we pressed over, execute it + if ( m_pressedLink && e->button() == Qt::LeftButton ) + { + const KPDFLink * link = getLink( e->x(), e->y() ); + if ( link == m_pressedLink ) + m_document->processLink( link ); + m_pressedLink = 0; + } +} + +void PresentationWidget::mouseMoveEvent( QMouseEvent * e ) +{ + // safety check + if ( m_width == -1 ) + return; + + // update cursor and tooltip if hovering a link + if ( KpdfSettings::slidesCursor() != KpdfSettings::EnumSlidesCursor::Hidden ) + testCursorOnLink( e->x(), e->y() ); + + if ( m_topBar->isShown() ) + { + // hide a shown bar when exiting the area + if ( e->y() > ( m_topBar->height() + 1 ) ) + m_topBar->hide(); + } + else + { + // show the bar if reaching top 2 pixels + if ( e->y() <= (geometry().top() + 1) ) + m_topBar->show(); + // handle "dragging the wheel" if clicking on its geometry + else if ( e->state() == Qt::LeftButton && m_overlayGeometry.contains( e->pos() ) ) + overlayClick( e->pos() ); + } +} + +void PresentationWidget::paintEvent( QPaintEvent * pe ) +{ + if (m_width == -1) + { + QRect d = KGlobalSettings::desktopGeometry(this); + m_width = d.width(); + m_height = d.height(); + + // create top toolbar + m_topBar = new KToolBar( this, "presentationBar" ); + m_topBar->setIconSize( 32 ); + m_topBar->setMovingEnabled( false ); + m_topBar->insertButton( QApplication::reverseLayout() ? "1rightarrow" : "1leftarrow", 2, SIGNAL( clicked() ), this, SLOT( slotPrevPage() ) ); + m_topBar->insertButton( QApplication::reverseLayout() ? "1leftarrow" : "1rightarrow", 3, SIGNAL( clicked() ), this, SLOT( slotNextPage() ) ); + m_topBar->insertButton( "exit", 1, SIGNAL( clicked() ), this, SLOT( close() ) ); + m_topBar->setGeometry( 0, 0, m_width, 32 + 10 ); + m_topBar->alignItemRight( 1 ); + m_topBar->hide(); + // change topbar background color + QPalette p = m_topBar->palette(); + p.setColor( QPalette::Active, QColorGroup::Button, Qt::gray ); + p.setColor( QPalette::Active, QColorGroup::Background, Qt::darkGray ); + m_topBar->setPalette( p ); + + // register this observer in document. events will come immediately + m_document->addObserver( this ); + + // show summary if requested + if ( KpdfSettings::slidesShowSummary() ) + generatePage(); + + KMessageBox::information(this, i18n("There are two ways of exiting presentation mode, you can press either ESC key or click with the quit button that appears when placing the mouse in the top-right corner. Of course you can cycle windows (Alt+TAB by default)"), QString::null, "presentationInfo"); + } + + // check painting rect consistancy + QRect r = pe->rect().intersect( geometry() ); + if ( r.isNull() || m_lastRenderedPixmap.isNull() ) + return; + + // blit the pixmap to the screen + QMemArray<QRect> allRects = pe->region().rects(); + uint numRects = allRects.count(); + for ( uint i = 0; i < numRects; i++ ) + { + const QRect & r = allRects[i]; + if ( !r.isValid() ) + continue; +#ifdef ENABLE_PROGRESS_OVERLAY + if ( KpdfSettings::slidesShowProgress() && r.intersects( m_overlayGeometry ) ) + { + // backbuffer the overlay operation + QPixmap backPixmap( r.size() ); + QPainter pixPainter( &backPixmap ); + + // first draw the background on the backbuffer + pixPainter.drawPixmap( QPoint(0,0), m_lastRenderedPixmap, r ); + + // then blend the overlay (a piece of) over the background + QRect ovr = m_overlayGeometry.intersect( r ); + pixPainter.drawPixmap( ovr.left() - r.left(), ovr.top() - r.top(), + m_lastRenderedOverlay, ovr.left() - m_overlayGeometry.left(), + ovr.top() - m_overlayGeometry.top(), ovr.width(), ovr.height() ); + + // finally blit the pixmap to the screen + pixPainter.end(); + bitBlt( this, r.topLeft(), &backPixmap, backPixmap.rect() ); + } else +#endif + // copy the rendered pixmap to the screen + bitBlt( this, r.topLeft(), &m_lastRenderedPixmap, r ); + } +} +// </widget events> + + +const KPDFLink * PresentationWidget::getLink( int x, int y, QRect * geometry ) const +{ + // no links on invalid pages + if ( geometry && !geometry->isNull() ) + geometry->setRect( 0, 0, -1, -1 ); + if ( m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size() ) + return 0; + + // get frame, page and geometry + const PresentationFrame * frame = m_frames[ m_frameIndex ]; + const KPDFPage * page = frame->page; + const QRect & frameGeometry = frame->geometry; + + // compute normalized x and y + double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width(); + double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height(); + + // no links outside the pages + if ( nx < 0 || nx > 1 || ny < 0 || ny > 1 ) + return 0; + + // check if 1) there is an object and 2) it's a link + const ObjectRect * object = page->hasObject( ObjectRect::Link, nx, ny ); + if ( !object ) + return 0; + + // compute link geometry if destination rect present + if ( geometry ) + { + *geometry = object->geometry( frameGeometry.width(), frameGeometry.height() ); + geometry->moveBy( frameGeometry.left(), frameGeometry.top() ); + } + + // return the link pointer + return (KPDFLink *)object->pointer(); +} + +void PresentationWidget::testCursorOnLink( int x, int y ) +{ + // get rect + QRect linkRect; + const KPDFLink * link = getLink( x, y, &linkRect ); + + // only react on changes (in/out from a link) + if ( (link && !m_handCursor) || (!link && m_handCursor) ) + { + // change cursor shape + m_handCursor = link != 0; + setCursor( m_handCursor ? KCursor::handCursor() : KCursor::arrowCursor()); + + // set tooltip over link's rect + QString tip = link ? link->linkTip() : QString::null; + if ( m_handCursor && !tip.isEmpty() ) + QToolTip::add( this, linkRect, tip ); + } +} + +void PresentationWidget::overlayClick( const QPoint & position ) +{ + // clicking the progress indicator + int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, + yPos = m_overlayGeometry.height() / 2 - position.y(); + if ( !xPos && !yPos ) + return; + + // compute angle relative to indicator (note coord transformation) + float angle = 0.5 + 0.5 * atan2( -xPos, -yPos ) / M_PI; + int pageIndex = (int)( angle * ( m_frames.count() - 1 ) + 0.5 ); + + // go to selected page + changePage( pageIndex ); +} + +void PresentationWidget::changePage( int newPage ) +{ + if ( m_frameIndex == newPage ) + return; + + // check if pixmap exists or else request it + m_frameIndex = newPage; + PresentationFrame * frame = m_frames[ m_frameIndex ]; + int pixW = frame->geometry.width(); + int pixH = frame->geometry.height(); + + // 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 ) ) + { + // operation will take long: set busy cursor + QApplication::setOverrideCursor( KCursor::workingCursor() ); + // request the pixmap + QValueList< PixmapRequest * > requests; + requests.push_back( new PixmapRequest( PRESENTATION_ID, m_frameIndex, pixW, pixH, PRESENTATION_PRIO ) ); + // restore cursor + QApplication::restoreOverrideCursor(); + // ask for next and previous page if not in low memory usage setting + if (KpdfSettings::memoryLevel() != KpdfSettings::EnumMemoryLevel::Low && KpdfSettings::enableThreading()) { + if (newPage + 1 < (int)m_document->pages()) + { + PresentationFrame *nextFrame = m_frames[ newPage + 1 ]; + pixW = nextFrame->geometry.width(); + pixH = nextFrame->geometry.height(); + if ( !nextFrame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) ) + requests.push_back( new PixmapRequest( PRESENTATION_ID, newPage + 1, pixW, pixH, PRESENTATION_PRELOAD_PRIO, true ) ); + } + if (newPage - 1 >= 0) + { + PresentationFrame *prevFrame = m_frames[ newPage - 1 ]; + pixW = prevFrame->geometry.width(); + pixH = prevFrame->geometry.height(); + if ( !prevFrame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) ) + requests.push_back( new PixmapRequest( PRESENTATION_ID, newPage - 1, pixW, pixH, PRESENTATION_PRELOAD_PRIO, true ) ); + } + } + m_document->requestPixmaps( requests ); + } + else + { + // make the background pixmap + generatePage(); + } + + // set a new viewport in document if page number differs + if ( m_frameIndex != -1 && m_frameIndex != m_document->viewport().pageNumber ) + m_document->setViewportPage( m_frameIndex, PRESENTATION_ID ); +} + +void PresentationWidget::generatePage() +{ + if ( m_lastRenderedPixmap.isNull() ) + m_lastRenderedPixmap.resize( m_width, m_height ); + + // opens the painter over the pixmap + QPainter pixmapPainter; + pixmapPainter.begin( &m_lastRenderedPixmap ); + // generate welcome page + if ( m_frameIndex == -1 ) + generateIntroPage( pixmapPainter ); + // generate a normal pixmap with extended margin filling + if ( m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages() ) + generateContentsPage( m_frameIndex, pixmapPainter ); + pixmapPainter.end(); + + // generate the top-right corner overlay +#ifdef ENABLE_PROGRESS_OVERLAY + if ( KpdfSettings::slidesShowProgress() && m_frameIndex != -1 ) + generateOverlay(); +#endif + + // start transition on pages that have one + const KPDFPageTransition * transition = m_frameIndex != -1 ? + m_frames[ m_frameIndex ]->page->getTransition() : 0; + if ( transition ) + initTransition( transition ); + else { + KPDFPageTransition trans = defaultTransition(); + initTransition( &trans ); + } + + // update cursor + tooltip + if ( KpdfSettings::slidesCursor() != KpdfSettings::EnumSlidesCursor::Hidden ) + { + QPoint p = mapFromGlobal( QCursor::pos() ); + testCursorOnLink( p.x(), p.y() ); + } +} + +void PresentationWidget::generateIntroPage( QPainter & p ) +{ + // use a vertical gray gradient background + int blend1 = m_height / 10, + blend2 = 9 * m_height / 10; + int baseTint = Qt::gray.red(); + for ( int i = 0; i < m_height; i++ ) + { + int k = baseTint; + if ( i < blend1 ) + k -= (int)( baseTint * (i-blend1)*(i-blend1) / (float)(blend1 * blend1) ); + if ( i > blend2 ) + k += (int)( (255-baseTint) * (i-blend2)*(i-blend2) / (float)(blend1 * blend1) ); + p.fillRect( 0, i, m_width, 1, QColor( k, k, k ) ); + } + + // draw kpdf logo in the four corners + QPixmap logo = DesktopIcon( "kpdf", 64 ); + if ( !logo.isNull() ) + { + p.drawPixmap( 5, 5, logo ); + p.drawPixmap( m_width - 5 - logo.width(), 5, logo ); + p.drawPixmap( m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo ); + p.drawPixmap( 5, m_height - 5 - logo.height(), logo ); + } + + // draw metadata text (the last line is 'click to begin') + int strNum = m_metaStrings.count(), + strHeight = m_height / ( strNum + 4 ), + fontHeight = 2 * strHeight / 3; + QFont font( p.font() ); + font.setPixelSize( fontHeight ); + QFontMetrics metrics( font ); + for ( int i = 0; i < strNum; i++ ) + { + // set a font to fit text width + float wScale = (float)metrics.boundingRect( m_metaStrings[i] ).width() / (float)m_width; + QFont f( font ); + if ( wScale > 1.0 ) + f.setPixelSize( (int)( (float)fontHeight / (float)wScale ) ); + p.setFont( f ); + + // text shadow + p.setPen( Qt::darkGray ); + p.drawText( 2, m_height / 4 + strHeight * i + 2, m_width, strHeight, + AlignHCenter | AlignVCenter, m_metaStrings[i] ); + // text body + p.setPen( 128 + (127 * i) / strNum ); + p.drawText( 0, m_height / 4 + strHeight * i, m_width, strHeight, + AlignHCenter | AlignVCenter, m_metaStrings[i] ); + } +} + +void PresentationWidget::generateContentsPage( int pageNum, QPainter & p ) +{ + PresentationFrame * frame = m_frames[ pageNum ]; + + // translate painter and contents rect + QRect geom( frame->geometry ); + p.translate( geom.left(), geom.top() ); + geom.moveBy( -geom.left(), -geom.top() ); + + // draw the page using the shared PagePainter class + int flags = PagePainter::Accessibility; + PagePainter::paintPageOnPainter( frame->page, PRESENTATION_ID, flags, + &p, geom, geom.width(), geom.height() ); + + // restore painter + p.translate( -frame->geometry.left(), -frame->geometry.top() ); + + // fill unpainted areas with background color + QRegion unpainted( QRect( 0, 0, m_width, m_height ) ); + QMemArray<QRect> rects = unpainted.subtract( frame->geometry ).rects(); + for ( uint i = 0; i < rects.count(); i++ ) + { + const QRect & r = rects[i]; + p.fillRect( r, KpdfSettings::slidesBackgroundColor() ); + } +} + +// from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation) +inline int qt_div255(int x) { return (x + (x>>8) + 0x80) >> 8; } +void PresentationWidget::generateOverlay() +{ +#ifdef ENABLE_PROGRESS_OVERLAY + // calculate overlay geometry and resize pixmap if needed + int side = m_width / 16; + m_overlayGeometry.setRect( m_width - side - 4, 4, side, side ); + if ( m_lastRenderedOverlay.width() != side ) + m_lastRenderedOverlay.resize( side, side ); + + // note: to get a sort of antialiasing, we render the pixmap double sized + // and the resulting image is smoothly scaled down. So here we open a + // painter on the double sized pixmap. + side *= 2; + QPixmap doublePixmap( side, side ); + doublePixmap.fill( Qt::black ); + QPainter pixmapPainter( &doublePixmap ); + + // draw PIE SLICES in blue levels (the levels will then be the alpha component) + int pages = m_document->pages(); + if ( pages > 28 ) + { // draw continuous slices + int degrees = (int)( 360 * (float)(m_frameIndex + 1) / (float)pages ); + pixmapPainter.setPen( 0x05 ); + pixmapPainter.setBrush( 0x40 ); + pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 ); + pixmapPainter.setPen( 0x40 ); + pixmapPainter.setBrush( 0xF0 ); + pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, -degrees*16 ); + } + else + { // draw discrete slices + float oldCoord = -90; + for ( int i = 0; i < pages; i++ ) + { + float newCoord = -90 + 360 * (float)(i + 1) / (float)pages; + pixmapPainter.setPen( i <= m_frameIndex ? 0x40 : 0x05 ); + pixmapPainter.setBrush( i <= m_frameIndex ? 0xF0 : 0x40 ); + pixmapPainter.drawPie( 2, 2, side - 4, side - 4, + (int)( -16*(oldCoord + 1) ), (int)( -16*(newCoord - (oldCoord + 2)) ) ); + oldCoord = newCoord; + } + } + int circleOut = side / 4; + pixmapPainter.setPen( Qt::black ); + pixmapPainter.setBrush( Qt::black ); + pixmapPainter.drawEllipse( circleOut, circleOut, side - 2*circleOut, side - 2*circleOut ); + + // draw TEXT using maximum opacity + QFont f( pixmapPainter.font() ); + f.setPixelSize( side / 4 ); + pixmapPainter.setFont( f ); + pixmapPainter.setPen( 0xFF ); + // use a little offset to prettify output + pixmapPainter.drawText( 2, 2, side, side, Qt::AlignCenter, QString::number( m_frameIndex + 1 ) ); + + // end drawing pixmap and halve image + pixmapPainter.end(); + QImage image( doublePixmap.convertToImage().smoothScale( side / 2, side / 2 ) ); + image.setAlphaBuffer( true ); + + // draw circular shadow using the same technique + doublePixmap.fill( Qt::black ); + pixmapPainter.begin( &doublePixmap ); + pixmapPainter.setPen( 0x40 ); + pixmapPainter.setBrush( 0x80 ); + pixmapPainter.drawEllipse( 0, 0, side, side ); + pixmapPainter.end(); + QImage shadow( doublePixmap.convertToImage().smoothScale( side / 2, side / 2 ) ); + + // generate a 2 colors pixmap using mixing shadow (made with highlight color) + // and image (made with highlightedText color) + QColor color = palette().active().highlightedText(); + int red = color.red(), green = color.green(), blue = color.blue(); + color = palette().active().highlight(); + int sRed = color.red(), sGreen = color.green(), sBlue = color.blue(); + // pointers + unsigned int * data = (unsigned int *)image.bits(), + * shadowData = (unsigned int *)shadow.bits(), + pixels = image.width() * image.height(); + // cache data (reduce computation time to 26%!) + int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0; + // foreach pixel + for( unsigned int i = 0; i < pixels; ++i ) + { + // alpha for shadow and image + int shadowAlpha = shadowData[i] & 0xFF, + srcAlpha = data[i] & 0xFF; + // cache values + if ( srcAlpha != c1 || shadowAlpha != c2 ) + { + c1 = srcAlpha; + c2 = shadowAlpha; + // fuse color components and alpha value of image over shadow + data[i] = qRgba( + cR = qt_div255( srcAlpha * red + (255 - srcAlpha) * sRed ), + cG = qt_div255( srcAlpha * green + (255 - srcAlpha) * sGreen ), + cB = qt_div255( srcAlpha * blue + (255 - srcAlpha) * sBlue ), + cA = qt_div255( srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha ) + ); + } + else + data[i] = qRgba( cR, cG, cB, cA ); + } + m_lastRenderedOverlay.convertFromImage( image ); + + // start the autohide timer + repaint( m_overlayGeometry, false /*clear*/ ); // toggle with next line + //update( m_overlayGeometry ); + m_overlayHideTimer->start( 2500, true ); +#endif +} + + + +void PresentationWidget::slotNextPage() +{ + // loop when configured + if ( m_frameIndex == (int)m_frames.count() - 1 && KpdfSettings::slidesLoop() ) + m_frameIndex = -1; + + if ( m_frameIndex < (int)m_frames.count() - 1 ) + { + // go to next page + changePage( m_frameIndex + 1 ); + + // auto advance to the next page if set + if ( KpdfSettings::slidesAdvance() ) + m_nextPageTimer->start( KpdfSettings::slidesAdvanceTime() * 1000 ); + } + else + { +#ifdef ENABLE_PROGRESS_OVERLAY + if ( KpdfSettings::slidesShowProgress() ) + generateOverlay(); +#endif + if ( m_transitionTimer->isActive() ) + { + m_transitionTimer->stop(); + update(); + } + } + + // we need the setFocus() call here to let KCursor::autoHide() work correctly + setFocus(); +} + +void PresentationWidget::slotPrevPage() +{ + if ( m_frameIndex > 0 ) + { + // go to previous page + changePage( m_frameIndex - 1 ); + + // auto advance to the next page if set + if ( KpdfSettings::slidesAdvance() ) + m_nextPageTimer->start( KpdfSettings::slidesAdvanceTime() * 1000 ); + } + else + { +#ifdef ENABLE_PROGRESS_OVERLAY + if ( KpdfSettings::slidesShowProgress() ) + generateOverlay(); +#endif + if ( m_transitionTimer->isActive() ) + { + m_transitionTimer->stop(); + update(); + } + } +} + +void PresentationWidget::slotFirstPage() +{ + changePage( 0 ); +} + +void PresentationWidget::slotLastPage() +{ + changePage( (int)m_frames.count() - 1 ); +} + +void PresentationWidget::slotHideOverlay() +{ + QRect geom( m_overlayGeometry ); + m_overlayGeometry.setCoords( 0, 0, -1, -1 ); + update( geom ); +} + +void PresentationWidget::slotTransitionStep() +{ + if ( m_transitionRects.empty() ) + { + // it's better to fix the transition to cover the whole screen than + // enabling the following line that wastes cpu for nothing + //update(); + return; + } + + for ( int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++ ) + { + update( m_transitionRects.first() ); + m_transitionRects.pop_front(); + } + m_transitionTimer->start( m_transitionDelay, true ); +} + +const KPDFPageTransition PresentationWidget::defaultTransition() const +{ + return defaultTransition( KpdfSettings::slidesTransition() ); +} + +const KPDFPageTransition PresentationWidget::defaultTransition( int type ) const +{ + switch ( type ) + { + case KpdfSettings::EnumSlidesTransition::BlindsHorizontal: + { + KPDFPageTransition transition( KPDFPageTransition::Blinds ); + transition.setAlignment( KPDFPageTransition::Horizontal ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::BlindsVertical: + { + KPDFPageTransition transition( KPDFPageTransition::Blinds ); + transition.setAlignment( KPDFPageTransition::Vertical ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::BoxIn: + { + KPDFPageTransition transition( KPDFPageTransition::Box ); + transition.setDirection( KPDFPageTransition::Inward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::BoxOut: + { + KPDFPageTransition transition( KPDFPageTransition::Box ); + transition.setDirection( KPDFPageTransition::Outward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::Dissolve: + { + return KPDFPageTransition( KPDFPageTransition::Dissolve ); + break; + } + case KpdfSettings::EnumSlidesTransition::GlitterDown: + { + KPDFPageTransition transition( KPDFPageTransition::Glitter ); + transition.setAngle( 270 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::GlitterRight: + { + KPDFPageTransition transition( KPDFPageTransition::Glitter ); + transition.setAngle( 0 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::GlitterRightDown: + { + KPDFPageTransition transition( KPDFPageTransition::Glitter ); + transition.setAngle( 315 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::Random: + { + return defaultTransition( KApplication::random() % 18 ); + break; + } + case KpdfSettings::EnumSlidesTransition::SplitHorizontalIn: + { + KPDFPageTransition transition( KPDFPageTransition::Split ); + transition.setAlignment( KPDFPageTransition::Horizontal ); + transition.setDirection( KPDFPageTransition::Inward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::SplitHorizontalOut: + { + KPDFPageTransition transition( KPDFPageTransition::Split ); + transition.setAlignment( KPDFPageTransition::Horizontal ); + transition.setDirection( KPDFPageTransition::Outward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::SplitVerticalIn: + { + KPDFPageTransition transition( KPDFPageTransition::Split ); + transition.setAlignment( KPDFPageTransition::Vertical ); + transition.setDirection( KPDFPageTransition::Inward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::SplitVerticalOut: + { + KPDFPageTransition transition( KPDFPageTransition::Split ); + transition.setAlignment( KPDFPageTransition::Vertical ); + transition.setDirection( KPDFPageTransition::Outward ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::WipeDown: + { + KPDFPageTransition transition( KPDFPageTransition::Wipe ); + transition.setAngle( 270 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::WipeRight: + { + KPDFPageTransition transition( KPDFPageTransition::Wipe ); + transition.setAngle( 0 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::WipeLeft: + { + KPDFPageTransition transition( KPDFPageTransition::Wipe ); + transition.setAngle( 180 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::WipeUp: + { + KPDFPageTransition transition( KPDFPageTransition::Wipe ); + transition.setAngle( 90 ); + return transition; + break; + } + case KpdfSettings::EnumSlidesTransition::Replace: + default: + return KPDFPageTransition( KPDFPageTransition::Replace ); + break; + } +} + +/** ONLY the TRANSITIONS GENERATION function from here on **/ +void PresentationWidget::initTransition( const KPDFPageTransition *transition ) +{ + // if it's just a 'replace' transition, repaint the screen + if ( transition->type() == KPDFPageTransition::Replace ) + { + update(); + return; + } + + const bool isInward = transition->direction() == KPDFPageTransition::Inward; + const bool isHorizontal = transition->alignment() == KPDFPageTransition::Horizontal; + const float totalTime = transition->duration(); + + m_transitionRects.clear(); + + switch( transition->type() ) + { + // split: horizontal / vertical and inward / outward + case KPDFPageTransition::Split: + { + const int steps = isHorizontal ? 100 : 75; + if ( isHorizontal ) + { + if ( isInward ) + { + int xPosition = 0; + for ( int i = 0; i < steps; i++ ) + { + int xNext = ((i + 1) * m_width) / (2 * steps); + m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); + m_transitionRects.push_back( QRect( m_width - xNext, 0, xNext - xPosition, m_height ) ); + xPosition = xNext; + } + } + else + { + int xPosition = m_width / 2; + for ( int i = 0; i < steps; i++ ) + { + int xNext = ((steps - (i + 1)) * m_width) / (2 * steps); + m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); + m_transitionRects.push_back( QRect( m_width - xPosition, 0, xPosition - xNext, m_height ) ); + xPosition = xNext; + } + } + } + else + { + if ( isInward ) + { + int yPosition = 0; + for ( int i = 0; i < steps; i++ ) + { + int yNext = ((i + 1) * m_height) / (2 * steps); + m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); + m_transitionRects.push_back( QRect( 0, m_height - yNext, m_width, yNext - yPosition ) ); + yPosition = yNext; + } + } + else + { + int yPosition = m_height / 2; + for ( int i = 0; i < steps; i++ ) + { + int yNext = ((steps - (i + 1)) * m_height) / (2 * steps); + m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); + m_transitionRects.push_back( QRect( 0, m_height - yPosition, m_width, yPosition - yNext ) ); + yPosition = yNext; + } + } + } + m_transitionMul = 2; + m_transitionDelay = (int)( (totalTime * 1000) / steps ); + } break; + + // blinds: horizontal(l-to-r) / vertical(t-to-b) + case KPDFPageTransition::Blinds: + { + const int blinds = isHorizontal ? 8 : 6; + const int steps = m_width / (4 * blinds); + if ( isHorizontal ) + { + int xPosition[ 8 ]; + for ( int b = 0; b < blinds; b++ ) + xPosition[ b ] = (b * m_width) / blinds; + + for ( int i = 0; i < steps; i++ ) + { + int stepOffset = (int)( ((float)i * (float)m_width) / ((float)blinds * (float)steps) ); + for ( int b = 0; b < blinds; b++ ) + { + m_transitionRects.push_back( QRect( xPosition[ b ], 0, stepOffset, m_height ) ); + xPosition[ b ] = stepOffset + (b * m_width) / blinds; + } + } + } + else + { + int yPosition[ 6 ]; + for ( int b = 0; b < blinds; b++ ) + yPosition[ b ] = (b * m_height) / blinds; + + for ( int i = 0; i < steps; i++ ) + { + int stepOffset = (int)( ((float)i * (float)m_height) / ((float)blinds * (float)steps) ); + for ( int b = 0; b < blinds; b++ ) + { + m_transitionRects.push_back( QRect( 0, yPosition[ b ], m_width, stepOffset ) ); + yPosition[ b ] = stepOffset + (b * m_height) / blinds; + } + } + } + m_transitionMul = blinds; + m_transitionDelay = (int)( (totalTime * 1000) / steps ); + } break; + + // box: inward / outward + case KPDFPageTransition::Box: + { + const int steps = m_width / 10; + if ( isInward ) + { + int L = 0, T = 0, R = m_width, B = m_height; + for ( int i = 0; i < steps; i++ ) + { + // compure shrinked box coords + int newL = ((i + 1) * m_width) / (2 * steps); + int newT = ((i + 1) * m_height) / (2 * steps); + int newR = m_width - newL; + int newB = m_height - newT; + // add left, right, topcenter, bottomcenter rects + m_transitionRects.push_back( QRect( L, T, newL - L, B - T ) ); + m_transitionRects.push_back( QRect( newR, T, R - newR, B - T ) ); + m_transitionRects.push_back( QRect( newL, T, newR - newL, newT - T ) ); + m_transitionRects.push_back( QRect( newL, newB, newR - newL, B - newB ) ); + L = newL; T = newT; R = newR, B = newB; + } + } + else + { + int L = m_width / 2, T = m_height / 2, R = L, B = T; + for ( int i = 0; i < steps; i++ ) + { + // compure shrinked box coords + int newL = ((steps - (i + 1)) * m_width) / (2 * steps); + int newT = ((steps - (i + 1)) * m_height) / (2 * steps); + int newR = m_width - newL; + int newB = m_height - newT; + // add left, right, topcenter, bottomcenter rects + m_transitionRects.push_back( QRect( newL, newT, L - newL, newB - newT ) ); + m_transitionRects.push_back( QRect( R, newT, newR - R, newB - newT ) ); + m_transitionRects.push_back( QRect( L, newT, R - L, T - newT ) ); + m_transitionRects.push_back( QRect( L, B, R - L, newB - B ) ); + L = newL; T = newT; R = newR, B = newB; + } + } + m_transitionMul = 4; + m_transitionDelay = (int)( (totalTime * 1000) / steps ); + } break; + + // wipe: implemented for 4 canonical angles + case KPDFPageTransition::Wipe: + { + const int angle = transition->angle(); + const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8; + if ( angle == 0 ) + { + int xPosition = 0; + for ( int i = 0; i < steps; i++ ) + { + int xNext = ((i + 1) * m_width) / steps; + m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); + xPosition = xNext; + } + } + else if ( angle == 90 ) + { + int yPosition = m_height; + for ( int i = 0; i < steps; i++ ) + { + int yNext = ((steps - (i + 1)) * m_height) / steps; + m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); + yPosition = yNext; + } + } + else if ( angle == 180 ) + { + int xPosition = m_width; + for ( int i = 0; i < steps; i++ ) + { + int xNext = ((steps - (i + 1)) * m_width) / steps; + m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); + xPosition = xNext; + } + } + else if ( angle == 270 ) + { + int yPosition = 0; + for ( int i = 0; i < steps; i++ ) + { + int yNext = ((i + 1) * m_height) / steps; + m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); + yPosition = yNext; + } + } + else + { + update(); + return; + } + m_transitionMul = 1; + m_transitionDelay = (int)( (totalTime * 1000) / steps ); + } break; + + // dissolve: replace 'random' rects + case KPDFPageTransition::Dissolve: + { + const int gridXsteps = 50; + const int gridYsteps = 38; + const int steps = gridXsteps * gridYsteps; + int oldX = 0; + int oldY = 0; + // create a grid of gridXstep by gridYstep QRects + for ( int y = 0; y < gridYsteps; y++ ) + { + int newY = (int)( m_height * ((float)(y+1) / (float)gridYsteps) ); + for ( int x = 0; x < gridXsteps; x++ ) + { + int newX = (int)( m_width * ((float)(x+1) / (float)gridXsteps) ); + m_transitionRects.push_back( QRect( oldX, oldY, newX - oldX, newY - oldY ) ); + oldX = newX; + } + oldX = 0; + oldY = newY; + } + // randomize the grid + for ( int i = 0; i < steps; i++ ) + { + int n1 = (int)(steps * drand48()); + int n2 = (int)(steps * drand48()); + // swap items if index differs + if ( n1 != n2 ) + { + QRect r = m_transitionRects[ n2 ]; + m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; + m_transitionRects[ n1 ] = r; + } + } + // set global transition parameters + m_transitionMul = 40; + m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); + } break; + + // glitter: similar to dissolve but has a direction + case KPDFPageTransition::Glitter: + { + const int gridXsteps = 50; + const int gridYsteps = 38; + const int steps = gridXsteps * gridYsteps; + const int angle = transition->angle(); + // generate boxes using a given direction + if ( angle == 90 ) + { + int yPosition = m_height; + for ( int i = 0; i < gridYsteps; i++ ) + { + int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps; + int xPosition = 0; + for ( int j = 0; j < gridXsteps; j++ ) + { + int xNext = ((j + 1) * m_width) / gridXsteps; + m_transitionRects.push_back( QRect( xPosition, yNext, xNext - xPosition, yPosition - yNext ) ); + xPosition = xNext; + } + yPosition = yNext; + } + } + else if ( angle == 180 ) + { + int xPosition = m_width; + for ( int i = 0; i < gridXsteps; i++ ) + { + int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps; + int yPosition = 0; + for ( int j = 0; j < gridYsteps; j++ ) + { + int yNext = ((j + 1) * m_height) / gridYsteps; + m_transitionRects.push_back( QRect( xNext, yPosition, xPosition - xNext, yNext - yPosition ) ); + yPosition = yNext; + } + xPosition = xNext; + } + } + else if ( angle == 270 ) + { + int yPosition = 0; + for ( int i = 0; i < gridYsteps; i++ ) + { + int yNext = ((i + 1) * m_height) / gridYsteps; + int xPosition = 0; + for ( int j = 0; j < gridXsteps; j++ ) + { + int xNext = ((j + 1) * m_width) / gridXsteps; + m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); + xPosition = xNext; + } + yPosition = yNext; + } + } + else // if angle is 0 or 315 + { + int xPosition = 0; + for ( int i = 0; i < gridXsteps; i++ ) + { + int xNext = ((i + 1) * m_width) / gridXsteps; + int yPosition = 0; + for ( int j = 0; j < gridYsteps; j++ ) + { + int yNext = ((j + 1) * m_height) / gridYsteps; + m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); + yPosition = yNext; + } + xPosition = xNext; + } + } + // add a 'glitter' (1 over 10 pieces is randomized) + int randomSteps = steps / 20; + for ( int i = 0; i < randomSteps; i++ ) + { + int n1 = (int)(steps * drand48()); + int n2 = (int)(steps * drand48()); + // swap items if index differs + if ( n1 != n2 ) + { + QRect r = m_transitionRects[ n2 ]; + m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; + m_transitionRects[ n1 ] = r; + } + } + // set global transition parameters + m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps; + m_transitionMul /= 2; + m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); + } break; + + // implement missing transitions (a binary raster engine needed here) + case KPDFPageTransition::Fly: + + case KPDFPageTransition::Push: + + case KPDFPageTransition::Cover: + + case KPDFPageTransition::Uncover: + + case KPDFPageTransition::Fade: + + default: + update(); + return; + } + + // send the first start to the timer + m_transitionTimer->start( 0, true ); +} + + +#include "presentationwidget.moc" diff --git a/kpdf/ui/presentationwidget.h b/kpdf/ui/presentationwidget.h new file mode 100644 index 00000000..4b0cdf19 --- /dev/null +++ b/kpdf/ui/presentationwidget.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_PRESENTATIONWIDGET_H_ +#define _KPDF_PRESENTATIONWIDGET_H_ + +#include <qdialog.h> +#include <qpixmap.h> +#include <qstringlist.h> +#include <qvaluevector.h> +#include "core/observer.h" +#include "core/pagetransition.h" + +class KAccel; +class KActionCollection; +class KToolBar; +class QTimer; + +class KPDFDocument; +class KPDFPage; +class KPDFLink; +class PresentationFrame; + +/** + * @short A widget that shows pages as fullscreen slides (with transitions fx). + * + * This is a fullscreen widget that displays + */ +class PresentationWidget : public QDialog, public DocumentObserver +{ + Q_OBJECT + public: + PresentationWidget( QWidget * parent, KPDFDocument * doc ); + ~PresentationWidget(); + + void setupActions( KActionCollection * ac ); + + // inherited from DocumentObserver + uint observerId() const { return PRESENTATION_ID; } + void notifySetup( const QValueVector< KPDFPage * > & pages, bool documentChanged ); + void notifyViewportChanged( bool smoothMove ); + void notifyPageChanged( int pageNumber, int changedFlags ); + bool canUnloadPixmap( int pageNumber ); + + protected: + // widget events +// bool event( QEvent * e ); + void keyPressEvent( QKeyEvent * e ); + void wheelEvent( QWheelEvent * e ); + void mousePressEvent( QMouseEvent * e ); + void mouseReleaseEvent( QMouseEvent * e ); + void mouseMoveEvent( QMouseEvent * e ); + void paintEvent( QPaintEvent * e ); + + private: + const KPDFLink * getLink( int x, int y, QRect * geometry = 0 ) const; + void testCursorOnLink( int x, int y ); + void overlayClick( const QPoint & position ); + void changePage( int newPage ); + void generatePage(); + void generateIntroPage( QPainter & p ); + void generateContentsPage( int page, QPainter & p ); + void generateOverlay(); + void initTransition( const KPDFPageTransition *transition ); + const KPDFPageTransition defaultTransition() const; + const KPDFPageTransition defaultTransition( int ) const; + + // cache stuff + int m_width; + int m_height; + QPixmap m_lastRenderedPixmap; + QPixmap m_lastRenderedOverlay; + QRect m_overlayGeometry; + const KPDFLink * m_pressedLink; + bool m_handCursor; + + // transition related + QTimer * m_transitionTimer; + QTimer * m_overlayHideTimer; + QTimer * m_nextPageTimer; + int m_transitionDelay; + int m_transitionMul; + QValueList< QRect > m_transitionRects; + + // misc stuff + KPDFDocument * m_document; + QValueVector< PresentationFrame * > m_frames; + int m_frameIndex; + QStringList m_metaStrings; + KToolBar * m_topBar; + KAccel * m_accel; + + private slots: + void slotNextPage(); + void slotPrevPage(); + void slotFirstPage(); + void slotLastPage(); + void slotHideOverlay(); + void slotTransitionStep(); +}; + +#endif diff --git a/kpdf/ui/propertiesdialog.cpp b/kpdf/ui/propertiesdialog.cpp new file mode 100644 index 00000000..d5810a07 --- /dev/null +++ b/kpdf/ui/propertiesdialog.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * 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 <qlayout.h> +#include <qlabel.h> +#include <klistview.h> +#include <klocale.h> +#include <ksqueezedtextlabel.h> +#include <kglobalsettings.h> + +// local includes +#include "propertiesdialog.h" +#include "core/document.h" + +PropertiesDialog::PropertiesDialog(QWidget *parent, KPDFDocument *doc) + : KDialogBase( Tabbed, i18n( "Unknown File" ), Ok, Ok, parent, 0, true, true ) +{ + // Properties + QFrame *page = addPage(i18n("Properties")); + QGridLayout *layout = new QGridLayout( page, 2, 2, marginHint(), spacingHint() ); + + // get document info, if not present display blank data and a warning + const DocumentInfo * info = doc->documentInfo(); + if ( !info ) { + layout->addWidget( new QLabel( i18n( "No document opened." ), page ), 0, 0 ); + return; + } + + // mime name based on mimetype id + QString mimeName = info->get( "mimeType" ).section( '/', -1 ).upper(); + setCaption( i18n("%1 Properties").arg( mimeName ) ); + + QDomElement docElement = info->documentElement(); + + int row = 0; + int valMaxWidth = 100; + for ( QDomNode node = docElement.firstChild(); !node.isNull(); node = node.nextSibling() ) { + QDomElement element = node.toElement(); + + QString titleString = element.attribute( "title" ); + QString valueString = element.attribute( "value" ); + if ( titleString.isEmpty() || valueString.isEmpty() ) + continue; + + // create labels and layout them + QLabel *key = new QLabel( i18n( "%1:" ).arg( titleString ), page ); + QLabel *value = new KSqueezedTextLabel( valueString, page ); + layout->addWidget( key, row, 0, AlignRight ); + layout->addWidget( value, row, 1 ); + row++; + + // refine maximum width of 'value' labels + valMaxWidth = QMAX( valMaxWidth, fontMetrics().width( valueString ) ); + } + + // add the number of pages if the generator hasn't done it already + QDomNodeList list = docElement.elementsByTagName( "pages" ); + if ( list.count() == 0 ) { + QLabel *key = new QLabel( i18n( "Pages:" ), page ); + QLabel *value = new QLabel( QString::number( doc->pages() ), page ); + + layout->addWidget( key, row, 0 ); + layout->addWidget( value, row, 1 ); + } + + // Fonts + QVBoxLayout *page2Layout = 0; + if (doc->hasFonts()) + { + QFrame *page2 = addPage(i18n("Fonts")); + page2Layout = new QVBoxLayout(page2, 0, KDialog::spacingHint()); + KListView *lv = new KListView(page2); + page2Layout->add(lv); + doc->putFontInfo(lv); + } + + // current width: left column + right column + dialog borders + int width = layout->minimumSize().width() + valMaxWidth + marginHint() + spacingHint() + marginHint() + 30; + if (page2Layout) + { + width = QMAX( width, page2Layout->sizeHint().width() + marginHint() + spacingHint() + 31 ); + } + // stay inside the 2/3 of the screen width + QRect screenContainer = KGlobalSettings::desktopGeometry( this ); + width = QMIN( width, 2*screenContainer.width()/3 ); + resize(width, 1); +} diff --git a/kpdf/ui/propertiesdialog.h b/kpdf/ui/propertiesdialog.h new file mode 100644 index 00000000..7bc39e5a --- /dev/null +++ b/kpdf/ui/propertiesdialog.h @@ -0,0 +1,23 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ + +#ifndef _PROPERTIESDIALOG_H_ +#define _PROPERTIESDIALOG_H_ + +#include <kdialogbase.h> + +class KPDFDocument; + +class PropertiesDialog : public KDialogBase +{ + public: + PropertiesDialog( QWidget *parent, KPDFDocument *doc ); +}; + +#endif diff --git a/kpdf/ui/searchwidget.cpp b/kpdf/ui/searchwidget.cpp new file mode 100644 index 00000000..30a5bcf2 --- /dev/null +++ b/kpdf/ui/searchwidget.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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 <qtooltip.h> +#include <qapplication.h> +#include <qtimer.h> +#include <kaction.h> +#include <kactioncollection.h> +#include <klocale.h> +#include <kiconloader.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <ktoolbarbutton.h> + +// local includes +#include "searchwidget.h" +#include "core/document.h" +#include "conf/settings.h" + +#define CLEAR_ID 1 +#define LEDIT_ID 2 +#define FIND_ID 3 + +SearchWidget::SearchWidget( QWidget * parent, KPDFDocument * document ) + : KToolBar( parent, "iSearchBar" ), m_document( document ), + m_searchType( 0 ), m_caseSensitive( false ) +{ + // change toolbar appearance + setMargin( 3 ); + setFlat( true ); + setIconSize( 16 ); + setMovingEnabled( false ); + + // a timer to ensure that we don't flood the document with requests to search + m_inputDelayTimer = new QTimer(this); + connect( m_inputDelayTimer, SIGNAL( timeout() ), + this, SLOT( startSearch() ) ); + + // 1. text line + insertLined( QString::null, LEDIT_ID, SIGNAL( textChanged(const QString &) ), + this, SLOT( slotTextChanged(const QString &) ), true, + i18n( "Enter at least 3 letters to filter pages" ), 0/*size*/, 1 ); + + // 2. clear button (uses a lineEdit slot, so it must be created after) + insertButton( QApplication::reverseLayout() ? "clear_left" : "locationbar_erase", + CLEAR_ID, SIGNAL( clicked() ), + getLined( LEDIT_ID ), SLOT( clear() ), true, + i18n( "Clear filter" ), 0/*index*/ ); + + // 3.1. create the popup menu for changing filtering features + m_menu = new KPopupMenu( this ); + m_menu->insertItem( i18n("Case Sensitive"), 1 ); + m_menu->insertSeparator( 2 ); + m_menu->insertItem( i18n("Match Phrase"), 3 ); + m_menu->insertItem( i18n("Match All Words"), 4 ); + m_menu->insertItem( i18n("Match Any Word"), 5 ); + m_menu->setItemChecked( 3, true ); + connect( m_menu, SIGNAL( activated(int) ), SLOT( slotMenuChaged(int) ) ); + + // 3.2. create the toolbar button that spawns the popup menu + insertButton( "kpdf", FIND_ID, m_menu, true, i18n( "Filter Options" ), 2/*index*/ ); + + // always maximize the text line + setItemAutoSized( LEDIT_ID ); +} + +void SearchWidget::clearText() +{ + getLined( LEDIT_ID )->clear(); +} + +void SearchWidget::slotTextChanged( const QString & text ) +{ + // if 0<length<3 set 'red' text and send a blank string to document + QColor color = text.length() > 0 && text.length() < 3 ? Qt::darkRed : palette().active().text(); + KLineEdit * lineEdit = getLined( LEDIT_ID ); + lineEdit->setPaletteForegroundColor( color ); + lineEdit->setPaletteBackgroundColor( palette().active().base() ); + m_inputDelayTimer->stop(); + m_inputDelayTimer->start(333, true); +} + +void SearchWidget::slotMenuChaged( int index ) +{ + // update internal variables and checked state + if ( index == 1 ) + { + m_caseSensitive = !m_caseSensitive; + m_menu->setItemChecked( 1, m_caseSensitive ); + } + else if ( index >= 3 && index <= 5 ) + { + m_searchType = index - 3; + for ( int i = 0; i < 3; i++ ) + m_menu->setItemChecked( i + 3, m_searchType == i ); + } + else + return; + + // update search + slotTextChanged( getLined( LEDIT_ID )->text() ); +} + +void SearchWidget::startSearch() +{ + // search text if have more than 3 chars or else clear search + QString text = getLined( LEDIT_ID )->text(); + bool ok = true; + if ( text.length() >= 3 ) + { + KPDFDocument::SearchType type = !m_searchType ? KPDFDocument::AllDoc : + ( (m_searchType > 1) ? KPDFDocument::GoogleAny : + KPDFDocument::GoogleAll ); + ok = m_document->searchText( SW_SEARCH_ID, text, true, m_caseSensitive, + type, false, qRgb( 0, 183, 255 ) ); + } + else + m_document->resetSearch( SW_SEARCH_ID ); + // if not found, use warning colors + if ( !ok ) + { + KLineEdit * lineEdit = getLined( LEDIT_ID ); + lineEdit->setPaletteForegroundColor( Qt::white ); + lineEdit->setPaletteBackgroundColor( Qt::red ); + } +} + +#include "searchwidget.moc" diff --git a/kpdf/ui/searchwidget.h b/kpdf/ui/searchwidget.h new file mode 100644 index 00000000..9f2a1e44 --- /dev/null +++ b/kpdf/ui/searchwidget.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * + * * + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_SEARCHWIDGET_H_ +#define _KPDF_SEARCHWIDGET_H_ + +#include <ktoolbar.h> + +class KPopupMenu; +class KPDFDocument; +class m_inputDelayTimer; + +// definition of searchID for this class (publicly available to ThumbnailsList) +#define SW_SEARCH_ID 3 + +/** + * @short A widget for find-as-you-type search. Outputs to the Document. + * + * This widget accepts keyboard input and performs a call to findTextAll(..) + * in the KPDFDocument class when there are 3 or more chars to search for. + * It supports case sensitive/unsensitive(default) and provieds a button + * for switching between the 2 modes. + */ +class SearchWidget : public KToolBar +{ + Q_OBJECT + public: + SearchWidget( QWidget *parent, KPDFDocument *document ); + void clearText(); + + private: + KPDFDocument * m_document; + KPopupMenu * m_menu; + QTimer * m_inputDelayTimer; + int m_searchType; + bool m_caseSensitive; + + private slots: + void slotTextChanged( const QString & text ); + void slotMenuChaged( int index ); + void startSearch(); +}; + +#endif diff --git a/kpdf/ui/thumbnaillist.cpp b/kpdf/ui/thumbnaillist.cpp new file mode 100644 index 00000000..60324533 --- /dev/null +++ b/kpdf/ui/thumbnaillist.cpp @@ -0,0 +1,575 @@ +/*************************************************************************** + * Copyright (C) 2004-2006 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 + m_margin; } + 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: + // the margin around the widget + static int const m_margin = 16; + + // 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() +{ + m_document->removeObserver( this ); + delete m_bookmarkOverlay; +} + +//BEGIN DocumentObserver inherited methods +void ThumbnailList::notifySetup( const QValueVector< KPDFPage * > & pages, bool documentChanged ) +{ + // if there was a widget selected, save its pagenumber to restore + // its selection (if available in the new set of pages) + int prevPage = -1; + if ( !documentChanged && m_selected ) + { + prevPage = m_selected->page()->number(); + } + + // 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; + if ( (*pIt)->number() == prevPage ) + { + m_selected = t; + m_selected->setSelected( true ); + } + 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 ( KpdfSettings::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 + KpdfSettings::setFilterBookmarks( filterOn ); + KpdfSettings::writeConfig(); + // 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 - m_margin; + 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, 0, width(), height() ); + } +} + +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 + m_margin; + int height = m_pixmapHeight + m_margin + m_labelHeight; + QRect clipRect = e->rect(); + if ( !clipRect.isValid() ) + return; + QPainter p( this ); + + // draw the bottom label + highlight mark + QColor fillColor = m_selected ? palette().active().highlight() : palette().active().base(); + p.fillRect( 0, 0, width, height, fillColor ); + p.setPen( m_selected ? palette().active().highlightedText() : palette().active().text() ); + p.drawText( 0, m_pixmapHeight + m_margin, width, m_labelHeight, Qt::AlignCenter, QString::number( m_labelNumber ) ); + + // draw page outline and pixmap + if ( clipRect.top() < m_pixmapHeight + m_margin ) + { + // 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( m_margin/2 - 1, m_margin/2 - 1, m_pixmapWidth + 2, m_pixmapHeight + 2 ); + // draw the clear rect + p.setPen( isBookmarked ? QColor( 0x804000 ) : palette().active().base() ); + // draw the bottom and right shadow edges + if ( !isBookmarked ) + { + int left, right, bottom, top; + left = m_margin/2 + 1; + right = m_margin/2 + m_pixmapWidth + 1; + bottom = m_pixmapHeight + m_margin/2 + 1; + top = m_margin/2 + 1; + p.setPen( Qt::gray ); + p.drawLine( left, bottom, right, bottom ); + p.drawLine( right, top, right, bottom ); + } + + // draw the page using the shared PagePainter class + p.translate( m_margin/2, m_margin/2 ); + clipRect.moveBy( -m_margin/2, -m_margin/2 ); + clipRect = clipRect.intersect( QRect( 0, 0, m_pixmapWidth, m_pixmapHeight ) ); + if ( clipRect.isValid() ) + { + int flags = PagePainter::Accessibility | PagePainter::Highlights; + PagePainter::paintPageOnPainter( m_page, THUMBNAILS_ID, flags, &p, + clipRect, m_pixmapWidth, m_pixmapHeight ); + } + + // 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, KpdfSettings::filterBookmarks() ); + //insertLineSeparator(); +} + + +#include "thumbnaillist.moc" diff --git a/kpdf/ui/thumbnaillist.h b/kpdf/ui/thumbnaillist.h new file mode 100644 index 00000000..e0f84610 --- /dev/null +++ b/kpdf/ui/thumbnaillist.h @@ -0,0 +1,121 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_THUMBNAILLIST_H_ +#define _KPDF_THUMBNAILLIST_H_ + +#include <qscrollview.h> +#include <qvaluevector.h> +#include <qvbox.h> +#include <ktoolbar.h> +#include "core/observer.h" + +class QTimer; +class KActionCollection; + +class KPDFDocument; +class ThumbnailWidget; + +/** + * @short A scrollview that displays pages pixmaps previews (aka thumbnails). + * + * ... + */ +class ThumbnailList : public QScrollView, public DocumentObserver +{ +Q_OBJECT + public: + ThumbnailList(QWidget *parent, KPDFDocument *document); + ~ThumbnailList(); + + // inherited: return thumbnails observer id + uint observerId() const { return THUMBNAILS_ID; } + // inherited: create thumbnails ( inherited as a DocumentObserver ) + void notifySetup( const QValueVector< KPDFPage * > & pages, bool documentChanged ); + // inherited: hilihght current thumbnail ( inherited as DocumentObserver ) + void notifyViewportChanged( bool smoothMove ); + // inherited: redraw thumbnail ( inherited as DocumentObserver ) + void notifyPageChanged( int pageNumber, int changedFlags ); + // inherited: request all visible pixmap (due to a global shange or so..) + void notifyContentsCleared( int changedFlags ); + // inherited: tell if pixmap is hidden and can be unloaded + bool canUnloadPixmap( int pageNumber ); + + // redraw visible widgets (useful for refreshing contents...) + void updateWidgets(); + + // called by ThumbnailWidgets to send (forward) rightClick signals + void forwardRightClick( const KPDFPage *, const QPoint & ); + // called by ThumbnailWidgets to get the overlay bookmark pixmap + const QPixmap * getBookmarkOverlay() const; + + public slots: + // these are connected to ThumbnailController buttons + void slotFilterBookmarks( bool filterOn ); + + protected: + // scroll up/down the view + void keyPressEvent( QKeyEvent * e ); + + // select a thumbnail by clicking on it + void contentsMousePressEvent( QMouseEvent * ); + + // resize thumbnails to fit the width + void viewportResizeEvent( QResizeEvent * ); + + // file drop related events (an url may be dropped even here) + void dragEnterEvent( QDragEnterEvent* ); + void dropEvent( QDropEvent* ); + + signals: + void urlDropped( const KURL& ); + void rightClick( const KPDFPage *, const QPoint & ); + + private: + void delayedRequestVisiblePixmaps( int delayMs = 0 ); + KPDFDocument *m_document; + ThumbnailWidget *m_selected; + QTimer *m_delayTimer; + QPixmap *m_bookmarkOverlay; + QValueVector<ThumbnailWidget *> m_thumbnails; + QValueList<ThumbnailWidget *> m_visibleThumbnails; + int m_vectorIndex; + + private slots: + // make requests for generating pixmaps for visible thumbnails + void slotRequestVisiblePixmaps( int newContentsX = -1, int newContentsY = -1 ); + // delay timeout: resize overlays and requests pixmaps + void slotDelayTimeout(); +}; + +/** + * @short A vertical boxed container with zero size hint (for insertion on left toolbox) + */ +class ThumbnailsBox : public QVBox +{ + public: + ThumbnailsBox( QWidget * parent ) : QVBox( parent ) {}; + QSize sizeHint() const { return QSize(); } +}; + +/** + * @short A toolbar thar set ThumbnailList properties when clicking on items + * + * This class is the small tolbar that resides in the bottom of the + * ThumbnailsBox container (below ThumbnailList and the SearchLine) and + * emits signals whenever a button is pressed. A click action results + * in invoking some method (or slot) in ThumbnailList. + */ +class ThumbnailController : public KToolBar +{ + public: + ThumbnailController( QWidget * parent, ThumbnailList * thumbnailList ); +}; + +#endif diff --git a/kpdf/ui/toc.cpp b/kpdf/ui/toc.cpp new file mode 100644 index 00000000..6db19933 --- /dev/null +++ b/kpdf/ui/toc.cpp @@ -0,0 +1,175 @@ +/*************************************************************************** + * Copyright (C) 2004-2006 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 <qheader.h> +#include <qvariant.h> +#include <klocale.h> + +// local includes +#include "toc.h" +#include "core/link.h" +#include "core/page.h" + +// uncomment following to enable a 2nd column showing the page referred +// by each tree entry note: PDF uses often references to viewports and +// they're slow when converted to page number. drop the 2nd column idea. +//#define TOC_ENABLE_PAGE_COLUMN + +class TOCItem : public KListViewItem +{ + public: + TOCItem( KListView *parent, TOCItem *after, const QDomElement & e ) + : KListViewItem( parent, after, e.tagName() ), m_element( e ) + { +#ifdef TOC_ENABLE_PAGE_COLUMN + if ( e.hasAttribute( "Page" ) ) + setText( 1, e.attribute( "Page" ) ); +#endif + setMultiLinesEnabled(true); + } + + TOCItem( KListViewItem *parent, TOCItem *after, const QDomElement & e ) + : KListViewItem( parent, after, e.tagName() ), m_element( e ) + { +#ifdef TOC_ENABLE_PAGE_COLUMN + if ( e.hasAttribute( "Page" ) ) + setText( 1, e.attribute( "Page" ) ); +#endif + setMultiLinesEnabled(true); + } + + const QDomElement & element() const + { + return m_element; + } + + private: + QDomElement m_element; +}; + +TOC::TOC(QWidget *parent, KPDFDocument *document) : KListView(parent), m_document(document) +{ + addColumn( i18n("Topic") ); +#ifdef TOC_ENABLE_PAGE_COLUMN + addColumn( i18n("Page") ); +#else + header() -> hide(); +#endif + setSorting(-1); + setRootIsDecorated(true); + // the next line causes bug:147233 +// setResizeMode(AllColumns); + setAllColumnsShowFocus(true); + connect(this, SIGNAL(clicked(QListViewItem *)), this, SLOT(slotExecuted(QListViewItem *))); + connect(this, SIGNAL(returnPressed(QListViewItem *)), this, SLOT(slotExecuted(QListViewItem *))); +} + +TOC::~TOC() +{ + m_document->removeObserver( this ); +} + +uint TOC::observerId() const +{ + return TOC_ID; +} + +void TOC::notifySetup( const QValueVector< KPDFPage * > & /*pages*/, bool documentChanged ) +{ + if ( !documentChanged ) + return; + + // clear contents + clear(); + + // request synopsis description (is a dom tree) + const DocumentSynopsis * syn = m_document->documentSynopsis(); + + // if not present, disable the contents tab + if ( !syn ) + { + emit hasTOC( false ); + return; + } + + // else populate the listview and enable the tab + addChildren( *syn ); + emit hasTOC( true ); +} + +void TOC::addChildren( const QDomNode & parentNode, KListViewItem * parentItem ) +{ + // keep track of the current listViewItem + TOCItem * currentItem = 0; + QDomNode n = parentNode.firstChild(); + while( !n.isNull() ) + { + // convert the node to an element (sure it is) + QDomElement e = n.toElement(); + + // insert the entry as top level (listview parented) or 2nd+ level + if ( !parentItem ) + currentItem = new TOCItem( this, currentItem, e ); + else + currentItem = new TOCItem( parentItem, currentItem, e ); + + // descend recursively and advance to the next node + if ( e.hasChildNodes() ) + addChildren( n, currentItem ); + + // open/keep close the item + bool isOpen = false; + if ( e.hasAttribute( "Open" ) ) + isOpen = QVariant( e.attribute( "Open" ) ).toBool(); + currentItem->setOpen( isOpen ); + + n = n.nextSibling(); + } +} + +void TOC::slotExecuted( QListViewItem *i ) +{ + TOCItem* tocItem = dynamic_cast<TOCItem*>( i ); + // that filters clicks on [+] that for a strange reason don't seem to be TOCItem* + if (tocItem == NULL) + return; + const QDomElement & e = tocItem->element(); + + QString externalFileName = e.attribute( "ExternalFileName" ); + if ( !externalFileName.isEmpty() ) + { + KPDFLinkGoto link( externalFileName, getViewport( e ) ); + m_document->processLink( &link ); + } + else + { + m_document->setViewport( getViewport( e ), TOC_ID ); + } +} + +DocumentViewport TOC::getViewport( const QDomElement &e ) const +{ + if ( e.hasAttribute( "Viewport" ) ) + { + // if the node has a viewport, set it + return DocumentViewport( e.attribute( "Viewport" ) ); + } + else if ( e.hasAttribute( "ViewportName" ) ) + { + // if the node references a viewport, get the reference and set it + const QString & page = e.attribute( "ViewportName" ); + const QString & viewport = m_document->getMetaData( "NamedViewport", page ); + if ( !viewport.isNull() ) + return DocumentViewport( viewport ); + } + return DocumentViewport(); +} + +#include "toc.moc" diff --git a/kpdf/ui/toc.h b/kpdf/ui/toc.h new file mode 100644 index 00000000..eaa398a5 --- /dev/null +++ b/kpdf/ui/toc.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2004-2006 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. * + ***************************************************************************/ + +#ifndef _KPDF_TOC_H_ +#define _KPDF_TOC_H_ + +#include <qdom.h> +#include <klistview.h> +#include "core/document.h" +#include "core/observer.h" + +class KPDFDocument; + +class TOC : public KListView, public DocumentObserver +{ +Q_OBJECT + public: + TOC(QWidget *parent, KPDFDocument *document); + ~TOC(); + + // inherited from DocumentObserver + uint observerId() const; + void notifySetup( const QValueVector< KPDFPage * > & pages, bool documentChanged ); + + signals: + void hasTOC(bool has); + + private slots: + void slotExecuted(QListViewItem *i); + + private: + void addChildren( const QDomNode & parentNode, KListViewItem * parentItem = 0 ); + DocumentViewport getViewport( const QDomElement &e ) const; + KPDFDocument *m_document; +}; + +#endif |