diff --git a/TODO b/TODO index d94cc189c..6a00e14a8 100644 --- a/TODO +++ b/TODO @@ -59,7 +59,6 @@ More items (first items will enter 'In progress list' first): -> presentation: implement missing transitions (6/11 done) -> presentation: add some gfx tools (like a red pencil) -> presentation: save a flag (to the xml) to open a pdf in presentation mode --> presentation: link following (difficult due to pagerects related to pageview pixmap only) -> presentation: wheel not visible on black. gradient appreciated on lighter backgrounds. -> investigate 'Splash' lack of smoothness at low resolutions (see lines in thumbnails) -> add search on the toc widget (a 'prune on type' lineedit like in thumbnails widget) @@ -73,6 +72,7 @@ More items (first items will enter 'In progress list' first): Done (newest features come first): -- merging from kdpf_annotations branch -- +-> ADD: presentation: link following (BR98388) -> ADD: Save zoom setting on exit -> ADD: Put fonts used by the document on the properties dialog -> ADD: partial implementation of XYZ links diff --git a/core/document.cpp b/core/document.cpp index 9edc9e889..8b562888f 100644 --- a/core/document.cpp +++ b/core/document.cpp @@ -1027,6 +1027,12 @@ void KPDFDocument::processLink( const KPDFLink * link ) case KPDFLinkAction::Quit: emit quit(); break; + case KPDFLinkAction::Presentation: + emit linkPresentation(); + break; + case KPDFLinkAction::EndPresentation: + emit linkEndPresentation(); + break; case KPDFLinkAction::Find: emit linkFind(); break; diff --git a/core/document.h b/core/document.h index 7400f194d..07fb11f3e 100644 --- a/core/document.h +++ b/core/document.h @@ -102,11 +102,13 @@ class KPDFDocument : public QObject void requestDone( PixmapRequest * request ); signals: - void close(); - void quit(); + void close(); + void quit(); void linkFind(); void linkGoToPage(); void openURL(const KURL &url); + void linkPresentation(); + void linkEndPresentation(); private: void sendGeneratorRequest(); diff --git a/core/generator_pdf/generator_pdf.cpp b/core/generator_pdf/generator_pdf.cpp index bf5b78d02..d83a8a6fe 100644 --- a/core/generator_pdf/generator_pdf.cpp +++ b/core/generator_pdf/generator_pdf.cpp @@ -304,7 +304,7 @@ void PDFGenerator::generatePixmap( PixmapRequest * request ) bool genTextPage = !page->hasSearchPage() && (request->width == page->width()) && (request->height == page->height()); // generate links and image rects if rendering pages on pageview - bool genObjectRects = request->id == PAGEVIEW_ID; + bool genObjectRects = request->id & (PAGEVIEW_ID | PRESENTATION_ID); // 0. LOCK [waits for the thread end] docLock.lock(); @@ -1080,7 +1080,7 @@ void PDFPixmapGeneratorThread::run() ( height == page->height() ); // generate links and image rects if rendering pages on pageview - bool genObjectRects = d->currentRequest->id == PAGEVIEW_ID; + bool genObjectRects = d->currentRequest->id & (PAGEVIEW_ID | PRESENTATION_ID); // 0. LOCK s[tart locking XPDF thread unsafe classes] d->generator->docLock.lock(); diff --git a/core/link.h b/core/link.h index 2954f7180..bbff4eaee 100644 --- a/core/link.h +++ b/core/link.h @@ -88,7 +88,7 @@ class KPDFLinkAction : public KPDFLink { public: // define types of actions - enum ActionType { PageFirst, PagePrev, PageNext, PageLast, HistoryBack, HistoryForward, Quit, Find, GoToPage, Close }; + enum ActionType { PageFirst, PagePrev, PageNext, PageLast, HistoryBack, HistoryForward, Quit, Presentation, EndPresentation, Find, GoToPage, Close }; // query for action type ActionType actionType() const { return m_type; } diff --git a/part.cpp b/part.cpp index 9b9f1322c..54b8eae1d 100644 --- a/part.cpp +++ b/part.cpp @@ -105,6 +105,8 @@ Part::Part(QWidget *parentWidget, const char *widgetName, m_document = new KPDFDocument(); connect( m_document, SIGNAL( linkFind() ), this, SLOT( slotFind() ) ); connect( m_document, SIGNAL( linkGoToPage() ), this, SLOT( slotGoToPage() ) ); + connect( m_document, SIGNAL( linkPresentation() ), this, SLOT( slotShowPresentation() ) ); + connect( m_document, SIGNAL( linkEndPresentation() ), this, SLOT( slotHidePresentation() ) ); connect( m_document, SIGNAL( openURL(const KURL &) ), this, SLOT( openURL(const KURL &) ) ); connect( m_document, SIGNAL( close() ), this, SLOT( close() ) ); @@ -431,6 +433,7 @@ bool Part::closeURL() m_temporaryLocalFile = QString::null; } + slotHidePresentation(); m_find->setEnabled( false ); m_findNext->setEnabled( false ); m_saveAs->setEnabled( false ); @@ -832,6 +835,12 @@ void Part::slotShowPresentation() m_presentationWidget = new PresentationWidget( widget(), m_document ); } +void Part::slotHidePresentation() +{ + if ( m_presentationWidget ) + delete (PresentationWidget*) m_presentationWidget; +} + void Part::slotPrint() { if (m_document->pages() == 0) return; diff --git a/part.h b/part.h index a590c25c8..b8cf0342c 100644 --- a/part.h +++ b/part.h @@ -106,6 +106,7 @@ protected slots: void slotShowProperties(); void slotShowLeftPanel(); void slotShowPresentation(); + void slotHidePresentation(); void close(); // can be connected to widget elements void updateViewActions(); diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index 48f179703..ba247382e 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -32,6 +32,7 @@ #include "pagepainter.h" #include "core/generator.h" #include "core/page.h" +#include "core/link.h" #include "conf/settings.h" @@ -49,7 +50,8 @@ struct PresentationFrame PresentationWidget::PresentationWidget( QWidget * parent, KPDFDocument * doc ) - : QDialog( parent, "presentationWidget", true, WDestructiveClose | WStyle_NoBorder), m_document( doc ), m_frameIndex( -1 ) + : 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 ); @@ -222,32 +224,62 @@ 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() ); - else - slotNextPage(); + 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 ) { - if (m_width == -1) return; - - // hide a shown bar when exiting the area + // 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(); } - // show a hidden bar if mouse reaches the top of the screen - else if ( !e->y() ) - m_topBar->show(); - // change page if dragging the mouse over the 'wheel' - else if ( e->state() == Qt::LeftButton && m_overlayGeometry.contains( e->pos() ) ) + 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 ) @@ -325,6 +357,58 @@ void PresentationWidget::paintEvent( QPaintEvent * pe ) // +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()); + } +} + void PresentationWidget::overlayClick( const QPoint & position ) { // clicking the progress indicator @@ -356,12 +440,20 @@ void PresentationWidget::changePage( int newPage ) // 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 * > request; request.push_back( new PixmapRequest( PRESENTATION_ID, m_frameIndex, pixW, pixH, PRESENTATION_PRIO ) ); m_document->requestPixmaps( request ); + // restore cursor + QApplication::restoreOverrideCursor(); } else + { + // make the background pixmap generatePage(); + } } void PresentationWidget::generatePage() @@ -395,6 +487,13 @@ void PresentationWidget::generatePage() 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 ) @@ -477,12 +576,14 @@ void PresentationWidget::generateContentsPage( int pageNum, QPainter & p ) } } +// 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, 0, side, side ); + m_overlayGeometry.setRect( m_width - side - 4, 4, side, side ); if ( m_lastRenderedOverlay.width() != side ) m_lastRenderedOverlay.resize( side, side ); @@ -496,13 +597,14 @@ void PresentationWidget::generateOverlay() // draw PIE SLICES in blue levels (the levels will then be the alpha component) int pages = m_document->pages(); - if ( pages > 36 ) + if ( pages > 28 ) { // draw continuous slices int degrees = (int)( 360 * (float)(m_frameIndex + 1) / (float)pages ); - pixmapPainter.setPen( 0x20 ); - pixmapPainter.setBrush( 0x10 ); + pixmapPainter.setPen( 0x05 ); + pixmapPainter.setBrush( 0x40 ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 ); - pixmapPainter.setBrush( 0xC0 ); + pixmapPainter.setPen( 0x40 ); + pixmapPainter.setBrush( 0xF0 ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, -degrees*16 ); } else @@ -512,7 +614,7 @@ void PresentationWidget::generateOverlay() { float newCoord = -90 + 360 * (float)(i + 1) / (float)pages; pixmapPainter.setPen( i <= m_frameIndex ? 0x40 : 0x05 ); - pixmapPainter.setBrush( i <= m_frameIndex ? 0xC0 : 0x10 ); + 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; @@ -533,15 +635,52 @@ void PresentationWidget::generateOverlay() // end drawing pixmap and halve image pixmapPainter.end(); - side /= 2; - QImage image( doublePixmap.convertToImage().smoothScale( side, side ) ); + QImage image( doublePixmap.convertToImage().smoothScale( side / 2, side / 2 ) ); image.setAlphaBuffer( true ); - int red = 52, green = 115, blue = 178, - pixels = image.width() * image.height(); - unsigned int * data = (unsigned int *)image.bits(); - for( int i = 0; i < pixels; ++i ) - data[i] = qRgba( red, green, blue, data[i] & 0xFF ); + // 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 diff --git a/ui/presentationwidget.h b/ui/presentationwidget.h index fd59855fa..d601fb179 100644 --- a/ui/presentationwidget.h +++ b/ui/presentationwidget.h @@ -22,6 +22,7 @@ class QTimer; class KPDFDocument; class KPDFPage; +class KPDFLink; class PresentationFrame; /** @@ -49,10 +50,13 @@ class PresentationWidget : public QDialog, public DocumentObserver 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(); @@ -69,6 +73,8 @@ class PresentationWidget : public QDialog, public DocumentObserver QPixmap m_lastRenderedPixmap; QPixmap m_lastRenderedOverlay; QRect m_overlayGeometry; + const KPDFLink * m_pressedLink; + bool m_handCursor; // transition related QTimer * m_transitionTimer;