SVN_SILENT made messages (.desktop file)
[kdegraphics.git] / okular / ui / presentationwidget.cpp
blob72e51f678cd394d0a0b304d4e829e54463185d30
1 /***************************************************************************
2 * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
10 #include "presentationwidget.h"
12 // qt/kde includes
13 #include <QtDBus/QDBusConnection>
14 #include <QtDBus/QDBusMessage>
15 #include <QtDBus/QDBusReply>
17 #include <qevent.h>
18 #include <qfontmetrics.h>
19 #include <kicon.h>
20 #include <qtimer.h>
21 #include <qimage.h>
22 #include <qlabel.h>
23 #include <qpainter.h>
24 #include <qstyle.h>
25 #include <qstyleoption.h>
26 #include <qtooltip.h>
27 #include <qvalidator.h>
28 #include <qapplication.h>
29 #include <qdesktopwidget.h>
30 #include <kcursor.h>
31 #include <krandom.h>
32 #include <qtoolbar.h>
33 #include <kaction.h>
34 #include <kactioncollection.h>
35 #include <klineedit.h>
36 #include <klocale.h>
37 #include <kiconloader.h>
38 #include <kmessagebox.h>
39 #include <kselectaction.h>
40 #include <kshortcut.h>
41 #include <kdialog.h>
43 // system includes
44 #include <stdlib.h>
45 #include <math.h>
47 // local includes
48 #include "annotationtools.h"
49 #include "pagepainter.h"
50 #include "presentationsearchbar.h"
51 #include "videowidget.h"
52 #include "core/action.h"
53 #include "core/annotations.h"
54 #include "core/audioplayer.h"
55 #include "core/document.h"
56 #include "core/generator.h"
57 #include "core/movie.h"
58 #include "core/page.h"
59 #include "settings.h"
62 // comment this to disable the top-right progress indicator
63 #define ENABLE_PROGRESS_OVERLAY
66 // a frame contains a pointer to the page object, its geometry and the
67 // transition effect to the next frame
68 struct PresentationFrame
70 ~PresentationFrame()
72 qDeleteAll( videoWidgets );
75 void recalcGeometry( int width, int height, float screenRatio )
77 // calculate frame geometry keeping constant aspect ratio
78 float pageRatio = page->ratio();
79 int pageWidth = width,
80 pageHeight = height;
81 if ( pageRatio > screenRatio )
82 pageWidth = (int)( (float)pageHeight / pageRatio );
83 else
84 pageHeight = (int)( (float)pageWidth * pageRatio );
85 geometry.setRect( ( width - pageWidth ) / 2,
86 ( height - pageHeight ) / 2,
87 pageWidth, pageHeight );
88 Q_FOREACH ( VideoWidget *vw, videoWidgets )
90 const Okular::NormalizedRect r = vw->normGeometry();
91 QRect vwgeom = r.geometry( geometry.width(), geometry.height() );
92 vw->resize( vwgeom.size() );
93 vw->move( geometry.topLeft() + vwgeom.topLeft() );
97 const Okular::Page * page;
98 QRect geometry;
99 QHash< Okular::Movie *, VideoWidget * > videoWidgets;
103 // a custom QToolBar that basically does not propagate the event if the widget
104 // background is not automatically filled
105 class PresentationToolBar : public QToolBar
107 public:
108 PresentationToolBar( QWidget * parent = 0 )
109 : QToolBar( parent )
112 protected:
113 void mousePressEvent( QMouseEvent * e )
115 QToolBar::mousePressEvent( e );
116 e->accept();
119 void mouseReleaseEvent( QMouseEvent * e )
121 QToolBar::mouseReleaseEvent( e );
122 e->accept();
127 PresentationWidget::PresentationWidget( QWidget * parent, Okular::Document * doc, KActionCollection * collection )
128 : QWidget( 0 /* must be null, to have an independent widget */, Qt::FramelessWindowHint ),
129 m_pressedLink( 0 ), m_handCursor( false ), m_drawingEngine( 0 ), m_screenSaverCookie( -1 ),
130 m_parentWidget( parent ),
131 m_document( doc ), m_frameIndex( -1 ), m_topBar( 0 ), m_pagesEdit( 0 ), m_searchBar( 0 ),
132 m_screenSelect( 0 ), m_blockNotifications( false ), m_inBlackScreenMode( false )
134 Q_UNUSED( parent )
135 setAttribute( Qt::WA_DeleteOnClose );
136 setAttribute( Qt::WA_OpaquePaintEvent );
137 setObjectName( "presentationWidget" );
138 QString caption = doc->metaData( "DocumentTitle" ).toString();
139 if ( caption.trimmed().isEmpty() )
140 caption = doc->currentDocument().fileName();
141 setWindowTitle( KDialog::makeStandardCaption( caption ) );
143 m_width = -1;
144 m_screen = -2;
146 // create top toolbar
147 m_topBar = new PresentationToolBar( this );
148 m_topBar->setObjectName( "presentationBar" );
149 m_topBar->setIconSize( QSize( 32, 32 ) );
150 m_topBar->setMovable( false );
151 m_topBar->addAction( KIcon( layoutDirection() == Qt::RightToLeft ? "go-next" : "go-previous" ), i18n( "Previous Page" ), this, SLOT( slotPrevPage() ) );
152 m_pagesEdit = new KLineEdit( m_topBar );
153 QSizePolicy sp = m_pagesEdit->sizePolicy();
154 sp.setHorizontalPolicy( QSizePolicy::Minimum );
155 m_pagesEdit->setSizePolicy( sp );
156 QFontMetrics fm( m_pagesEdit->font() );
157 QStyleOptionFrame option;
158 option.initFrom( m_pagesEdit );
159 m_pagesEdit->setMaximumWidth( fm.width( QString::number( m_document->pages() ) ) + 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit ) + 4 ); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp
160 QIntValidator *validator = new QIntValidator( 1, m_document->pages(), m_pagesEdit );
161 m_pagesEdit->setValidator( validator );
162 m_topBar->addWidget( m_pagesEdit );
163 QLabel *pagesLabel = new QLabel( m_topBar );
164 pagesLabel->setText( QLatin1String( " / " ) + QString::number( m_document->pages() ) + QLatin1String( " " ) );
165 m_topBar->addWidget( pagesLabel );
166 connect( m_pagesEdit, SIGNAL( returnPressed() ), this, SLOT( slotPageChanged() ) );
167 m_topBar->addAction( KIcon( layoutDirection() == Qt::RightToLeft ? "go-previous" : "go-next" ), i18n( "Next Page" ), this, SLOT( slotNextPage() ) );
168 m_topBar->addSeparator();
169 QAction *drawingAct = collection->action( "presentation_drawing_mode" );
170 connect( drawingAct, SIGNAL( toggled( bool ) ), SLOT( togglePencilMode( bool ) ) );
171 m_topBar->addAction( drawingAct );
172 addAction( drawingAct );
173 QAction *eraseDrawingAct = collection->action( "presentation_erase_drawings" );
174 connect( eraseDrawingAct, SIGNAL( triggered() ), SLOT( clearDrawings() ) );
175 m_topBar->addAction( eraseDrawingAct );
176 addAction( eraseDrawingAct );
177 QDesktopWidget *desktop = QApplication::desktop();
178 if ( desktop->numScreens() > 1 )
180 m_topBar->addSeparator();
181 m_screenSelect = new KSelectAction( KIcon( "video-display" ), i18n( "Switch Screen" ), m_topBar );
182 m_screenSelect->setToolBarMode( KSelectAction::MenuMode );
183 m_screenSelect->setToolButtonPopupMode( QToolButton::InstantPopup );
184 m_topBar->addAction( m_screenSelect );
185 const int screenCount = desktop->numScreens();
186 for ( int i = 0; i < screenCount; ++i )
188 QAction *act = m_screenSelect->addAction( i18nc( "%1 is the screen number (0, 1, ...)", "Screen %1", i ) );
189 act->setData( qVariantFromValue( i ) );
192 QWidget *spacer = new QWidget( m_topBar );
193 spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::MinimumExpanding );
194 m_topBar->addWidget( spacer );
195 m_topBar->addAction( KIcon( "application-exit" ), i18n( "Exit Presentation Mode" ), this, SLOT( close() ) );
196 m_topBar->setAutoFillBackground( true );
197 m_topBar->hide();
198 // change topbar background color
199 QPalette p = m_topBar->palette();
200 p.setColor( QPalette::Active, QPalette::Button, Qt::gray );
201 p.setColor( QPalette::Active, QPalette::Background, Qt::darkGray );
202 m_topBar->setPalette( p );
204 // misc stuff
205 setMouseTracking( true );
206 setContextMenuPolicy( Qt::PreventContextMenu );
207 m_transitionTimer = new QTimer( this );
208 m_transitionTimer->setSingleShot( true );
209 connect( m_transitionTimer, SIGNAL( timeout() ), this, SLOT( slotTransitionStep() ) );
210 m_overlayHideTimer = new QTimer( this );
211 m_overlayHideTimer->setSingleShot( true );
212 connect( m_overlayHideTimer, SIGNAL( timeout() ), this, SLOT( slotHideOverlay() ) );
213 m_nextPageTimer = new QTimer( this );
214 m_nextPageTimer->setSingleShot( true );
215 connect( m_nextPageTimer, SIGNAL( timeout() ), this, SLOT( slotNextPage() ) );
217 // handle cursor appearance as specified in configuration
218 if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay )
220 KCursor::setAutoHideCursor( this, true );
221 KCursor::setHideCursorDelay( 3000 );
223 else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden )
225 setCursor( QCursor( Qt::BlankCursor ) );
228 setupActions( collection );
230 // inhibit the screen saver
231 inhibitScreenSaver();
233 QTimer::singleShot( 0, this, SLOT( slotDelayedEvents() ) );
236 PresentationWidget::~PresentationWidget()
238 // allow the screen saver again
239 allowScreenSaver();
241 // stop the audio playbacks
242 Okular::AudioPlayer::instance()->stopPlaybacks();
244 // remove our highlights
245 if ( m_searchBar )
247 m_document->resetSearch( PRESENTATION_SEARCH_ID );
250 // remove this widget from document observer
251 m_document->removeObserver( this );
253 QAction *drawingAct = m_ac->action( "presentation_drawing_mode" );
254 disconnect( drawingAct, 0, this, 0 );
255 if ( drawingAct->isChecked() )
256 drawingAct->toggle();
257 m_document->removePageAnnotations( m_document->viewport().pageNumber, m_currentPageDrawings );
258 delete m_drawingEngine;
260 // delete frames
261 QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end();
262 for ( ; fIt != fEnd; ++fIt )
263 delete *fIt;
267 void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags )
269 // same document, nothing to change - here we assume the document sets up
270 // us with the whole document set as first notifySetup()
271 if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) )
272 return;
274 // delete previous frames (if any (shouldn't be))
275 QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end();
276 for ( ; fIt != fEnd; ++fIt )
277 delete *fIt;
278 if ( !m_frames.isEmpty() )
279 kWarning() << "Frames setup changed while a Presentation is in progress.";
280 m_frames.clear();
282 // create the new frames
283 QVector< Okular::Page * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end();
284 float screenRatio = (float)m_height / (float)m_width;
285 for ( ; setIt != setEnd; ++setIt )
287 PresentationFrame * frame = new PresentationFrame();
288 frame->page = *setIt;
289 const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
290 QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.begin(), aEnd = annotations.end();
291 for ( ; aIt != aEnd; ++aIt )
293 Okular::Annotation * a = *aIt;
294 if ( a->subType() == Okular::Annotation::AMovie )
296 Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
297 VideoWidget * vw = new VideoWidget( movieAnn, m_document, this );
298 frame->videoWidgets.insert( movieAnn->movie(), vw );
299 vw->hide();
302 frame->recalcGeometry( m_width, m_height, screenRatio );
303 // add the frame to the vector
304 m_frames.push_back( frame );
307 // get metadata from the document
308 m_metaStrings.clear();
309 const Okular::DocumentInfo * info = m_document->documentInfo();
310 if ( info )
312 if ( !info->get( "title" ).isNull() )
313 m_metaStrings += i18n( "Title: %1", info->get( "title" ) );
314 if ( !info->get( "author" ).isNull() )
315 m_metaStrings += i18n( "Author: %1", info->get( "author" ) );
317 m_metaStrings += i18n( "Pages: %1", m_document->pages() );
318 m_metaStrings += i18n( "Click to begin" );
321 void PresentationWidget::notifyViewportChanged( bool /*smoothMove*/ )
323 // discard notifications if displaying the summary
324 if ( m_frameIndex == -1 && Okular::Settings::slidesShowSummary() )
325 return;
327 // display the current page
328 changePage( m_document->viewport().pageNumber );
330 // auto advance to the next page if set
331 startAutoChangeTimer();
334 void PresentationWidget::notifyPageChanged( int pageNumber, int changedFlags )
336 // if we are blocking the notifications, do nothing
337 if ( m_blockNotifications )
338 return;
340 // check if it's the last requested pixmap. if so update the widget.
341 if ( (changedFlags & ( DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights ) ) && pageNumber == m_frameIndex )
342 generatePage( changedFlags & ( DocumentObserver::Annotations | DocumentObserver::Highlights ) );
345 bool PresentationWidget::canUnloadPixmap( int pageNumber ) const
347 if ( Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Aggressive )
349 // can unload all pixmaps except for the currently visible one
350 return pageNumber != m_frameIndex;
352 else
354 // can unload all pixmaps except for the currently visible one, previous and next
355 return qAbs(pageNumber - m_frameIndex) <= 1;
359 void PresentationWidget::setupActions( KActionCollection * collection )
361 m_ac = collection;
362 addAction( m_ac->action( "go_previous" ) );
363 addAction( m_ac->action( "go_next" ) );
364 addAction( m_ac->action( "first_page" ) );
365 addAction( m_ac->action( "last_page" ) );
367 QAction *action = m_ac->action( "switch_blackscreen_mode" );
368 connect( action, SIGNAL( toggled( bool ) ), SLOT( toggleBlackScreenMode( bool ) ) );
369 addAction( action );
373 // <widget events>
374 bool PresentationWidget::event( QEvent * e )
376 if ( e->type() == QEvent::ToolTip )
378 QHelpEvent * he = (QHelpEvent*)e;
380 QRect r;
381 const Okular::Action * link = getLink( he->x(), he->y(), &r );
383 if ( link )
385 QString tip = link->actionTip();
386 if ( !tip.isEmpty() )
387 QToolTip::showText( he->globalPos(), tip, this, r );
389 e->accept();
390 return true;
392 else
393 // do not stop the event
394 return QWidget::event( e );
397 void PresentationWidget::keyPressEvent( QKeyEvent * e )
399 if (m_width == -1) return;
401 if ( e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_PageUp )
402 slotPrevPage();
403 else if ( e->key() == Qt::Key_Right || e->key() == Qt::Key_Space || e->key() == Qt::Key_PageDown )
404 slotNextPage();
405 else if ( e->key() == Qt::Key_Home )
406 slotFirstPage();
407 else if ( e->key() == Qt::Key_End )
408 slotLastPage();
409 else if ( e->key() == Qt::Key_Escape )
411 if ( !m_topBar->isHidden() )
412 m_topBar->hide();
413 else
414 close();
418 void PresentationWidget::wheelEvent( QWheelEvent * e )
420 if (m_width == -1) return;
422 // performance note: don't remove the clipping
423 int div = e->delta() / 120;
424 if ( div > 0 )
426 if ( div > 3 )
427 div = 3;
428 while ( div-- )
429 slotPrevPage();
431 else if ( div < 0 )
433 if ( div < -3 )
434 div = -3;
435 while ( div++ )
436 slotNextPage();
440 void PresentationWidget::mousePressEvent( QMouseEvent * e )
442 if (m_width == -1) return;
444 if ( m_drawingEngine )
446 QRect r = routeMouseDrawingEvent( e );
447 if ( r.isValid() )
449 m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() );
450 update( m_drawingRect );
452 return;
455 // pressing left button
456 if ( e->button() == Qt::LeftButton )
458 // if pressing on a link, skip other checks
459 if ( ( m_pressedLink = getLink( e->x(), e->y() ) ) )
460 return;
462 // handle clicking on top-right overlay
463 if ( m_overlayGeometry.contains( e->pos() ) )
465 overlayClick( e->pos() );
466 return;
469 // if no other actions, go to next page
470 slotNextPage();
472 // pressing right button
473 else if ( e->button() == Qt::RightButton )
474 slotPrevPage();
477 void PresentationWidget::mouseReleaseEvent( QMouseEvent * e )
479 if ( m_drawingEngine )
481 QRect r = routeMouseDrawingEvent( e );
482 (void)r;
483 if ( m_drawingEngine->creationCompleted() )
485 QList< Okular::Annotation * > annots = m_drawingEngine->end();
486 // manually disable and re-enable the pencil mode, so we can do
487 // cleaning of the actual drawer and create a new one just after
488 // that - that gives continuous drawing
489 togglePencilMode( false );
490 togglePencilMode( true );
491 foreach( Okular::Annotation * ann, annots )
492 m_document->addPageAnnotation( m_frameIndex, ann );
493 m_currentPageDrawings << annots;
495 return;
498 // if releasing on the same link we pressed over, execute it
499 if ( m_pressedLink && e->button() == Qt::LeftButton )
501 const Okular::Action * link = getLink( e->x(), e->y() );
502 if ( link == m_pressedLink )
503 m_document->processAction( link );
504 m_pressedLink = 0;
508 void PresentationWidget::mouseMoveEvent( QMouseEvent * e )
510 // safety check
511 if ( m_width == -1 )
512 return;
514 // update cursor and tooltip if hovering a link
515 if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden )
516 testCursorOnLink( e->x(), e->y() );
518 if ( !m_topBar->isHidden() )
520 // hide a shown bar when exiting the area
521 if ( e->y() > ( m_topBar->height() + 1 ) )
523 m_topBar->hide();
524 setFocus( Qt::OtherFocusReason );
527 else
529 if ( m_drawingEngine && e->buttons() != Qt::NoButton )
531 QRect r = routeMouseDrawingEvent( e );
533 m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() );
534 update( m_drawingRect );
537 else
539 // show the bar if reaching top 2 pixels
540 if ( e->y() <= 1 )
541 m_topBar->show();
542 // handle "dragging the wheel" if clicking on its geometry
543 else if ( ( QApplication::mouseButtons() & Qt::LeftButton ) && m_overlayGeometry.contains( e->pos() ) )
544 overlayClick( e->pos() );
549 void PresentationWidget::paintEvent( QPaintEvent * pe )
551 if ( m_inBlackScreenMode )
553 QPainter painter( this );
554 painter.fillRect( pe->rect(), Qt::black );
555 return;
558 if (m_width == -1)
560 m_width = width();
561 m_height = height();
563 connect( m_document, SIGNAL( linkFind() ), this, SLOT( slotFind() ) );
565 // register this observer in document. events will come immediately
566 m_document->addObserver( this );
568 // show summary if requested
569 if ( Okular::Settings::slidesShowSummary() )
570 generatePage();
573 // check painting rect consistancy
574 QRect r = pe->rect().intersect( QRect( QPoint( 0, 0 ), geometry().size() ) );
575 if ( r.isNull() )
576 return;
578 if ( m_lastRenderedPixmap.isNull() )
580 QPainter painter( this );
581 painter.fillRect( pe->rect(), Okular::Settings::slidesBackgroundColor() );
582 return;
585 // blit the pixmap to the screen
586 QVector<QRect> allRects = pe->region().rects();
587 uint numRects = allRects.count();
588 QPainter painter( this );
589 for ( uint i = 0; i < numRects; i++ )
591 const QRect & r = allRects[i];
592 if ( !r.isValid() )
593 continue;
594 #ifdef ENABLE_PROGRESS_OVERLAY
595 if ( Okular::Settings::slidesShowProgress() && r.intersects( m_overlayGeometry ) )
597 // backbuffer the overlay operation
598 QPixmap backPixmap( r.size() );
599 QPainter pixPainter( &backPixmap );
601 // first draw the background on the backbuffer
602 pixPainter.drawPixmap( QPoint(0,0), m_lastRenderedPixmap, r );
604 // then blend the overlay (a piece of) over the background
605 QRect ovr = m_overlayGeometry.intersect( r );
606 pixPainter.drawPixmap( ovr.left() - r.left(), ovr.top() - r.top(),
607 m_lastRenderedOverlay, ovr.left() - m_overlayGeometry.left(),
608 ovr.top() - m_overlayGeometry.top(), ovr.width(), ovr.height() );
610 // finally blit the pixmap to the screen
611 pixPainter.end();
612 painter.drawPixmap( r.topLeft(), backPixmap, backPixmap.rect() );
613 } else
614 #endif
615 // copy the rendered pixmap to the screen
616 painter.drawPixmap( r.topLeft(), m_lastRenderedPixmap, r );
618 if ( m_drawingEngine && m_drawingRect.intersects( pe->rect() ) )
620 const QRect & geom = m_frames[ m_frameIndex ]->geometry;
621 painter.save();
622 painter.translate( geom.topLeft() );
623 m_drawingEngine->paint( &painter, geom.width(), geom.height(), m_drawingRect.intersect( pe->rect() ) );
624 painter.restore();
626 painter.end();
629 void PresentationWidget::resizeEvent( QResizeEvent *re )
631 // kDebug() << re->oldSize() << "=>" << re->size();
632 if ( re->oldSize() == QSize( -1, -1 ) )
633 return;
635 m_screen = QApplication::desktop()->screenNumber( this );
637 applyNewScreenSize( re->oldSize() );
639 // </widget events>
642 const Okular::Action * PresentationWidget::getLink( int x, int y, QRect * geometry ) const
644 // no links on invalid pages
645 if ( geometry && !geometry->isNull() )
646 geometry->setRect( 0, 0, 0, 0 );
647 if ( m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size() )
648 return 0;
650 // get frame, page and geometry
651 const PresentationFrame * frame = m_frames[ m_frameIndex ];
652 const Okular::Page * page = frame->page;
653 const QRect & frameGeometry = frame->geometry;
655 // compute normalized x and y
656 double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width();
657 double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height();
659 // no links outside the pages
660 if ( nx < 0 || nx > 1 || ny < 0 || ny > 1 )
661 return 0;
663 // check if 1) there is an object and 2) it's a link
664 const QRect d = QApplication::desktop()->screenGeometry( m_screen );
665 const Okular::ObjectRect * object = page->objectRect( Okular::ObjectRect::Action, nx, ny, d.width(), d.height() );
666 if ( !object )
667 return 0;
669 // compute link geometry if destination rect present
670 if ( geometry )
672 *geometry = object->boundingRect( frameGeometry.width(), frameGeometry.height() );
673 geometry->translate( frameGeometry.left(), frameGeometry.top() );
676 // return the link pointer
677 return (Okular::Action *)object->object();
680 void PresentationWidget::testCursorOnLink( int x, int y )
682 const Okular::Action * link = getLink( x, y, 0 );
684 // only react on changes (in/out from a link)
685 if ( (link && !m_handCursor) || (!link && m_handCursor) )
687 // change cursor shape
688 m_handCursor = link != 0;
689 setCursor( QCursor( m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor ) );
693 void PresentationWidget::overlayClick( const QPoint & position )
695 // clicking the progress indicator
696 int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2,
697 yPos = m_overlayGeometry.height() / 2 - position.y();
698 if ( !xPos && !yPos )
699 return;
701 // compute angle relative to indicator (note coord transformation)
702 float angle = 0.5 + 0.5 * atan2( (double)-xPos, (double)-yPos ) / M_PI;
703 int pageIndex = (int)( angle * ( m_frames.count() - 1 ) + 0.5 );
705 // go to selected page
706 changePage( pageIndex );
709 void PresentationWidget::changePage( int newPage )
711 if ( m_frameIndex == newPage )
712 return;
714 const int oldIndex = m_frameIndex;
715 // check if pixmap exists or else request it
716 m_frameIndex = newPage;
717 PresentationFrame * frame = m_frames[ m_frameIndex ];
718 int pixW = frame->geometry.width();
719 int pixH = frame->geometry.height();
721 bool signalsBlocked = m_pagesEdit->signalsBlocked();
722 m_pagesEdit->blockSignals( true );
723 m_pagesEdit->setText( QString::number( m_frameIndex + 1 ) );
724 m_pagesEdit->blockSignals( signalsBlocked );
726 // if pixmap not inside the Okular::Page we request it and wait for
727 // notifyPixmapChanged call or else we can proceed to pixmap generation
728 if ( !frame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) )
730 requestPixmaps();
732 else
734 // make the background pixmap
735 generatePage();
738 // set a new viewport in document if page number differs
739 if ( m_frameIndex != -1 && m_frameIndex != m_document->viewport().pageNumber )
741 // stop the audio playback, if any
742 Okular::AudioPlayer::instance()->stopPlaybacks();
743 // perform the page closing action, if any
744 if ( m_document->page( m_document->viewport().pageNumber )->pageAction( Okular::Page::Closing ) )
745 m_document->processAction( m_document->page( m_document->viewport().pageNumber )->pageAction( Okular::Page::Closing ) );
747 // remove the drawing on the old page before switching
748 clearDrawings();
749 m_document->setViewportPage( m_frameIndex, PRESENTATION_ID );
751 // perform the page opening action, if any
752 if ( m_document->page( m_frameIndex)->pageAction( Okular::Page::Opening ) )
753 m_document->processAction( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) );
757 if ( oldIndex != m_frameIndex )
759 if ( oldIndex != -1 )
761 Q_FOREACH ( VideoWidget *vw, m_frames[ oldIndex ]->videoWidgets )
763 vw->stop();
764 vw->hide();
768 Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets )
770 vw->show();
771 vw->raise();
777 void PresentationWidget::generatePage( bool disableTransition )
779 if ( m_lastRenderedPixmap.isNull() )
780 m_lastRenderedPixmap = QPixmap( m_width, m_height );
782 // opens the painter over the pixmap
783 QPainter pixmapPainter;
784 pixmapPainter.begin( &m_lastRenderedPixmap );
785 // generate welcome page
786 if ( m_frameIndex == -1 )
787 generateIntroPage( pixmapPainter );
788 // generate a normal pixmap with extended margin filling
789 if ( m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages() )
790 generateContentsPage( m_frameIndex, pixmapPainter );
791 pixmapPainter.end();
793 // generate the top-right corner overlay
794 #ifdef ENABLE_PROGRESS_OVERLAY
795 if ( Okular::Settings::slidesShowProgress() && m_frameIndex != -1 )
796 generateOverlay();
797 #endif
799 // start transition on pages that have one
800 if ( !disableTransition && Okular::Settings::slidesTransitionsEnabled() )
802 const Okular::PageTransition * transition = m_frameIndex != -1 ?
803 m_frames[ m_frameIndex ]->page->transition() : 0;
804 if ( transition )
805 initTransition( transition );
806 else {
807 Okular::PageTransition trans = defaultTransition();
808 initTransition( &trans );
811 else
813 Okular::PageTransition trans = defaultTransition( Okular::Settings::EnumSlidesTransition::Replace );
814 initTransition( &trans );
817 // update cursor + tooltip
818 if ( Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden )
820 QPoint p = mapFromGlobal( QCursor::pos() );
821 testCursorOnLink( p.x(), p.y() );
825 void PresentationWidget::generateIntroPage( QPainter & p )
827 // use a vertical gray gradient background
828 int blend1 = m_height / 10,
829 blend2 = 9 * m_height / 10;
830 int baseTint = QColor(Qt::gray).red();
831 for ( int i = 0; i < m_height; i++ )
833 int k = baseTint;
834 if ( i < blend1 )
835 k -= (int)( baseTint * (i-blend1)*(i-blend1) / (float)(blend1 * blend1) );
836 if ( i > blend2 )
837 k += (int)( (255-baseTint) * (i-blend2)*(i-blend2) / (float)(blend1 * blend1) );
838 p.fillRect( 0, i, m_width, 1, QColor( k, k, k ) );
841 // draw okular logo in the four corners
842 QPixmap logo = DesktopIcon( "okular", 64 );
843 if ( !logo.isNull() )
845 p.drawPixmap( 5, 5, logo );
846 p.drawPixmap( m_width - 5 - logo.width(), 5, logo );
847 p.drawPixmap( m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo );
848 p.drawPixmap( 5, m_height - 5 - logo.height(), logo );
851 // draw metadata text (the last line is 'click to begin')
852 int strNum = m_metaStrings.count(),
853 strHeight = m_height / ( strNum + 4 ),
854 fontHeight = 2 * strHeight / 3;
855 QFont font( p.font() );
856 font.setPixelSize( fontHeight );
857 QFontMetrics metrics( font );
858 for ( int i = 0; i < strNum; i++ )
860 // set a font to fit text width
861 float wScale = (float)metrics.boundingRect( m_metaStrings[i] ).width() / (float)m_width;
862 QFont f( font );
863 if ( wScale > 1.0 )
864 f.setPixelSize( (int)( (float)fontHeight / (float)wScale ) );
865 p.setFont( f );
867 // text shadow
868 p.setPen( Qt::darkGray );
869 p.drawText( 2, m_height / 4 + strHeight * i + 2, m_width, strHeight,
870 Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] );
871 // text body
872 p.setPen( 128 + (127 * i) / strNum );
873 p.drawText( 0, m_height / 4 + strHeight * i, m_width, strHeight,
874 Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] );
878 void PresentationWidget::generateContentsPage( int pageNum, QPainter & p )
880 PresentationFrame * frame = m_frames[ pageNum ];
882 // translate painter and contents rect
883 QRect geom( frame->geometry );
884 p.translate( geom.left(), geom.top() );
885 geom.translate( -geom.left(), -geom.top() );
887 // draw the page using the shared PagePainter class
888 int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations;
889 PagePainter::paintPageOnPainter( &p, frame->page, PRESENTATION_ID, flags,
890 geom.width(), geom.height(), geom );
892 // restore painter
893 p.translate( -frame->geometry.left(), -frame->geometry.top() );
895 // fill unpainted areas with background color
896 QRegion unpainted( QRect( 0, 0, m_width, m_height ) );
897 QVector<QRect> rects = unpainted.subtract( frame->geometry ).rects();
898 for ( int i = 0; i < rects.count(); i++ )
900 const QRect & r = rects[i];
901 p.fillRect( r, Okular::Settings::slidesBackgroundColor() );
905 // from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation)
906 inline int qt_div255(int x) { return (x + (x>>8) + 0x80) >> 8; }
907 void PresentationWidget::generateOverlay()
909 #ifdef ENABLE_PROGRESS_OVERLAY
910 // calculate overlay geometry and resize pixmap if needed
911 int side = m_width / 16;
912 m_overlayGeometry.setRect( m_width - side - 4, 4, side, side );
914 // note: to get a sort of antialiasing, we render the pixmap double sized
915 // and the resulting image is smoothly scaled down. So here we open a
916 // painter on the double sized pixmap.
917 side *= 2;
918 QPixmap doublePixmap( side, side );
919 doublePixmap.fill( Qt::black );
920 QPainter pixmapPainter( &doublePixmap );
921 pixmapPainter.setRenderHints( QPainter::Antialiasing );
923 // draw PIE SLICES in blue levels (the levels will then be the alpha component)
924 int pages = m_document->pages();
925 if ( pages > 28 )
926 { // draw continuous slices
927 int degrees = (int)( 360 * (float)(m_frameIndex + 1) / (float)pages );
928 pixmapPainter.setPen( 0x05 );
929 pixmapPainter.setBrush( QColor( 0x40 ) );
930 pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 );
931 pixmapPainter.setPen( 0x40 );
932 pixmapPainter.setBrush( QColor( 0xF0 ) );
933 pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, -degrees*16 );
935 else
936 { // draw discrete slices
937 float oldCoord = -90;
938 for ( int i = 0; i < pages; i++ )
940 float newCoord = -90 + 360 * (float)(i + 1) / (float)pages;
941 pixmapPainter.setPen( i <= m_frameIndex ? 0x40 : 0x05 );
942 pixmapPainter.setBrush( QColor( i <= m_frameIndex ? 0xF0 : 0x40 ) );
943 pixmapPainter.drawPie( 2, 2, side - 4, side - 4,
944 (int)( -16*(oldCoord + 1) ), (int)( -16*(newCoord - (oldCoord + 2)) ) );
945 oldCoord = newCoord;
948 int circleOut = side / 4;
949 pixmapPainter.setPen( Qt::black );
950 pixmapPainter.setBrush( Qt::black );
951 pixmapPainter.drawEllipse( circleOut, circleOut, side - 2*circleOut, side - 2*circleOut );
953 // draw TEXT using maximum opacity
954 QFont f( pixmapPainter.font() );
955 f.setPixelSize( side / 4 );
956 pixmapPainter.setFont( f );
957 pixmapPainter.setPen( 0xFF );
958 // use a little offset to prettify output
959 pixmapPainter.drawText( 2, 2, side, side, Qt::AlignCenter, QString::number( m_frameIndex + 1 ) );
961 // end drawing pixmap and halve image
962 pixmapPainter.end();
963 QImage image( doublePixmap.toImage().scaled( side / 2, side / 2, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
964 image = image.convertToFormat( QImage::Format_ARGB32 );
966 // draw circular shadow using the same technique
967 doublePixmap.fill( Qt::black );
968 pixmapPainter.begin( &doublePixmap );
969 pixmapPainter.setPen( 0x40 );
970 pixmapPainter.setBrush( QColor( 0x80 ) );
971 pixmapPainter.drawEllipse( 0, 0, side, side );
972 pixmapPainter.end();
973 QImage shadow( doublePixmap.toImage().scaled( side / 2, side / 2, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
975 // generate a 2 colors pixmap using mixing shadow (made with highlight color)
976 // and image (made with highlightedText color)
977 QPalette pal = palette();
978 QColor color = pal.color( QPalette::Active, QPalette::HighlightedText );
979 int red = color.red(), green = color.green(), blue = color.blue();
980 color = pal.color( QPalette::Active, QPalette::Highlight );
981 int sRed = color.red(), sGreen = color.green(), sBlue = color.blue();
982 // pointers
983 unsigned int * data = (unsigned int *)image.bits(),
984 * shadowData = (unsigned int *)shadow.bits(),
985 pixels = image.width() * image.height();
986 // cache data (reduce computation time to 26%!)
987 int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0;
988 // foreach pixel
989 for( unsigned int i = 0; i < pixels; ++i )
991 // alpha for shadow and image
992 int shadowAlpha = shadowData[i] & 0xFF,
993 srcAlpha = data[i] & 0xFF;
994 // cache values
995 if ( srcAlpha != c1 || shadowAlpha != c2 )
997 c1 = srcAlpha;
998 c2 = shadowAlpha;
999 // fuse color components and alpha value of image over shadow
1000 data[i] = qRgba(
1001 cR = qt_div255( srcAlpha * red + (255 - srcAlpha) * sRed ),
1002 cG = qt_div255( srcAlpha * green + (255 - srcAlpha) * sGreen ),
1003 cB = qt_div255( srcAlpha * blue + (255 - srcAlpha) * sBlue ),
1004 cA = qt_div255( srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha )
1007 else
1008 data[i] = qRgba( cR, cG, cB, cA );
1010 m_lastRenderedOverlay = QPixmap::fromImage( image );
1012 // start the autohide timer
1013 //repaint( m_overlayGeometry ); // toggle with next line
1014 update( m_overlayGeometry );
1015 m_overlayHideTimer->start( 2500 );
1016 #endif
1020 QRect PresentationWidget::routeMouseDrawingEvent( QMouseEvent * e )
1022 const QRect & geom = m_frames[ m_frameIndex ]->geometry;
1023 const Okular::Page * page = m_frames[ m_frameIndex ]->page;
1025 AnnotatorEngine::EventType eventType = AnnotatorEngine::Press;
1026 if ( e->type() == QEvent::MouseMove )
1027 eventType = AnnotatorEngine::Move;
1028 else if ( e->type() == QEvent::MouseButtonRelease )
1029 eventType = AnnotatorEngine::Release;
1031 // find out the pressed button
1032 AnnotatorEngine::Button button = AnnotatorEngine::None;
1033 Qt::MouseButtons buttonState = ( eventType == AnnotatorEngine::Move ) ? e->buttons() : e->button();
1034 if ( buttonState == Qt::LeftButton )
1035 button = AnnotatorEngine::Left;
1036 else if ( buttonState == Qt::RightButton )
1037 button = AnnotatorEngine::Right;
1038 static bool hasclicked = false;
1039 if ( eventType == AnnotatorEngine::Press )
1040 hasclicked = true;
1042 double nX = ( (double)e->x() - (double)geom.left() ) / (double)geom.width();
1043 double nY = ( (double)e->y() - (double)geom.top() ) / (double)geom.height();
1044 QRect ret;
1045 if ( hasclicked && nX >= 0 && nX < 1 && nY >= 0 && nY < 1 )
1046 ret = m_drawingEngine->event( eventType, button, nX, nY, geom.width(), geom.height(), page );
1048 if ( eventType == AnnotatorEngine::Release )
1049 hasclicked = false;
1051 return ret;
1054 void PresentationWidget::startAutoChangeTimer()
1056 double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[ m_frameIndex ]->page->duration() : -1;
1057 if ( Okular::Settings::slidesAdvance() || pageDuration >= 0.0 )
1059 double secs = pageDuration < 0.0
1060 ? Okular::Settings::slidesAdvanceTime()
1061 : qMin<double>( pageDuration, Okular::Settings::slidesAdvanceTime() );
1062 m_nextPageTimer->start( (int)( secs * 1000 ) );
1066 void PresentationWidget::recalcGeometry()
1068 QDesktopWidget *desktop = QApplication::desktop();
1069 const int preferenceScreen = Okular::Settings::slidesScreen();
1070 int screen = 0;
1071 if ( preferenceScreen == -2 )
1073 screen = desktop->screenNumber( m_parentWidget );
1075 else if ( preferenceScreen == -1 )
1077 screen = desktop->primaryScreen();
1079 else if ( preferenceScreen >= 0 && preferenceScreen < desktop->numScreens() )
1081 screen = preferenceScreen;
1083 else
1085 screen = desktop->screenNumber( m_parentWidget );
1086 Okular::Settings::setSlidesScreen( -2 );
1088 const QRect screenGeom = desktop->screenGeometry( screen );
1089 // kDebug() << screen << "=>" << screenGeom;
1090 m_screen = screen;
1091 setGeometry( screenGeom );
1094 void PresentationWidget::repositionContent()
1096 const QRect ourGeom = geometry();
1098 m_topBar->setGeometry( 0, 0, ourGeom.width(), 32 + 10 );
1101 void PresentationWidget::requestPixmaps()
1103 PresentationFrame * frame = m_frames[ m_frameIndex ];
1104 int pixW = frame->geometry.width();
1105 int pixH = frame->geometry.height();
1107 // operation will take long: set busy cursor
1108 QApplication::setOverrideCursor( QCursor( Qt::BusyCursor ) );
1109 // request the pixmap
1110 QLinkedList< Okular::PixmapRequest * > requests;
1111 requests.push_back( new Okular::PixmapRequest( PRESENTATION_ID, m_frameIndex, pixW, pixH, PRESENTATION_PRIO, false ) );
1112 // restore cursor
1113 QApplication::restoreOverrideCursor();
1114 // ask for next and previous page if not in low memory usage setting
1115 if ( Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low && Okular::Settings::enableThreading() )
1117 if ( m_frameIndex + 1 < (int)m_document->pages() )
1119 PresentationFrame *nextFrame = m_frames[ m_frameIndex + 1 ];
1120 pixW = nextFrame->geometry.width();
1121 pixH = nextFrame->geometry.height();
1122 if ( !nextFrame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) )
1123 requests.push_back( new Okular::PixmapRequest( PRESENTATION_ID, m_frameIndex + 1, pixW, pixH, PRESENTATION_PRELOAD_PRIO, true ) );
1125 if ( m_frameIndex - 1 >= 0 )
1127 PresentationFrame *prevFrame = m_frames[ m_frameIndex - 1 ];
1128 pixW = prevFrame->geometry.width();
1129 pixH = prevFrame->geometry.height();
1130 if ( !prevFrame->page->hasPixmap( PRESENTATION_ID, pixW, pixH ) )
1131 requests.push_back( new Okular::PixmapRequest( PRESENTATION_ID, m_frameIndex - 1, pixW, pixH, PRESENTATION_PRELOAD_PRIO, true ) );
1134 m_document->requestPixmaps( requests );
1138 void PresentationWidget::slotNextPage()
1140 // loop when configured
1141 if ( m_frameIndex == (int)m_frames.count() - 1 && Okular::Settings::slidesLoop() )
1142 m_frameIndex = -1;
1144 if ( m_frameIndex < (int)m_frames.count() - 1 )
1146 // go to next page
1147 changePage( m_frameIndex + 1 );
1148 // auto advance to the next page if set
1149 startAutoChangeTimer();
1151 else
1153 #ifdef ENABLE_PROGRESS_OVERLAY
1154 if ( Okular::Settings::slidesShowProgress() )
1155 generateOverlay();
1156 #endif
1157 if ( m_transitionTimer->isActive() )
1159 m_transitionTimer->stop();
1160 update();
1163 // we need the setFocus() call here to let KCursor::autoHide() work correctly
1164 setFocus();
1167 void PresentationWidget::slotPrevPage()
1169 if ( m_frameIndex > 0 )
1171 // go to previous page
1172 changePage( m_frameIndex - 1 );
1174 // auto advance to the next page if set
1175 startAutoChangeTimer();
1177 else
1179 #ifdef ENABLE_PROGRESS_OVERLAY
1180 if ( Okular::Settings::slidesShowProgress() )
1181 generateOverlay();
1182 #endif
1183 if ( m_transitionTimer->isActive() )
1185 m_transitionTimer->stop();
1186 update();
1191 void PresentationWidget::slotFirstPage()
1193 changePage( 0 );
1196 void PresentationWidget::slotLastPage()
1198 changePage( (int)m_frames.count() - 1 );
1201 void PresentationWidget::slotHideOverlay()
1203 QRect geom( m_overlayGeometry );
1204 m_overlayGeometry.setCoords( 0, 0, -1, -1 );
1205 update( geom );
1208 void PresentationWidget::slotTransitionStep()
1210 if ( m_transitionRects.empty() )
1212 // it's better to fix the transition to cover the whole screen than
1213 // enabling the following line that wastes cpu for nothing
1214 //update();
1215 return;
1218 for ( int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++ )
1220 update( m_transitionRects.first() );
1221 m_transitionRects.pop_front();
1223 m_transitionTimer->start( m_transitionDelay );
1226 void PresentationWidget::slotDelayedEvents()
1228 recalcGeometry();
1229 repositionContent();
1231 if ( m_screenSelect )
1233 m_screenSelect->setCurrentItem( m_screen );
1234 connect( m_screenSelect->selectableActionGroup(), SIGNAL( triggered( QAction * ) ),
1235 this, SLOT( chooseScreen( QAction * ) ) );
1238 // show widget and take control
1239 show();
1240 setWindowState( windowState() | Qt::WindowFullScreen );
1242 connect( QApplication::desktop(), SIGNAL( resized( int ) ), this, SLOT( screenResized( int ) ) );
1244 // inform user on how to exit from presentation mode
1245 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(), "presentationInfo" );
1248 void PresentationWidget::slotPageChanged()
1250 bool ok = true;
1251 int p = m_pagesEdit->text().toInt( &ok );
1252 if ( !ok )
1253 return;
1255 changePage( p - 1 );
1258 void PresentationWidget::togglePencilMode( bool on )
1260 if ( on )
1262 QString colorstring = Okular::Settings::slidesPencilColor().name();
1263 // FIXME this should not be recreated every time
1264 QDomDocument doc( "engine" );
1265 QDomElement root = doc.createElement( "engine" );
1266 root.setAttribute( "color", colorstring );
1267 doc.appendChild( root );
1268 QDomElement annElem = doc.createElement( "annotation" );
1269 root.appendChild( annElem );
1270 annElem.setAttribute( "type", "Ink" );
1271 annElem.setAttribute( "color", colorstring );
1272 annElem.setAttribute( "width", "2" );
1273 m_drawingEngine = new SmoothPathEngine( root );
1274 setCursor( KCursor( "pencil", Qt::ArrowCursor ) );
1276 else
1278 delete m_drawingEngine;
1279 m_drawingEngine = 0;
1280 m_drawingRect = QRect();
1281 setCursor( Qt::ArrowCursor );
1285 void PresentationWidget::clearDrawings()
1287 m_document->removePageAnnotations( m_document->viewport().pageNumber, m_currentPageDrawings );
1288 m_currentPageDrawings.clear();
1291 void PresentationWidget::screenResized( int screen )
1293 // we can ignore if a screen was resized in the case the screen is not
1294 // where we are on
1295 if ( screen != m_screen )
1296 return;
1298 setScreen( screen );
1301 void PresentationWidget::chooseScreen( QAction *act )
1303 if ( !act || act->data().type() != QVariant::Int )
1304 return;
1306 const int newScreen = act->data().toInt();
1308 setScreen( newScreen );
1311 void PresentationWidget::toggleBlackScreenMode( bool )
1313 m_inBlackScreenMode = !m_inBlackScreenMode;
1315 update();
1318 void PresentationWidget::setScreen( int newScreen )
1320 const QRect screenGeom = QApplication::desktop()->screenGeometry( newScreen );
1321 const QSize oldSize = size();
1322 // kDebug() << newScreen << "=>" << screenGeom;
1323 m_screen = newScreen;
1324 setGeometry( screenGeom );
1326 applyNewScreenSize( oldSize );
1329 void PresentationWidget::applyNewScreenSize( const QSize & oldSize )
1331 repositionContent();
1333 // if by chance the new screen has the same resolution of the previous,
1334 // do not invalidate pixmaps and such..
1335 if ( size() == oldSize )
1336 return;
1338 m_width = width();
1339 m_height = height();
1341 // update the frames
1342 QVector< PresentationFrame * >::const_iterator fIt = m_frames.constBegin(), fEnd = m_frames.constEnd();
1343 const float screenRatio = (float)m_height / (float)m_width;
1344 for ( ; fIt != fEnd; ++fIt )
1346 (*fIt)->recalcGeometry( m_width, m_height, screenRatio );
1349 if ( m_frameIndex != -1 )
1351 // ugliness alarm!
1352 const_cast< Okular::Page * >( m_frames[ m_frameIndex ]->page )->deletePixmap( PRESENTATION_ID );
1353 // force the regeneration of the pixmap
1354 m_lastRenderedPixmap = QPixmap();
1355 m_blockNotifications = true;
1356 requestPixmaps();
1357 m_blockNotifications = false;
1359 generatePage( true /* no transitions */ );
1362 void PresentationWidget::inhibitScreenSaver()
1364 QDBusMessage message = QDBusMessage::createMethodCall( "org.freedesktop.ScreenSaver", "/ScreenSaver",
1365 "org.freedesktop.ScreenSaver", "Inhibit" );
1366 message << QString( "Okular" );
1367 message << i18nc( "Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation" );
1369 QDBusReply<uint> reply = QDBusConnection::sessionBus().call( message );
1370 if ( reply.isValid() )
1371 m_screenSaverCookie = reply.value();
1374 void PresentationWidget::allowScreenSaver()
1376 if ( m_screenSaverCookie != -1 )
1378 QDBusMessage message = QDBusMessage::createMethodCall( "org.freedesktop.ScreenSaver", "/ScreenSaver",
1379 "org.freedesktop.ScreenSaver", "UnInhibit" );
1380 message << (uint)m_screenSaverCookie;
1381 QDBusConnection::sessionBus().send( message );
1385 void PresentationWidget::slotFind()
1387 if ( !m_searchBar )
1389 m_searchBar = new PresentationSearchBar( m_document, this, this );
1390 m_searchBar->forceSnap();
1392 m_searchBar->focusOnSearchEdit();
1393 m_searchBar->show();
1397 const Okular::PageTransition PresentationWidget::defaultTransition() const
1399 return defaultTransition( Okular::Settings::slidesTransition() );
1402 const Okular::PageTransition PresentationWidget::defaultTransition( int type ) const
1404 switch ( type )
1406 case Okular::Settings::EnumSlidesTransition::BlindsHorizontal:
1408 Okular::PageTransition transition( Okular::PageTransition::Blinds );
1409 transition.setAlignment( Okular::PageTransition::Horizontal );
1410 return transition;
1411 break;
1413 case Okular::Settings::EnumSlidesTransition::BlindsVertical:
1415 Okular::PageTransition transition( Okular::PageTransition::Blinds );
1416 transition.setAlignment( Okular::PageTransition::Vertical );
1417 return transition;
1418 break;
1420 case Okular::Settings::EnumSlidesTransition::BoxIn:
1422 Okular::PageTransition transition( Okular::PageTransition::Box );
1423 transition.setDirection( Okular::PageTransition::Inward );
1424 return transition;
1425 break;
1427 case Okular::Settings::EnumSlidesTransition::BoxOut:
1429 Okular::PageTransition transition( Okular::PageTransition::Box );
1430 transition.setDirection( Okular::PageTransition::Outward );
1431 return transition;
1432 break;
1434 case Okular::Settings::EnumSlidesTransition::Dissolve:
1436 return Okular::PageTransition( Okular::PageTransition::Dissolve );
1437 break;
1439 case Okular::Settings::EnumSlidesTransition::GlitterDown:
1441 Okular::PageTransition transition( Okular::PageTransition::Glitter );
1442 transition.setAngle( 270 );
1443 return transition;
1444 break;
1446 case Okular::Settings::EnumSlidesTransition::GlitterRight:
1448 Okular::PageTransition transition( Okular::PageTransition::Glitter );
1449 transition.setAngle( 0 );
1450 return transition;
1451 break;
1453 case Okular::Settings::EnumSlidesTransition::GlitterRightDown:
1455 Okular::PageTransition transition( Okular::PageTransition::Glitter );
1456 transition.setAngle( 315 );
1457 return transition;
1458 break;
1460 case Okular::Settings::EnumSlidesTransition::Random:
1462 return defaultTransition( KRandom::random() % 18 );
1463 break;
1465 case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn:
1467 Okular::PageTransition transition( Okular::PageTransition::Split );
1468 transition.setAlignment( Okular::PageTransition::Horizontal );
1469 transition.setDirection( Okular::PageTransition::Inward );
1470 return transition;
1471 break;
1473 case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut:
1475 Okular::PageTransition transition( Okular::PageTransition::Split );
1476 transition.setAlignment( Okular::PageTransition::Horizontal );
1477 transition.setDirection( Okular::PageTransition::Outward );
1478 return transition;
1479 break;
1481 case Okular::Settings::EnumSlidesTransition::SplitVerticalIn:
1483 Okular::PageTransition transition( Okular::PageTransition::Split );
1484 transition.setAlignment( Okular::PageTransition::Vertical );
1485 transition.setDirection( Okular::PageTransition::Inward );
1486 return transition;
1487 break;
1489 case Okular::Settings::EnumSlidesTransition::SplitVerticalOut:
1491 Okular::PageTransition transition( Okular::PageTransition::Split );
1492 transition.setAlignment( Okular::PageTransition::Vertical );
1493 transition.setDirection( Okular::PageTransition::Outward );
1494 return transition;
1495 break;
1497 case Okular::Settings::EnumSlidesTransition::WipeDown:
1499 Okular::PageTransition transition( Okular::PageTransition::Wipe );
1500 transition.setAngle( 270 );
1501 return transition;
1502 break;
1504 case Okular::Settings::EnumSlidesTransition::WipeRight:
1506 Okular::PageTransition transition( Okular::PageTransition::Wipe );
1507 transition.setAngle( 0 );
1508 return transition;
1509 break;
1511 case Okular::Settings::EnumSlidesTransition::WipeLeft:
1513 Okular::PageTransition transition( Okular::PageTransition::Wipe );
1514 transition.setAngle( 180 );
1515 return transition;
1516 break;
1518 case Okular::Settings::EnumSlidesTransition::WipeUp:
1520 Okular::PageTransition transition( Okular::PageTransition::Wipe );
1521 transition.setAngle( 90 );
1522 return transition;
1523 break;
1525 case Okular::Settings::EnumSlidesTransition::Replace:
1526 default:
1527 return Okular::PageTransition( Okular::PageTransition::Replace );
1528 break;
1530 // should not happen, just make gcc happy
1531 return Okular::PageTransition();
1534 /** ONLY the TRANSITIONS GENERATION function from here on **/
1535 void PresentationWidget::initTransition( const Okular::PageTransition *transition )
1537 // if it's just a 'replace' transition, repaint the screen
1538 if ( transition->type() == Okular::PageTransition::Replace )
1540 update();
1541 return;
1544 const bool isInward = transition->direction() == Okular::PageTransition::Inward;
1545 const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal;
1546 const float totalTime = transition->duration();
1548 m_transitionRects.clear();
1550 switch( transition->type() )
1552 // split: horizontal / vertical and inward / outward
1553 case Okular::PageTransition::Split:
1555 const int steps = isHorizontal ? 100 : 75;
1556 if ( isHorizontal )
1558 if ( isInward )
1560 int xPosition = 0;
1561 for ( int i = 0; i < steps; i++ )
1563 int xNext = ((i + 1) * m_width) / (2 * steps);
1564 m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) );
1565 m_transitionRects.push_back( QRect( m_width - xNext, 0, xNext - xPosition, m_height ) );
1566 xPosition = xNext;
1569 else
1571 int xPosition = m_width / 2;
1572 for ( int i = 0; i < steps; i++ )
1574 int xNext = ((steps - (i + 1)) * m_width) / (2 * steps);
1575 m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) );
1576 m_transitionRects.push_back( QRect( m_width - xPosition, 0, xPosition - xNext, m_height ) );
1577 xPosition = xNext;
1581 else
1583 if ( isInward )
1585 int yPosition = 0;
1586 for ( int i = 0; i < steps; i++ )
1588 int yNext = ((i + 1) * m_height) / (2 * steps);
1589 m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) );
1590 m_transitionRects.push_back( QRect( 0, m_height - yNext, m_width, yNext - yPosition ) );
1591 yPosition = yNext;
1594 else
1596 int yPosition = m_height / 2;
1597 for ( int i = 0; i < steps; i++ )
1599 int yNext = ((steps - (i + 1)) * m_height) / (2 * steps);
1600 m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) );
1601 m_transitionRects.push_back( QRect( 0, m_height - yPosition, m_width, yPosition - yNext ) );
1602 yPosition = yNext;
1606 m_transitionMul = 2;
1607 m_transitionDelay = (int)( (totalTime * 1000) / steps );
1608 } break;
1610 // blinds: horizontal(l-to-r) / vertical(t-to-b)
1611 case Okular::PageTransition::Blinds:
1613 const int blinds = isHorizontal ? 8 : 6;
1614 const int steps = m_width / (4 * blinds);
1615 if ( isHorizontal )
1617 int xPosition[ 8 ];
1618 for ( int b = 0; b < blinds; b++ )
1619 xPosition[ b ] = (b * m_width) / blinds;
1621 for ( int i = 0; i < steps; i++ )
1623 int stepOffset = (int)( ((float)i * (float)m_width) / ((float)blinds * (float)steps) );
1624 for ( int b = 0; b < blinds; b++ )
1626 m_transitionRects.push_back( QRect( xPosition[ b ], 0, stepOffset, m_height ) );
1627 xPosition[ b ] = stepOffset + (b * m_width) / blinds;
1631 else
1633 int yPosition[ 6 ];
1634 for ( int b = 0; b < blinds; b++ )
1635 yPosition[ b ] = (b * m_height) / blinds;
1637 for ( int i = 0; i < steps; i++ )
1639 int stepOffset = (int)( ((float)i * (float)m_height) / ((float)blinds * (float)steps) );
1640 for ( int b = 0; b < blinds; b++ )
1642 m_transitionRects.push_back( QRect( 0, yPosition[ b ], m_width, stepOffset ) );
1643 yPosition[ b ] = stepOffset + (b * m_height) / blinds;
1647 m_transitionMul = blinds;
1648 m_transitionDelay = (int)( (totalTime * 1000) / steps );
1649 } break;
1651 // box: inward / outward
1652 case Okular::PageTransition::Box:
1654 const int steps = m_width / 10;
1655 if ( isInward )
1657 int L = 0, T = 0, R = m_width, B = m_height;
1658 for ( int i = 0; i < steps; i++ )
1660 // compure shrinked box coords
1661 int newL = ((i + 1) * m_width) / (2 * steps);
1662 int newT = ((i + 1) * m_height) / (2 * steps);
1663 int newR = m_width - newL;
1664 int newB = m_height - newT;
1665 // add left, right, topcenter, bottomcenter rects
1666 m_transitionRects.push_back( QRect( L, T, newL - L, B - T ) );
1667 m_transitionRects.push_back( QRect( newR, T, R - newR, B - T ) );
1668 m_transitionRects.push_back( QRect( newL, T, newR - newL, newT - T ) );
1669 m_transitionRects.push_back( QRect( newL, newB, newR - newL, B - newB ) );
1670 L = newL; T = newT; R = newR, B = newB;
1673 else
1675 int L = m_width / 2, T = m_height / 2, R = L, B = T;
1676 for ( int i = 0; i < steps; i++ )
1678 // compure shrinked box coords
1679 int newL = ((steps - (i + 1)) * m_width) / (2 * steps);
1680 int newT = ((steps - (i + 1)) * m_height) / (2 * steps);
1681 int newR = m_width - newL;
1682 int newB = m_height - newT;
1683 // add left, right, topcenter, bottomcenter rects
1684 m_transitionRects.push_back( QRect( newL, newT, L - newL, newB - newT ) );
1685 m_transitionRects.push_back( QRect( R, newT, newR - R, newB - newT ) );
1686 m_transitionRects.push_back( QRect( L, newT, R - L, T - newT ) );
1687 m_transitionRects.push_back( QRect( L, B, R - L, newB - B ) );
1688 L = newL; T = newT; R = newR, B = newB;
1691 m_transitionMul = 4;
1692 m_transitionDelay = (int)( (totalTime * 1000) / steps );
1693 } break;
1695 // wipe: implemented for 4 canonical angles
1696 case Okular::PageTransition::Wipe:
1698 const int angle = transition->angle();
1699 const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8;
1700 if ( angle == 0 )
1702 int xPosition = 0;
1703 for ( int i = 0; i < steps; i++ )
1705 int xNext = ((i + 1) * m_width) / steps;
1706 m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) );
1707 xPosition = xNext;
1710 else if ( angle == 90 )
1712 int yPosition = m_height;
1713 for ( int i = 0; i < steps; i++ )
1715 int yNext = ((steps - (i + 1)) * m_height) / steps;
1716 m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) );
1717 yPosition = yNext;
1720 else if ( angle == 180 )
1722 int xPosition = m_width;
1723 for ( int i = 0; i < steps; i++ )
1725 int xNext = ((steps - (i + 1)) * m_width) / steps;
1726 m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) );
1727 xPosition = xNext;
1730 else if ( angle == 270 )
1732 int yPosition = 0;
1733 for ( int i = 0; i < steps; i++ )
1735 int yNext = ((i + 1) * m_height) / steps;
1736 m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) );
1737 yPosition = yNext;
1740 else
1742 update();
1743 return;
1745 m_transitionMul = 1;
1746 m_transitionDelay = (int)( (totalTime * 1000) / steps );
1747 } break;
1749 // dissolve: replace 'random' rects
1750 case Okular::PageTransition::Dissolve:
1752 const int gridXsteps = 50;
1753 const int gridYsteps = 38;
1754 const int steps = gridXsteps * gridYsteps;
1755 int oldX = 0;
1756 int oldY = 0;
1757 // create a grid of gridXstep by gridYstep QRects
1758 for ( int y = 0; y < gridYsteps; y++ )
1760 int newY = (int)( m_height * ((float)(y+1) / (float)gridYsteps) );
1761 for ( int x = 0; x < gridXsteps; x++ )
1763 int newX = (int)( m_width * ((float)(x+1) / (float)gridXsteps) );
1764 m_transitionRects.push_back( QRect( oldX, oldY, newX - oldX, newY - oldY ) );
1765 oldX = newX;
1767 oldX = 0;
1768 oldY = newY;
1770 // randomize the grid
1771 for ( int i = 0; i < steps; i++ )
1773 int n1 = (int)(steps * drand48());
1774 int n2 = (int)(steps * drand48());
1775 // swap items if index differs
1776 if ( n1 != n2 )
1778 QRect r = m_transitionRects[ n2 ];
1779 m_transitionRects[ n2 ] = m_transitionRects[ n1 ];
1780 m_transitionRects[ n1 ] = r;
1783 // set global transition parameters
1784 m_transitionMul = 40;
1785 m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps );
1786 } break;
1788 // glitter: similar to dissolve but has a direction
1789 case Okular::PageTransition::Glitter:
1791 const int gridXsteps = 50;
1792 const int gridYsteps = 38;
1793 const int steps = gridXsteps * gridYsteps;
1794 const int angle = transition->angle();
1795 // generate boxes using a given direction
1796 if ( angle == 90 )
1798 int yPosition = m_height;
1799 for ( int i = 0; i < gridYsteps; i++ )
1801 int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps;
1802 int xPosition = 0;
1803 for ( int j = 0; j < gridXsteps; j++ )
1805 int xNext = ((j + 1) * m_width) / gridXsteps;
1806 m_transitionRects.push_back( QRect( xPosition, yNext, xNext - xPosition, yPosition - yNext ) );
1807 xPosition = xNext;
1809 yPosition = yNext;
1812 else if ( angle == 180 )
1814 int xPosition = m_width;
1815 for ( int i = 0; i < gridXsteps; i++ )
1817 int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps;
1818 int yPosition = 0;
1819 for ( int j = 0; j < gridYsteps; j++ )
1821 int yNext = ((j + 1) * m_height) / gridYsteps;
1822 m_transitionRects.push_back( QRect( xNext, yPosition, xPosition - xNext, yNext - yPosition ) );
1823 yPosition = yNext;
1825 xPosition = xNext;
1828 else if ( angle == 270 )
1830 int yPosition = 0;
1831 for ( int i = 0; i < gridYsteps; i++ )
1833 int yNext = ((i + 1) * m_height) / gridYsteps;
1834 int xPosition = 0;
1835 for ( int j = 0; j < gridXsteps; j++ )
1837 int xNext = ((j + 1) * m_width) / gridXsteps;
1838 m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) );
1839 xPosition = xNext;
1841 yPosition = yNext;
1844 else // if angle is 0 or 315
1846 int xPosition = 0;
1847 for ( int i = 0; i < gridXsteps; i++ )
1849 int xNext = ((i + 1) * m_width) / gridXsteps;
1850 int yPosition = 0;
1851 for ( int j = 0; j < gridYsteps; j++ )
1853 int yNext = ((j + 1) * m_height) / gridYsteps;
1854 m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) );
1855 yPosition = yNext;
1857 xPosition = xNext;
1860 // add a 'glitter' (1 over 10 pieces is randomized)
1861 int randomSteps = steps / 20;
1862 for ( int i = 0; i < randomSteps; i++ )
1864 int n1 = (int)(steps * drand48());
1865 int n2 = (int)(steps * drand48());
1866 // swap items if index differs
1867 if ( n1 != n2 )
1869 QRect r = m_transitionRects[ n2 ];
1870 m_transitionRects[ n2 ] = m_transitionRects[ n1 ];
1871 m_transitionRects[ n1 ] = r;
1874 // set global transition parameters
1875 m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps;
1876 m_transitionMul /= 2;
1877 m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps );
1878 } break;
1880 // implement missing transitions (a binary raster engine needed here)
1881 case Okular::PageTransition::Fly:
1883 case Okular::PageTransition::Push:
1885 case Okular::PageTransition::Cover:
1887 case Okular::PageTransition::Uncover:
1889 case Okular::PageTransition::Fade:
1891 default:
1892 update();
1893 return;
1896 // send the first start to the timer
1897 m_transitionTimer->start( 0 );
1901 #include "presentationwidget.moc"