diff --git a/conf/dlgperformance.cpp b/conf/dlgperformance.cpp index b86af6f8f..1e1cc56ef 100644 --- a/conf/dlgperformance.cpp +++ b/conf/dlgperformance.cpp @@ -1,70 +1,69 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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. * ***************************************************************************/ #include "dlgperformance.h" #include #include #include -#include #include "settings_core.h" #include "ui_dlgperformancebase.h" DlgPerformance::DlgPerformance( QWidget * parent ) : QWidget( parent ) { Q_PROPERTY( QButtonGroup checkedId READ checkedId USER true ); m_dlg = new Ui_DlgPerformanceBase(); m_dlg->setupUi( this ); QFont labelFont = m_dlg->descLabel->font(); labelFont.setBold( true ); m_dlg->descLabel->setFont( labelFont ); - m_dlg->cpuLabel->setPixmap( BarIcon( QStringLiteral("cpu"), 32 ) ); -// m_dlg->memoryLabel->setPixmap( BarIcon( "kcmmemory", 32 ) ); // TODO: enable again when proper icon is available + m_dlg->cpuLabel->setPixmap( QIcon::fromTheme( QStringLiteral("cpu") ).pixmap( 32 ) ); +// m_dlg->memoryLabel->setPixmap( QIcon::fromTheme( "kcmmemory" ).pixmap( 32 ) ); // TODO: enable again when proper icon is available m_dlg->memoryLevelGroup->setId(m_dlg->lowRadio, 0); m_dlg->memoryLevelGroup->setId(m_dlg->normalRadio, 1); m_dlg->memoryLevelGroup->setId(m_dlg->aggressiveRadio, 2); m_dlg->memoryLevelGroup->setId(m_dlg->greedyRadio, 3); connect(m_dlg->memoryLevelGroup, static_cast(&QButtonGroup::buttonClicked), this, &DlgPerformance::radioGroup_changed); } DlgPerformance::~DlgPerformance() { delete m_dlg; } void DlgPerformance::radioGroup_changed( int which ) { switch ( which ) { case 0: m_dlg->descLabel->setText( i18n("Keeps used memory as low as possible. Do not reuse anything. (For systems with low memory.)") ); break; case 1: m_dlg->descLabel->setText( i18n("A good compromise between memory usage and speed gain. Preload next page and boost searches. (For systems with 2GB of memory, typically.)") ); break; case 2: m_dlg->descLabel->setText( i18n("Keeps everything in memory. Preload next pages. Boost searches. (For systems with more than 4GB of memory.)") ); break; case 3: // xgettext: no-c-format m_dlg->descLabel->setText( i18n("Loads and keeps everything in memory. Preload all pages. (Will use at maximum 50% of your total memory or your free memory, whatever is bigger.)")); break; } } #include "moc_dlgperformance.cpp" diff --git a/ui/ktreeviewsearchline.cpp b/ui/ktreeviewsearchline.cpp index 2466adbec..e1978dd33 100644 --- a/ui/ktreeviewsearchline.cpp +++ b/ui/ktreeviewsearchline.cpp @@ -1,399 +1,398 @@ /* Copyright (c) 2003 Scott Wheeler Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2006 Hamish Rodda Copyright 2007 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktreeviewsearchline.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include class KTreeViewSearchLine::Private { public: Private( KTreeViewSearchLine *_parent ) : parent( _parent ), treeView( nullptr ), caseSensitive( Qt::CaseInsensitive ), regularExpression( false ), activeSearch( false ), queuedSearches( 0 ) { } KTreeViewSearchLine *parent; QTreeView * treeView; Qt::CaseSensitivity caseSensitive; bool regularExpression; bool activeSearch; QString search; int queuedSearches; void rowsInserted(const QModelIndex & parent, int start, int end) const; void treeViewDeleted( QObject *treeView ); void slotCaseSensitive(); void slotRegularExpression(); void checkItemParentsNotVisible(QTreeView *treeView); bool checkItemParentsVisible(QTreeView *treeView, const QModelIndex &index); }; //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::Private::rowsInserted( const QModelIndex & parentIndex, int start, int end ) const { QAbstractItemModel* model = qobject_cast( parent->sender() ); if ( !model ) return; QTreeView* widget = nullptr; if ( treeView->model() == model ) { widget = treeView; } if ( !widget ) return; for ( int i = start; i <= end; ++i ) { widget->setRowHidden( i, parentIndex, !parent->itemMatches( parentIndex, i, parent->text() ) ); } } void KTreeViewSearchLine::Private::treeViewDeleted( QObject *object ) { if ( object == treeView ) { treeView = nullptr; parent->setEnabled( false ); } } void KTreeViewSearchLine::Private::slotCaseSensitive() { if ( caseSensitive == Qt::CaseSensitive) parent->setCaseSensitivity( Qt::CaseInsensitive ); else parent->setCaseSensitivity( Qt::CaseSensitive ); parent->updateSearch(); } void KTreeViewSearchLine::Private::slotRegularExpression() { if ( regularExpression ) parent->setRegularExpression( false ); else parent->setRegularExpression( true ); parent->updateSearch(); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// /** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary. * * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the * the first child of the list view. * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. */ bool KTreeViewSearchLine::Private::checkItemParentsVisible( QTreeView *treeView, const QModelIndex &index ) { bool childMatch = false; const int rowcount = treeView->model()->rowCount( index ); for ( int i = 0; i < rowcount; ++i ) childMatch |= checkItemParentsVisible( treeView, treeView->model()->index( i, 0, index ) ); // Should this item be shown? It should if any children should be, or if it matches. const QModelIndex parentindex = index.parent(); if ( childMatch || parent->itemMatches( parentindex, index.row(), search ) ) { treeView->setRowHidden( index.row(), parentindex, false ); return true; } treeView->setRowHidden( index.row(), parentindex, true ); return false; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// KTreeViewSearchLine::KTreeViewSearchLine( QWidget *parent, QTreeView *treeView ) : KLineEdit( parent ), d( new Private( this ) ) { connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch); setClearButtonEnabled( true ); setTreeView( treeView ); if ( !treeView ) { setEnabled( false ); } } KTreeViewSearchLine::~KTreeViewSearchLine() { delete d; } Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const { return d->caseSensitive; } bool KTreeViewSearchLine::regularExpression() const { return d->regularExpression; } QTreeView *KTreeViewSearchLine::treeView() const { return d->treeView; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::updateSearch( const QString &pattern ) { d->search = pattern.isNull() ? text() : pattern; updateSearch( d->treeView ); } void KTreeViewSearchLine::updateSearch( QTreeView *treeView ) { if ( !treeView || !treeView->model()->rowCount() ) return; // If there's a selected item that is visible, make sure that it's visible // when the search changes too (assuming that it still matches). QModelIndex currentIndex = treeView->currentIndex(); bool wasUpdateEnabled = treeView->updatesEnabled(); treeView->setUpdatesEnabled( false ); for ( int i = 0; i < treeView->model()->rowCount(); ++i ) d->checkItemParentsVisible( treeView, treeView->rootIndex() ); treeView->setUpdatesEnabled( wasUpdateEnabled ); if ( currentIndex.isValid() ) treeView->scrollTo( currentIndex ); } void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive ) { if ( d->caseSensitive != caseSensitive ) { d->caseSensitive = caseSensitive; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setRegularExpression( bool value ) { if ( d->regularExpression != value ) { d->regularExpression = value; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setTreeView( QTreeView *treeView ) { disconnectTreeView( d->treeView ); d->treeView = treeView; connectTreeView( treeView ); setEnabled( treeView != nullptr ); } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// bool KTreeViewSearchLine::itemMatches( const QModelIndex &parentIndex, int row, const QString &pattern ) const { if ( pattern.isEmpty() ) return true; if ( !parentIndex.isValid() && parentIndex != d->treeView->rootIndex() ) return false; // Construct a regular expression object with the right options. QRegExp expression = QRegExp( pattern, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, d->regularExpression ? QRegExp::RegExp : QRegExp::FixedString ); // If the search column list is populated, search just the columns // specified. If it is empty default to searching all of the columns. QAbstractItemModel *model = d->treeView->model(); const int columncount = model->columnCount( parentIndex ); for ( int i = 0; i < columncount; ++i) { if ( expression.indexIn( model->data( model->index( row, i, parentIndex ), Qt::DisplayRole ).toString() ) >= 0 ) return true; } return false; } void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent *event ) { QMenu *popup = KLineEdit::createStandardContextMenu(); popup->addSeparator(); QMenu *optionsSubMenu = popup->addMenu( i18n("Search Options") ); QAction* caseSensitiveAction = optionsSubMenu->addAction( i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, SLOT(slotCaseSensitive()) ); caseSensitiveAction->setCheckable( true ); caseSensitiveAction->setChecked( d->caseSensitive ); QAction* regularExpressionAction = optionsSubMenu->addAction( i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, SLOT(slotRegularExpression()) ); regularExpressionAction->setCheckable( true ); regularExpressionAction->setChecked( d->regularExpression ); popup->exec( event->globalPos() ); delete popup; } void KTreeViewSearchLine::connectTreeView( QTreeView *treeView ) { if ( treeView ) { connect( treeView, SIGNAL(destroyed(QObject*)), this, SLOT(treeViewDeleted(QObject*)) ); connect( treeView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); } } void KTreeViewSearchLine::disconnectTreeView( QTreeView *treeView ) { if ( treeView ) { disconnect( treeView, SIGNAL(destroyed(QObject*)), this, SLOT(treeViewDeleted(QObject*)) ); disconnect( treeView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); } } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::queueSearch( const QString &search ) { d->queuedSearches++; d->search = search; QTimer::singleShot( 200, this, &KTreeViewSearchLine::activateSearch ); } void KTreeViewSearchLine::activateSearch() { --(d->queuedSearches); if ( d->queuedSearches == 0 ) updateSearch( d->search ); } //////////////////////////////////////////////////////////////////////////////// // KTreeViewSearchLineWidget //////////////////////////////////////////////////////////////////////////////// class KTreeViewSearchLineWidget::Private { public: Private() : treeView( nullptr ), searchLine( nullptr ) { } QTreeView *treeView; KTreeViewSearchLine *searchLine; }; KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget *parent, QTreeView *treeView ) : QWidget( parent ), d( new Private ) { d->treeView = treeView; QTimer::singleShot( 0, this, &KTreeViewSearchLineWidget::createWidgets ); } KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() { delete d; } KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine( QTreeView *treeView ) const { return new KTreeViewSearchLine( const_cast(this), treeView ); } void KTreeViewSearchLineWidget::createWidgets() { QLabel *label = new QLabel( i18n("S&earch:"), this ); label->setObjectName( QStringLiteral("kde toolbar widget") ); searchLine()->show(); label->setBuddy( d->searchLine ); label->show(); QHBoxLayout* layout = new QHBoxLayout( this ); layout->setSpacing( 5 ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->addWidget( label ); layout->addWidget( d->searchLine ); } KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const { if ( !d->searchLine ) d->searchLine = createSearchLine( d->treeView ); return d->searchLine; } #include "moc_ktreeviewsearchline.cpp" diff --git a/ui/pageviewutils.cpp b/ui/pageviewutils.cpp index d88a37248..7ca2e019c 100644 --- a/ui/pageviewutils.cpp +++ b/ui/pageviewutils.cpp @@ -1,948 +1,948 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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. * ***************************************************************************/ #include "pageviewutils.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include // system includes #include // local includes #include "formwidgets.h" #include "pageview.h" #include "videowidget.h" #include "core/movie.h" #include "core/page.h" #include "core/form.h" #include "settings.h" /*********************/ /** PageViewItem */ /*********************/ PageViewItem::PageViewItem( const Okular::Page * page ) : m_page( page ), m_zoomFactor( 1.0 ), m_visible( true ), m_formsVisible( false ), m_crop( 0., 0., 1., 1. ) { } PageViewItem::~PageViewItem() { qDeleteAll( m_formWidgets ); qDeleteAll( m_videoWidgets ); } const Okular::Page * PageViewItem::page() const { return m_page; } int PageViewItem::pageNumber() const { return m_page->number(); } const QRect& PageViewItem::croppedGeometry() const { return m_croppedGeometry; } int PageViewItem::croppedWidth() const { return m_croppedGeometry.width(); } int PageViewItem::croppedHeight() const { return m_croppedGeometry.height(); } const QRect& PageViewItem::uncroppedGeometry() const { return m_uncroppedGeometry; } int PageViewItem::uncroppedWidth() const { return m_uncroppedGeometry.width(); } int PageViewItem::uncroppedHeight() const { return m_uncroppedGeometry.height(); } const Okular::NormalizedRect & PageViewItem::crop() const { return m_crop; } double PageViewItem::zoomFactor() const { return m_zoomFactor; } double PageViewItem::absToPageX( double absX ) const { return ( absX - m_uncroppedGeometry.left() ) / m_uncroppedGeometry.width(); } double PageViewItem::absToPageY( double absY ) const { return ( absY - m_uncroppedGeometry.top() ) / m_uncroppedGeometry.height(); } bool PageViewItem::isVisible() const { return m_visible; } QSet& PageViewItem::formWidgets() { return m_formWidgets; } QHash< Okular::Movie *, VideoWidget* >& PageViewItem::videoWidgets() { return m_videoWidgets; } void PageViewItem::setWHZC( int w, int h, double z, const Okular:: NormalizedRect & c ) { m_croppedGeometry.setWidth( w ); m_croppedGeometry.setHeight( h ); m_zoomFactor = z; m_crop = c; m_uncroppedGeometry.setWidth( qRound( w / ( c.right - c.left ) ) ); m_uncroppedGeometry.setHeight( qRound( h / ( c.bottom - c.top ) ) ); foreach(FormWidgetIface *fwi, m_formWidgets) { Okular::NormalizedRect r = fwi->rect(); fwi->setWidthHeight( qRound( fabs( r.right - r.left ) * m_uncroppedGeometry.width() ), qRound( fabs( r.bottom - r.top ) * m_uncroppedGeometry.height() ) ); } Q_FOREACH ( VideoWidget *vw, m_videoWidgets ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->resize( qRound( fabs( r.right - r.left ) * m_uncroppedGeometry.width() ), qRound( fabs( r.bottom - r.top ) * m_uncroppedGeometry.height() ) ); } } void PageViewItem::moveTo( int x, int y ) // Assumes setWHZC() has already been called { m_croppedGeometry.moveLeft( x ); m_croppedGeometry.moveTop( y ); m_uncroppedGeometry.moveLeft( qRound( x - m_crop.left * m_uncroppedGeometry.width() ) ); m_uncroppedGeometry.moveTop( qRound( y - m_crop.top * m_uncroppedGeometry.height() ) ); QSet::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end(); for ( ; it != itEnd; ++it ) { Okular::NormalizedRect r = (*it)->rect(); (*it)->moveTo( qRound( x + m_uncroppedGeometry.width() * r.left ) + 1, qRound( y + m_uncroppedGeometry.height() * r.top ) + 1 ); } Q_FOREACH ( VideoWidget *vw, m_videoWidgets ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->move( qRound( x + m_uncroppedGeometry.width() * r.left ) + 1, qRound( y + m_uncroppedGeometry.height() * r.top ) + 1 ); } } void PageViewItem::setVisible( bool visible ) { setFormWidgetsVisible( visible && m_formsVisible ); m_visible = visible; } void PageViewItem::invalidate() { m_croppedGeometry.setRect( 0, 0, 0, 0 ); m_uncroppedGeometry.setRect( 0, 0, 0, 0 ); } bool PageViewItem::setFormWidgetsVisible( bool visible ) { m_formsVisible = visible; if ( !m_visible ) return false; bool somehadfocus = false; QSet::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end(); for ( ; it != itEnd; ++it ) { bool hadfocus = (*it)->setVisibility( visible && (*it)->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown((*it)->formField()) ); somehadfocus = somehadfocus || hadfocus; } return somehadfocus; } void PageViewItem::reloadFormWidgetsState() { foreach(FormWidgetIface *fwi, m_formWidgets) { fwi->setVisibility( fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField())); } } /*********************/ /** PageViewMessage */ /*********************/ PageViewMessage::PageViewMessage( QWidget * parent ) : QWidget( parent ), m_timer( nullptr ) , m_lineSpacing( 0 ) { setObjectName( QStringLiteral( "pageViewMessage" ) ); setFocusPolicy( Qt::NoFocus ); QPalette pal = palette(); pal.setColor( QPalette::Active, QPalette::Window, QApplication::palette().color( QPalette::Active, QPalette::Window ) ); setPalette( pal ); // if the layout is LtR, we can safely place it in the right position if ( layoutDirection() == Qt::LeftToRight ) move( 10, 10 ); resize( 0, 0 ); hide(); } void PageViewMessage::display( const QString & message, const QString & details, Icon icon, int durationMs ) // give Caesar what belongs to Caesar: code taken from Amarok's osd.h/.cpp // "redde (reddite, pl.) cesari quae sunt cesaris", just btw. :) // The code has been heavily modified since then. { if ( !Okular::Settings::showOSD() ) { hide(); return; } // set text m_message = message; m_details = details; // reset vars m_lineSpacing = 0; // load icon (if set) m_symbol = QPixmap(); + const auto symbolSize = style()->pixelMetric(QStyle::PM_SmallIconSize); if ( icon != None ) { switch ( icon ) { case Annotation: - m_symbol = SmallIcon( QStringLiteral("draw-freehand") ); + m_symbol = QIcon::fromTheme( QStringLiteral("draw-freehand") ).pixmap( symbolSize ); break; case Find: - m_symbol = SmallIcon( QStringLiteral("zoom-original") ); + m_symbol = QIcon::fromTheme( QStringLiteral("zoom-original") ).pixmap( symbolSize ); break; case Error: - m_symbol = SmallIcon( QStringLiteral("dialog-error") ); + m_symbol = QIcon::fromTheme( QStringLiteral("dialog-error") ).pixmap( symbolSize ); break; case Warning: - m_symbol = SmallIcon( QStringLiteral("dialog-warning") ); + m_symbol = QIcon::fromTheme( QStringLiteral("dialog-warning") ).pixmap( symbolSize ); break; default: - m_symbol = SmallIcon( QStringLiteral("dialog-information") ); + m_symbol = QIcon::fromTheme( QStringLiteral("dialog-information") ).pixmap( symbolSize ); break; } } computeSizeAndResize(); // 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 ); m_timer->setSingleShot( true ); connect(m_timer, &QTimer::timeout, this, &PageViewMessage::hide); } m_timer->start( durationMs ); } else if ( m_timer ) m_timer->stop(); qobject_cast(parentWidget())->viewport()->installEventFilter(this); } QRect PageViewMessage::computeTextRect( const QString & message, int extra_width ) const // Return the QRect which embeds the text { int charSize = fontMetrics().averageCharWidth(); /* width of the viewport, minus 20 (~ size removed by further resizing), minus the extra size (usually the icon width), minus (a bit empirical) twice the mean width of a character to ensure that the bounding box is really smaller than the container. */ const int boundingWidth = qobject_cast(parentWidget())->viewport()->width() - 20 - ( extra_width > 0 ? 2 + extra_width : 0 ) - 2*charSize; QRect textRect = fontMetrics().boundingRect( 0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message ); textRect.translate( -textRect.left(), -textRect.top() ); textRect.adjust( 0, 0, 2, 2 ); return textRect; } void PageViewMessage::computeSizeAndResize() { // determine text rectangle const QRect textRect = computeTextRect( m_message, m_symbol.width() ); int width = textRect.width(), height = textRect.height(); if ( !m_details.isEmpty() ) { // determine details text rectangle const QRect detailsRect = computeTextRect( m_details, m_symbol.width() ); width = qMax( width, detailsRect.width() ); height += detailsRect.height(); // plus add a ~60% line spacing m_lineSpacing = static_cast< int >( fontMetrics().height() * 0.6 ); height += m_lineSpacing; } // update geometry with icon information if ( ! m_symbol.isNull() ) { width += 2 + m_symbol.width(); height = qMax( height, m_symbol.height() ); } // resize widget resize( QRect( 0, 0, width + 10, height + 8 ).size() ); // if the layout is RtL, we can move it to the right place only after we // know how much size it will take if ( layoutDirection() == Qt::RightToLeft ) move( parentWidget()->width() - geometry().width() - 10 - 1, 10 ); } bool PageViewMessage::eventFilter(QObject * obj, QEvent * event ) { /* if the parent object (scroll area) resizes, the message should resize as well */ if (event->type() == QEvent::Resize) { QResizeEvent *resizeEvent = static_cast(event); if ( resizeEvent->oldSize() != resizeEvent->size() ) { computeSizeAndResize(); } } // standard event processing return QObject::eventFilter(obj, event); } void PageViewMessage::paintEvent( QPaintEvent * /* e */ ) { const QRect textRect = computeTextRect( m_message, m_symbol.width() ); QRect detailsRect; if ( !m_details.isEmpty() ) { detailsRect = computeTextRect( m_details, m_symbol.width() ); } int textXOffset = 0, // add 2 to account for the reduced drawRoundRect later textYOffset = ( geometry().height() - textRect.height() - detailsRect.height() - m_lineSpacing + 2 ) / 2, iconXOffset = 0, iconYOffset = !m_symbol.isNull() ? ( geometry().height() - m_symbol.height() ) / 2 : 0, shadowOffset = 1; if ( layoutDirection() == Qt::RightToLeft ) iconXOffset = 2 + textRect.width(); else textXOffset = 2 + m_symbol.width(); // draw background QPainter painter( this ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.setPen( Qt::black ); painter.setBrush( palette().color( QPalette::Window ) ); painter.translate( 0.5, 0.5 ); painter.drawRoundRect( 1, 1, width()-2, height()-2, 1600 / width(), 1600 / height() ); // draw icon if present if ( !m_symbol.isNull() ) painter.drawPixmap( 5 + iconXOffset, iconYOffset, m_symbol, 0, 0, m_symbol.width(), m_symbol.height() ); const int xStartPoint = 5 + textXOffset; const int yStartPoint = textYOffset; const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap; // draw shadow and text painter.setPen( palette().color( QPalette::Window ).darker( 115 ) ); painter.drawText( xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, m_message ); if ( !m_details.isEmpty() ) painter.drawText( xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing + shadowOffset, textRect.width(), detailsRect.height(), textDrawingFlags, m_details ); painter.setPen( palette().color( QPalette::WindowText ) ); painter.drawText( xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, m_message ); if ( !m_details.isEmpty() ) painter.drawText( xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing, textRect.width(), detailsRect.height(), textDrawingFlags, m_details ); } void PageViewMessage::mousePressEvent( QMouseEvent * /*e*/ ) { if ( m_timer ) m_timer->stop(); hide(); } /*********************/ /** PageViewToolBar */ /*********************/ ToolBarButton::ToolBarButton( QWidget * parent, const AnnotationToolItem &item ) : QToolButton( parent ), m_id( item.id ), m_isText( item.isText ) { setCheckable( true ); setAutoRaise( true ); resize( buttonSize, buttonSize ); setIconSize( QSize( iconSize, iconSize ) ); setIcon( QIcon( item.pixmap ) ); // set shortcut if defined if ( !item.shortcut.isEmpty() ) setShortcut( QKeySequence( item.shortcut ) ); else KAcceleratorManager::setNoAccel( this ); // if accel is set display it along name QString accelString = shortcut().toString( QKeySequence::NativeText ); if ( !accelString.isEmpty() ) setToolTip( QStringLiteral("%1 [%2]").arg( item.text, accelString ) ); else setToolTip( item.text ); } void ToolBarButton::mouseDoubleClickEvent( QMouseEvent * /*event*/ ) { emit buttonDoubleClicked( buttonID() ); } /* PageViewToolBar */ static const int toolBarGridSize = 40; class ToolBarPrivate { public: ToolBarPrivate( PageViewToolBar * qq ) : q( qq ) { } // rebuild contents and reposition then widget void buildToolBar(); void reposition(); // compute the visible and hidden positions along current side QPoint getInnerPoint() const; QPoint getOuterPoint() const; void selectButton( ToolBarButton * button ); PageViewToolBar * q; // anchored widget and side QWidget * anchorWidget; PageViewToolBar::Side anchorSide; // slide in/out stuff QTimer * animTimer; QPoint currentPosition; QPoint endPosition; bool hiding; bool visible; // background pixmap and buttons QPixmap backgroundPixmap; QLinkedList< ToolBarButton * > buttons; }; PageViewToolBar::PageViewToolBar( PageView * parent, QWidget * anchorWidget ) : QWidget( parent ), d( new ToolBarPrivate( this ) ) { // initialize values of the private data storage structure d->anchorWidget = anchorWidget; d->anchorSide = Left; d->hiding = false; d->visible = false; // create the animation timer d->animTimer = new QTimer( this ); connect( d->animTimer, &QTimer::timeout, this, &PageViewToolBar::slotAnimate ); // apply a filter to get notified when anchor changes geometry d->anchorWidget->installEventFilter( this ); setContextMenuPolicy( Qt::ActionsContextMenu ); addAction( parent->actionCollection()->action( QStringLiteral("options_configure_annotations") ) ); } PageViewToolBar::~PageViewToolBar() { // delete the private data storage structure delete d; } void PageViewToolBar::setItems( const QLinkedList &items ) { // delete buttons if already present if ( !d->buttons.isEmpty() ) { QLinkedList< ToolBarButton * >::iterator it = d->buttons.begin(), end = d->buttons.end(); for ( ; it != end; ++it ) delete *it; d->buttons.clear(); } // create new buttons for given items QLinkedList::const_iterator it = items.begin(), end = items.end(); for ( ; it != end; ++it ) { ToolBarButton * button = new ToolBarButton( this, *it ); connect(button, &ToolBarButton::clicked, this, &PageViewToolBar::slotButtonClicked); connect(button, &ToolBarButton::buttonDoubleClicked, this, &PageViewToolBar::buttonDoubleClicked); d->buttons.append( button ); } // rebuild toolbar shape and contents d->reposition(); } void PageViewToolBar::setSide( Side side ) { d->anchorSide = side; d->reposition(); } void PageViewToolBar::showAndAnimate() { // set parameters for sliding in d->hiding = false; show(); #ifdef OKULAR_ANIMATE_REVIEW_TOOBAR // start scrolling in d->animTimer->start( 20 ); #else d->currentPosition = d->endPosition; move( d->currentPosition ); d->visible = true; #endif } void PageViewToolBar::hideAndDestroy() { // set parameters for sliding out d->hiding = true; d->endPosition = d->getOuterPoint(); #ifdef OKULAR_ANIMATE_REVIEW_TOOBAR // start scrolling out d->animTimer->start( 20 ); #else d->currentPosition = d->endPosition; move( d->currentPosition ); d->visible = false; deleteLater(); #endif } void PageViewToolBar::selectButton( int id ) { ToolBarButton * button = nullptr; if ( id >= 0 && id < d->buttons.count() ) button = *(d->buttons.begin() + id); else { QLinkedList< ToolBarButton * >::const_iterator it = d->buttons.begin(), end = d->buttons.end(); for ( ; !button && it != end; ++it ) if ( (*it)->isChecked() ) button = *it; if ( button ) button->setChecked( false ); } d->selectButton( button ); } bool PageViewToolBar::eventFilter( QObject * obj, QEvent * e ) { // if anchorWidget changed geometry reposition toolbar if ( obj == d->anchorWidget && e->type() == QEvent::Resize ) { d->animTimer->stop(); if ( d->hiding ) deleteLater(); else d->reposition(); } // don't block event return false; } void PageViewToolBar::paintEvent( QPaintEvent * e ) { // paint the internal pixmap over the widget QPainter p( this ); p.drawImage( e->rect().topLeft(), d->backgroundPixmap.toImage(), e->rect() ); } void PageViewToolBar::mousePressEvent( QMouseEvent * e ) { // set 'dragging' cursor if ( e->button() == Qt::LeftButton ) setCursor( Qt::SizeAllCursor ); } void PageViewToolBar::mouseMoveEvent( QMouseEvent * e ) { if ( ( QApplication::mouseButtons() & Qt::LeftButton ) != Qt::LeftButton ) return; // compute the nearest side to attach the widget to QPoint parentPos = mapToParent( e->pos() ); float nX = (float)parentPos.x() / (float)d->anchorWidget->width(), nY = (float)parentPos.y() / (float)d->anchorWidget->height(); if ( nX > 0.3 && nX < 0.7 && nY > 0.3 && nY < 0.7 ) return; bool LT = nX < (1.0 - nY); bool LB = nX < (nY); Side side = LT ? ( LB ? Left : Top ) : ( LB ? Bottom : Right ); // check if side changed if ( side == d->anchorSide ) return; d->anchorSide = side; d->reposition(); emit orientationChanged( (int)side ); } void PageViewToolBar::mouseReleaseEvent( QMouseEvent * e ) { // set normal cursor if ( e->button() == Qt::LeftButton ) setCursor( Qt::ArrowCursor ); } void ToolBarPrivate::buildToolBar() { int buttonsNumber = buttons.count(), parentWidth = anchorWidget->width(), parentHeight = anchorWidget->height(), myCols = 1, myRows = 1; // 1. find out columns and rows we're going to use bool topLeft = anchorSide == PageViewToolBar::Left || anchorSide == PageViewToolBar::Top; bool vertical = anchorSide == PageViewToolBar::Left || anchorSide == PageViewToolBar::Right; if ( vertical ) { myCols = 1 + (buttonsNumber * toolBarGridSize) / (parentHeight - toolBarGridSize); myRows = (int)ceil( (float)buttonsNumber / (float)myCols ); } else { myRows = 1 + (buttonsNumber * toolBarGridSize) / (parentWidth - toolBarGridSize); myCols = (int)ceil( (float)buttonsNumber / (float)myRows ); } // 2. compute widget size (from rows/cols) int myWidth = myCols * toolBarGridSize, myHeight = myRows * toolBarGridSize, xOffset = (toolBarGridSize - ToolBarButton::buttonSize) / 2, yOffset = (toolBarGridSize - ToolBarButton::buttonSize) / 2; if ( vertical ) { myHeight += 16; myWidth += 4; yOffset += 12; if ( anchorSide == PageViewToolBar::Right ) xOffset += 4; } else { myWidth += 16; myHeight += 4; xOffset += 12; if ( anchorSide == PageViewToolBar::Bottom ) yOffset += 4; } bool prevUpdates = q->updatesEnabled(); q->setUpdatesEnabled( false ); // 3. resize pixmap, mask and widget QBitmap mask( myWidth + 1, myHeight + 1 ); backgroundPixmap = QPixmap( myWidth + 1, myHeight + 1 ); backgroundPixmap.fill(Qt::transparent); q->resize( myWidth + 1, myHeight + 1 ); // 4. create and set transparency mask // 4. draw background QPainter maskPainter( &mask); mask.fill( Qt::white ); maskPainter.setBrush( Qt::black ); if ( vertical ) maskPainter.drawRoundRect( topLeft ? -10 : 0, 0, myWidth + 11, myHeight, 2000 / (myWidth + 10), 2000 / myHeight ); else maskPainter.drawRoundRect( 0, topLeft ? -10 : 0, myWidth, myHeight + 11, 2000 / myWidth, 2000 / (myHeight + 10) ); maskPainter.end(); q->setMask( mask ); // 5. draw background QPainter bufferPainter( &backgroundPixmap ); bufferPainter.translate( 0.5, 0.5 ); QPalette pal = q->palette(); // 5.1. draw horizontal/vertical gradient QLinearGradient grad; switch ( anchorSide ) { case PageViewToolBar::Left: grad = QLinearGradient( 0, 1, myWidth + 1, 1 ); break; case PageViewToolBar::Right: grad = QLinearGradient( myWidth + 1, 1, 0, 1 ); break; case PageViewToolBar::Top: grad = QLinearGradient( 1, 0, 1, myHeight + 1 ); break; case PageViewToolBar::Bottom: grad = QLinearGradient( 1, myHeight + 1, 0, 1 ); break; } grad.setColorAt( 0, pal.color( QPalette::Active, QPalette::Button ) ); grad.setColorAt( 1, pal.color( QPalette::Active, QPalette::Light ) ); bufferPainter.setBrush( QBrush( grad ) ); // 5.2. draw rounded border bufferPainter.setPen( pal.color( QPalette::Active, QPalette::Dark ).lighter( 140 ) ); bufferPainter.setRenderHints( QPainter::Antialiasing ); if ( vertical ) bufferPainter.drawRoundRect( topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight ); else bufferPainter.drawRoundRect( 0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10) ); // 5.3. draw handle bufferPainter.translate( -0.5, -0.5 ); bufferPainter.setPen( pal.color( QPalette::Active, QPalette::Mid ) ); if ( vertical ) { int dx = anchorSide == PageViewToolBar::Left ? 2 : 4; bufferPainter.drawLine( dx, 6, dx + myWidth - 8, 6 ); bufferPainter.drawLine( dx, 9, dx + myWidth - 8, 9 ); bufferPainter.setPen( pal.color( QPalette::Active, QPalette::Light ) ); bufferPainter.drawLine( dx + 1, 7, dx + myWidth - 7, 7 ); bufferPainter.drawLine( dx + 1, 10, dx + myWidth - 7, 10 ); } else { int dy = anchorSide == PageViewToolBar::Top ? 2 : 4; bufferPainter.drawLine( 6, dy, 6, dy + myHeight - 8 ); bufferPainter.drawLine( 9, dy, 9, dy + myHeight - 8 ); bufferPainter.setPen( pal.color( QPalette::Active, QPalette::Light ) ); bufferPainter.drawLine( 7, dy + 1, 7, dy + myHeight - 7 ); bufferPainter.drawLine( 10, dy + 1, 10, dy + myHeight - 7 ); } bufferPainter.end(); // 6. reposition buttons (in rows/col grid) int gridX = 0, gridY = 0; QLinkedList< ToolBarButton * >::const_iterator it = buttons.begin(), end = buttons.end(); for ( ; it != end; ++it ) { ToolBarButton * button = *it; button->move( gridX * toolBarGridSize + xOffset, gridY * toolBarGridSize + yOffset ); button->show(); if ( ++gridX == myCols ) { gridX = 0; gridY++; } } q->setUpdatesEnabled( prevUpdates ); } void ToolBarPrivate::reposition() { // note: hiding widget here will gives better gfx, but ends drag operation // rebuild widget and move it to its final place buildToolBar(); if ( !visible ) { currentPosition = getOuterPoint(); endPosition = getInnerPoint(); } else { currentPosition = getInnerPoint(); endPosition = getOuterPoint(); } q->move( currentPosition ); // repaint all buttons (to update background) QLinkedList< ToolBarButton * >::const_iterator it = buttons.begin(), end = buttons.end(); for ( ; it != end; ++it ) (*it)->update(); } QPoint ToolBarPrivate::getInnerPoint() const { // returns the final position of the widget QPoint newPos; switch ( anchorSide ) { case PageViewToolBar::Left: newPos = QPoint( 0, ( anchorWidget->height() - q->height() ) / 2 ); break; case PageViewToolBar::Top: newPos = QPoint( ( anchorWidget->width() - q->width() ) / 2, 0 ); break; case PageViewToolBar::Right: newPos = QPoint( anchorWidget->width() - q->width(), ( anchorWidget->height() - q->height() ) / 2 ); break; case PageViewToolBar::Bottom: newPos = QPoint( ( anchorWidget->width() - q->width()) / 2, anchorWidget->height() - q->height() ); break; } return newPos + anchorWidget->pos(); } QPoint ToolBarPrivate::getOuterPoint() const { // returns the point from which the transition starts QPoint newPos; switch ( anchorSide ) { case PageViewToolBar::Left: newPos = QPoint( -q->width(), ( anchorWidget->height() - q->height() ) / 2 ); break; case PageViewToolBar::Top: newPos = QPoint( ( anchorWidget->width() - q->width() ) / 2, -q->height() ); break; case PageViewToolBar::Right: newPos = QPoint( anchorWidget->width(), ( anchorWidget->height() - q->height() ) / 2 ); break; case PageViewToolBar::Bottom: newPos = QPoint( ( anchorWidget->width() - q->width() ) / 2, anchorWidget->height() ); break; } return newPos + anchorWidget->pos(); } void PageViewToolBar::slotAnimate() { // move currentPosition towards endPosition int dX = d->endPosition.x() - d->currentPosition.x(), dY = d->endPosition.y() - d->currentPosition.y(); dX = dX / 6 + qMax( -1, qMin( 1, dX) ); dY = dY / 6 + qMax( -1, qMin( 1, dY) ); d->currentPosition.setX( d->currentPosition.x() + dX ); d->currentPosition.setY( d->currentPosition.y() + dY ); // move the widget move( d->currentPosition ); // handle arrival to the end if ( d->currentPosition == d->endPosition ) { d->animTimer->stop(); if ( d->hiding ) { d->visible = false; deleteLater(); } else { d->visible = true; } } } void PageViewToolBar::slotButtonClicked() { ToolBarButton * button = qobject_cast( sender() ); d->selectButton( button ); } void ToolBarPrivate::selectButton( ToolBarButton * button ) { if ( button ) { // deselect other buttons QLinkedList< ToolBarButton * >::const_iterator it = buttons.begin(), end = buttons.end(); for ( ; it != end; ++it ) if ( *it != button ) (*it)->setChecked( false ); // emit signal (-1 if button has been unselected) emit q->toolSelected( button->isChecked() ? button->buttonID() : -1 ); } } void PageViewToolBar::setToolsEnabled( bool on ) { QLinkedList< ToolBarButton * >::const_iterator it = d->buttons.begin(), end = d->buttons.end(); for ( ; it != end; ++it ) (*it)->setEnabled( on ); } void PageViewToolBar::setTextToolsEnabled( bool on ) { QLinkedList< ToolBarButton * >::const_iterator it = d->buttons.begin(), end = d->buttons.end(); for ( ; it != end; ++it ) if ( (*it)->isText() ) (*it)->setEnabled( on ); } #include "moc_pageviewutils.cpp" diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index 49d065467..14912805a 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -1,2522 +1,2521 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * * * 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. * ***************************************************************************/ #include "presentationwidget.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #ifdef Q_OS_LINUX #include #include // For ::close() for sleep inhibition #endif // system includes #include #include // local includes #include "annotationtools.h" #include "debug_ui.h" #include "drawingtoolactions.h" #include "guiutils.h" #include "pagepainter.h" #include "presentationsearchbar.h" #include "priorities.h" #include "videowidget.h" #include "core/action.h" #include "core/annotations.h" #include "core/audioplayer.h" #include "core/document.h" #include "core/generator.h" #include "core/movie.h" #include "core/page.h" #include "settings.h" #include "settings_core.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 { PresentationFrame() = default; ~PresentationFrame() { qDeleteAll( videoWidgets ); } PresentationFrame(const PresentationFrame &) = delete; PresentationFrame &operator=(const PresentationFrame &) = delete; void recalcGeometry( int width, int height, float screenRatio ) { // calculate frame geometry keeping constant aspect ratio float pageRatio = page->ratio(); int pageWidth = width, pageHeight = height; if ( pageRatio > screenRatio ) pageWidth = (int)( (float)pageHeight / pageRatio ); else pageHeight = (int)( (float)pageWidth * pageRatio ); geometry.setRect( ( width - pageWidth ) / 2, ( height - pageHeight ) / 2, pageWidth, pageHeight ); Q_FOREACH ( VideoWidget *vw, videoWidgets ) { const Okular::NormalizedRect r = vw->normGeometry(); QRect vwgeom = r.geometry( geometry.width(), geometry.height() ); vw->resize( vwgeom.size() ); vw->move( geometry.topLeft() + vwgeom.topLeft() ); } } const Okular::Page * page; QRect geometry; QHash< Okular::Movie *, VideoWidget * > videoWidgets; QLinkedList< SmoothPath > drawings; }; // a custom QToolBar that basically does not propagate the event if the widget // background is not automatically filled class PresentationToolBar : public QToolBar { Q_OBJECT public: PresentationToolBar( QWidget * parent = Q_NULLPTR ) : QToolBar( parent ) {} protected: void mousePressEvent( QMouseEvent * e ) override { QToolBar::mousePressEvent( e ); e->accept(); } void mouseReleaseEvent( QMouseEvent * e ) override { QToolBar::mouseReleaseEvent( e ); e->accept(); } }; PresentationWidget::PresentationWidget( QWidget * parent, Okular::Document * doc, DrawingToolActions * drawingToolActions, KActionCollection * collection ) : QWidget( nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint ), m_pressedLink( nullptr ), m_handCursor( false ), m_drawingEngine( nullptr ), m_screenInhibitCookie(0), m_sleepInhibitFd(-1), m_parentWidget( parent ), m_document( doc ), m_frameIndex( -1 ), m_topBar( nullptr ), m_pagesEdit( nullptr ), m_searchBar( nullptr ), m_ac( collection ), m_screenSelect( nullptr ), m_isSetup( false ), m_blockNotifications( false ), m_inBlackScreenMode( false ), m_showSummaryView( Okular::Settings::slidesShowSummary() ), m_advanceSlides( Okular::SettingsCore::slidesAdvance() ), m_goToPreviousPageOnRelease( false ), m_goToNextPageOnRelease( false ) { Q_UNUSED( parent ) setAttribute( Qt::WA_DeleteOnClose ); setAttribute( Qt::WA_OpaquePaintEvent ); setObjectName( QStringLiteral( "presentationWidget" ) ); QString caption = doc->metaData( QStringLiteral("DocumentTitle") ).toString(); if ( caption.trimmed().isEmpty() ) caption = doc->currentDocument().fileName(); caption = i18nc( "[document title/filename] – Presentation", "%1 – Presentation", caption ); setWindowTitle( caption ); m_width = -1; m_screen = -2; // create top toolbar m_topBar = new PresentationToolBar( this ); m_topBar->setObjectName( QStringLiteral( "presentationBar" ) ); m_topBar->setMovable( false ); m_topBar->layout()->setContentsMargins(0, 0, 0, 0); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous") ), i18n( "Previous Page" ), this, SLOT(slotPrevPage()) ); m_pagesEdit = new KLineEdit( m_topBar ); QSizePolicy sp = m_pagesEdit->sizePolicy(); sp.setHorizontalPolicy( QSizePolicy::Minimum ); m_pagesEdit->setSizePolicy( sp ); QFontMetrics fm( m_pagesEdit->font() ); QStyleOptionFrame option; option.initFrom( m_pagesEdit ); 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 QIntValidator *validator = new QIntValidator( 1, m_document->pages(), m_pagesEdit ); m_pagesEdit->setValidator( validator ); m_topBar->addWidget( m_pagesEdit ); QLabel *pagesLabel = new QLabel( m_topBar ); pagesLabel->setText( QLatin1String( " / " ) + QString::number( m_document->pages() ) + QLatin1String( " " ) ); m_topBar->addWidget( pagesLabel ); connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next") ), i18n( "Next Page" ), this, SLOT(slotNextPage()) ); m_topBar->addSeparator(); QAction *playPauseAct = collection->action( QStringLiteral("presentation_play_pause") ); playPauseAct->setEnabled( true ); connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause); m_topBar->addAction( playPauseAct ); setPlayPauseIcon(); addAction( playPauseAct ); m_topBar->addSeparator(); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } connect( drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine ); connect( drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions ); QAction *eraseDrawingAct = collection->action( QStringLiteral("presentation_erase_drawings") ); eraseDrawingAct->setEnabled( true ); connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings); m_topBar->addAction( eraseDrawingAct ); addAction( eraseDrawingAct ); QDesktopWidget *desktop = QApplication::desktop(); if ( desktop->numScreens() > 1 ) { m_topBar->addSeparator(); m_screenSelect = new KSelectAction( QIcon::fromTheme( QStringLiteral("video-display") ), i18n( "Switch Screen" ), m_topBar ); m_screenSelect->setToolBarMode( KSelectAction::MenuMode ); m_screenSelect->setToolButtonPopupMode( QToolButton::InstantPopup ); m_topBar->addAction( m_screenSelect ); const int screenCount = desktop->numScreens(); for ( int i = 0; i < screenCount; ++i ) { QAction *act = m_screenSelect->addAction( i18nc( "%1 is the screen number (0, 1, ...)", "Screen %1", i ) ); act->setData( QVariant::fromValue( i ) ); } } QWidget *spacer = new QWidget( m_topBar ); spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::MinimumExpanding ); m_topBar->addWidget( spacer ); m_topBar->addAction( QIcon::fromTheme( QStringLiteral("application-exit") ), i18n( "Exit Presentation Mode" ), this, SLOT(close()) ); m_topBar->setAutoFillBackground( true ); showTopBar( false ); // change topbar background color QPalette p = m_topBar->palette(); p.setColor( QPalette::Active, QPalette::Button, Qt::gray ); p.setColor( QPalette::Active, QPalette::Window, Qt::darkGray ); m_topBar->setPalette( p ); // Grab swipe gestures to change pages grabGesture(Qt::SwipeGesture); // misc stuff setMouseTracking( true ); setContextMenuPolicy( Qt::PreventContextMenu ); m_transitionTimer = new QTimer( this ); m_transitionTimer->setSingleShot( true ); connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep); m_overlayHideTimer = new QTimer( this ); m_overlayHideTimer->setSingleShot( true ); connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay); m_nextPageTimer = new QTimer( this ); m_nextPageTimer->setSingleShot( true ); connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage); connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction); connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction); // handle cursor appearance as specified in configuration if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); KCursor::setHideCursorDelay( 3000 ); } else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { setCursor( QCursor( Qt::BlankCursor ) ); } setupActions(); // inhibit power management inhibitPowerManagement(); show(); QTimer::singleShot( 0, this, &PresentationWidget::slotDelayedEvents ); // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled setFocus( Qt::OtherFocusReason ); // Catch TabletEnterProximity and TabletLeaveProximity events from the QApplication qApp->installEventFilter( this ); } PresentationWidget::~PresentationWidget() { // allow power management saver again allowPowerManagement(); // stop the audio playbacks Okular::AudioPlayer::instance()->stopPlaybacks(); // remove our highlights if ( m_searchBar ) { m_document->resetSearch( PRESENTATION_SEARCH_ID ); } // remove this widget from document observer m_document->removeObserver( this ); foreach( QAction *action, m_topBar->actions() ) { action->setChecked( false ); action->setEnabled( false ); } delete m_drawingEngine; // delete frames qDeleteAll( m_frames ); qApp->removeEventFilter( this ); } void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { // same document, nothing to change - here we assume the document sets up // us with the whole document set as first notifySetup() if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) return; // delete previous frames (if any (shouldn't be)) qDeleteAll( m_frames ); if ( !m_frames.isEmpty() ) qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress."; m_frames.clear(); // create the new frames float screenRatio = (float)m_height / (float)m_width; for ( const Okular::Page * page : pageSet ) { PresentationFrame * frame = new PresentationFrame(); frame->page = page; const QLinkedList< Okular::Annotation * > annotations = page->annotations(); for ( Okular::Annotation * a : annotations ) { if ( a->subType() == Okular::Annotation::AMovie ) { Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), m_document, this ); frame->videoWidgets.insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); if ( richMediaAnn->movie() ) { VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), m_document, this ); frame->videoWidgets.insert( richMediaAnn->movie(), vw ); vw->pageInitialized(); } } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); if ( movie ) { VideoWidget * vw = new VideoWidget( screenAnn, movie, m_document, this ); frame->videoWidgets.insert( movie, vw ); vw->pageInitialized(); } } } frame->recalcGeometry( m_width, m_height, screenRatio ); // add the frame to the vector m_frames.push_back( frame ); } // get metadata from the document m_metaStrings.clear(); const Okular::DocumentInfo info = m_document->documentInfo( QSet() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author ); if ( !info.get( Okular::DocumentInfo::Title ).isNull() ) m_metaStrings += i18n( "Title: %1", info.get( Okular::DocumentInfo::Title ) ); if ( !info.get( Okular::DocumentInfo::Author ).isNull() ) m_metaStrings += i18n( "Author: %1", info.get( Okular::DocumentInfo::Author ) ); m_metaStrings += i18n( "Pages: %1", m_document->pages() ); m_metaStrings += i18n( "Click to begin" ); m_isSetup = true; } void PresentationWidget::notifyViewportChanged( bool /*smoothMove*/ ) { // display the current page changePage( m_document->viewport().pageNumber ); // auto advance to the next page if set startAutoChangeTimer(); } void PresentationWidget::notifyPageChanged( int pageNumber, int changedFlags ) { // if we are blocking the notifications, do nothing if ( m_blockNotifications ) return; // check if it's the last requested pixmap. if so update the widget. if ( (changedFlags & ( DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights ) ) && pageNumber == m_frameIndex ) generatePage( changedFlags & ( DocumentObserver::Annotations | DocumentObserver::Highlights ) ); } void PresentationWidget::notifyCurrentPageChanged( int previousPage, int currentPage ) { if ( previousPage != -1 ) { // stop video playback Q_FOREACH ( VideoWidget *vw, m_frames[ previousPage ]->videoWidgets ) { vw->stop(); vw->pageLeft(); } // stop audio playback, if any Okular::AudioPlayer::instance()->stopPlaybacks(); // perform the page closing action, if any if ( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ) m_document->processAction( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( previousPage )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); if ( action ) m_document->processAction( action ); } } if ( currentPage != -1 ) { m_frameIndex = currentPage; // check if pixmap exists or else request it PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); bool signalsBlocked = m_pagesEdit->signalsBlocked(); m_pagesEdit->blockSignals( true ); m_pagesEdit->setText( QString::number( m_frameIndex + 1 ) ); m_pagesEdit->blockSignals( signalsBlocked ); // if pixmap not inside the Okular::Page we request it and wait for // notifyPixmapChanged call or else we can proceed to pixmap generation if ( !frame->page->hasPixmap( this, ceil(pixW * qApp->devicePixelRatio()), ceil(pixH * qApp->devicePixelRatio()) ) ) { requestPixmaps(); } else { // make the background pixmap generatePage(); } // perform the page opening action, if any if ( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ) m_document->processAction( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( m_frameIndex )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); if ( action ) m_document->processAction( action ); } // start autoplay video playback Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets ) vw->pageEntered(); } } bool PresentationWidget::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // can unload all pixmaps except for the currently visible one return pageNumber != m_frameIndex; } else { // can unload all pixmaps except for the currently visible one, previous and next return qAbs(pageNumber - m_frameIndex) <= 1; } } void PresentationWidget::setupActions() { addAction( m_ac->action( QStringLiteral("first_page") ) ); addAction( m_ac->action( QStringLiteral("last_page") ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Prior ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Next ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentBack ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentForward ) ) ) ); QAction *action = m_ac->action( QStringLiteral("switch_blackscreen_mode") ); connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode); action->setEnabled( true ); addAction( action ); } void PresentationWidget::setPlayPauseIcon() { QAction *playPauseAction = m_ac->action( QStringLiteral("presentation_play_pause") ); if ( m_advanceSlides ) { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Pause" ) ); } else { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-start") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Play" ) ); } } bool PresentationWidget::eventFilter (QObject *o, QEvent *e ) { if ( o == qApp ) { if ( e->type() == QTabletEvent::TabletEnterProximity ) { setCursor( QCursor( Qt::CrossCursor ) ); } else if ( e->type() == QTabletEvent::TabletLeaveProximity ) { setCursor( QCursor( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ? Qt::BlankCursor : Qt::ArrowCursor ) ); if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay) { // Trick KCursor to hide the cursor if needed by sending an "unknown" key press event // Send also the key release to make the world happy even it's probably not needed QKeyEvent kp( QEvent::KeyPress, 0, Qt::NoModifier ); qApp->sendEvent( this, &kp ); QKeyEvent kr( QEvent::KeyRelease, 0, Qt::NoModifier ); qApp->sendEvent( this, &kr ); } } } return false; } // bool PresentationWidget::event( QEvent * e ) { if ( e->type() == QEvent::Gesture ) return gestureEvent(static_cast(e)); if ( e->type() == QEvent::ToolTip ) { QHelpEvent * he = (QHelpEvent*)e; QRect r; const Okular::Action * link = getLink( he->x(), he->y(), &r ); if ( link ) { QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, this, r ); } e->accept(); return true; } else // do not stop the event return QWidget::event( e ); } bool PresentationWidget::gestureEvent( QGestureEvent * event ) { // Swiping left or right on a touch screen will go to the previous or next slide, respectively. // The precise gesture is the standard Qt swipe: with three(!) fingers. if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) { QSwipeGesture * swipeEvent = static_cast(swipe); if (swipeEvent->state() == Qt::GestureFinished) { if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) { slotPrevPage(); event->accept(); return true; } if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) { slotNextPage(); event->accept(); return true; } } } return false; } void PresentationWidget::keyPressEvent( QKeyEvent * e ) { if ( !m_isSetup ) return; switch ( e->key() ) { case Qt::Key_Left: case Qt::Key_Backspace: case Qt::Key_PageUp: case Qt::Key_Up: slotPrevPage(); break; case Qt::Key_Right: case Qt::Key_Space: case Qt::Key_PageDown: case Qt::Key_Down: slotNextPage(); break; case Qt::Key_Home: slotFirstPage(); break; case Qt::Key_End: slotLastPage(); break; case Qt::Key_Escape: if ( !m_topBar->isHidden() ) showTopBar( false ); else close(); break; } } void PresentationWidget::wheelEvent( QWheelEvent * e ) { if ( !m_isSetup ) return; // performance note: don't remove the clipping int div = e->angleDelta().y() / 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 ) { if ( !m_isSetup ) return; if ( m_drawingEngine ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } return; } // 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; const Okular::Annotation *annotation = getAnnotation( e->x(), e->y() ); if ( annotation ) { if ( annotation->subType() == Okular::Annotation::AMovie ) { const Okular::MovieAnnotation *movieAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::ARichMedia ) { const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( richMediaAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::AScreen ) { m_document->processAction( static_cast( annotation )->action() ); return; } } // handle clicking on top-right overlay if ( !( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) && m_overlayGeometry.contains( e->pos() ) ) { overlayClick( e->pos() ); return; } // Actual mouse press events always lead to the next page page if ( e->source() == Qt::MouseEventNotSynthesized ) { m_goToNextPageOnRelease = true; } // Touch events may lead to the previous or next page else if ( Okular::Settings::slidesTapNavigation() != Okular::Settings::EnumSlidesTapNavigation::Disabled ) { switch ( Okular::Settings::slidesTapNavigation() ) { case Okular::Settings::EnumSlidesTapNavigation::ForwardBackward: { if ( e->x() < ( geometry().width()/2 ) ) { m_goToPreviousPageOnRelease = true; } else { m_goToNextPageOnRelease = true; } break; } case Okular::Settings::EnumSlidesTapNavigation::Forward: { m_goToNextPageOnRelease = true; break; } case Okular::Settings::EnumSlidesTapNavigation::Disabled: { // Do Nothing } } } } // pressing forward button else if ( e->button() == Qt::ForwardButton ) { m_goToNextPageOnRelease = true; } // pressing right or backward button else if ( e->button() == Qt::RightButton || e->button() == Qt::BackButton ) m_goToPreviousPageOnRelease = true; } void PresentationWidget::mouseReleaseEvent( QMouseEvent * e ) { if ( m_drawingEngine ) { routeMouseDrawingEvent( e ); return; } // if releasing on the same link we pressed over, execute it if ( m_pressedLink && e->button() == Qt::LeftButton ) { const Okular::Action * link = getLink( e->x(), e->y() ); if ( link == m_pressedLink ) m_document->processAction( link ); m_pressedLink = nullptr; } if ( m_goToPreviousPageOnRelease ) { slotPrevPage(); m_goToPreviousPageOnRelease = false; } if ( m_goToNextPageOnRelease ) { slotNextPage(); m_goToNextPageOnRelease = false; } } void PresentationWidget::mouseMoveEvent( QMouseEvent * e ) { // safety check if ( !m_isSetup ) return; // update cursor and tooltip if hovering a link if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) testCursorOnLink( e->x(), e->y() ); if ( !m_topBar->isHidden() ) { // hide a shown bar when exiting the area if ( e->y() > ( m_topBar->height() + 1 ) ) { showTopBar( false ); setFocus( Qt::OtherFocusReason ); } } else { if ( m_drawingEngine && e->buttons() != Qt::NoButton ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } } else { // show the bar if reaching top 2 pixels if ( e->y() <= 1 ) showTopBar( true ); // handle "dragging the wheel" if clicking on its geometry else if ( ( QApplication::mouseButtons() & Qt::LeftButton ) && m_overlayGeometry.contains( e->pos() ) ) overlayClick( e->pos() ); } } } void PresentationWidget::paintEvent( QPaintEvent * pe ) { qreal dpr = devicePixelRatioF(); if ( m_inBlackScreenMode ) { QPainter painter( this ); painter.fillRect( pe->rect(), Qt::black ); return; } if ( !m_isSetup ) { m_width = width(); m_height = height(); connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind); // register this observer in document. events will come immediately m_document->addObserver( this ); // show summary if requested if ( Okular::Settings::slidesShowSummary() ) generatePage(); } // check painting rect consistency QRect r = pe->rect().intersected( QRect( QPoint( 0, 0 ), geometry().size() ) ); if ( r.isNull() ) return; if ( m_lastRenderedPixmap.isNull() ) { QPainter painter( this ); painter.fillRect( pe->rect(), Okular::Settings::slidesBackgroundColor() ); return; } // blit the pixmap to the screen QPainter painter( this ); for ( const QRect &r : pe->region() ) { if ( !r.isValid() ) continue; #ifdef ENABLE_PROGRESS_OVERLAY const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect()); if ( Okular::Settings::slidesShowProgress() && r.intersects( m_overlayGeometry ) ) { // backbuffer the overlay operation QPixmap backPixmap( dR.size() ); backPixmap.setDevicePixelRatio( dpr ); QPainter pixPainter( &backPixmap ); // first draw the background on the backbuffer pixPainter.drawPixmap( QPoint(0,0), m_lastRenderedPixmap, dR ); // then blend the overlay (a piece of) over the background QRect ovr = m_overlayGeometry.intersected( r ); pixPainter.drawPixmap( (ovr.left() - r.left()), (ovr.top() - r.top()), m_lastRenderedOverlay, (ovr.left() - m_overlayGeometry.left()) * dpr, (ovr.top() - m_overlayGeometry.top()) * dpr, ovr.width() * dpr, ovr.height() * dpr ); // finally blit the pixmap to the screen pixPainter.end(); const QRect backPixmapRect = backPixmap.rect(); const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect()); painter.drawPixmap( r.topLeft(), backPixmap, dBackPixmapRect ); } else #endif // copy the rendered pixmap to the screen painter.drawPixmap( r.topLeft(), m_lastRenderedPixmap, dR ); } // paint drawings if ( m_frameIndex != -1 ) { painter.save(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; const QSize pmSize( geom.width() * dpr, geom.height() * dpr ); QPixmap pm( pmSize ); pm.fill( Qt::transparent ); QPainter pmPainter( &pm ); pm.setDevicePixelRatio( dpr ); pmPainter.setRenderHints( QPainter::Antialiasing ); // Paint old paths foreach ( const SmoothPath &drawing, m_frames[ m_frameIndex ]->drawings ) drawing.paint( &pmPainter, pmSize.width(), pmSize.height() ); // Paint the path that is currently being drawn by the user if ( m_drawingEngine && m_drawingRect.intersects( pe->rect() ) ) m_drawingEngine->paint( &pmPainter, pmSize.width(), pmSize.height(), m_drawingRect.intersected( pe->rect() ) ); painter.setRenderHints( QPainter::Antialiasing ); painter.drawPixmap( geom.topLeft() , pm ); painter.restore(); } painter.end(); } void PresentationWidget::resizeEvent( QResizeEvent *re ) { // qCDebug(OkularUiDebug) << re->oldSize() << "=>" << re->size(); if ( re->oldSize() == QSize( -1, -1 ) ) return; m_screen = QApplication::desktop()->screenNumber( this ); applyNewScreenSize( re->oldSize() ); } void PresentationWidget::leaveEvent( QEvent * e ) { Q_UNUSED( e ) if ( !m_topBar->isHidden() ) { showTopBar( false ); } } // const void * PresentationWidget::getObjectRect( Okular::ObjectRect::ObjectType type, int x, int y, QRect * geometry ) const { // no links on invalid pages if ( geometry && !geometry->isNull() ) geometry->setRect( 0, 0, 0, 0 ); if ( m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size() ) return nullptr; // get frame, page and geometry const PresentationFrame * frame = m_frames[ m_frameIndex ]; const Okular::Page * 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 nullptr; // check if 1) there is an object and 2) it's a link const QRect d = QApplication::desktop()->screenGeometry( m_screen ); const Okular::ObjectRect * object = page->objectRect( type, nx, ny, d.width(), d.height() ); if ( !object ) return nullptr; // compute link geometry if destination rect present if ( geometry ) { *geometry = object->boundingRect( frameGeometry.width(), frameGeometry.height() ); geometry->translate( frameGeometry.left(), frameGeometry.top() ); } // return the link pointer return object->object(); } const Okular::Action * PresentationWidget::getLink( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::Action, x, y, geometry ) ); } const Okular::Annotation * PresentationWidget::getAnnotation( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::OAnnotation, x, y, geometry ) ); } void PresentationWidget::testCursorOnLink( int x, int y ) { const Okular::Action * link = getLink( x, y, nullptr ); const Okular::Annotation *annotation = getAnnotation( x, y, nullptr ); const bool needsHandCursor = ( ( link != nullptr ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AMovie ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::ARichMedia ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AScreen ) && ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != nullptr ) ) ); // only react on changes (in/out from a link) if ( ( needsHandCursor && !m_handCursor ) || ( !needsHandCursor && m_handCursor ) ) { // change cursor shape m_handCursor = needsHandCursor; setCursor( QCursor( m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor ) ); } } 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( (double)-xPos, (double)-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_showSummaryView ) { m_showSummaryView = false; m_frameIndex = -1; return; } if ( m_frameIndex == newPage ) return; // switch to newPage m_document->setViewportPage( newPage, this ); if ( (Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1 ) notifyCurrentPageChanged( -1, newPage ); } void PresentationWidget::generatePage( bool disableTransition ) { if ( m_lastRenderedPixmap.isNull() ) { qreal dpr = qApp->devicePixelRatio(); m_lastRenderedPixmap = QPixmap( m_width * dpr, m_height * dpr ); m_lastRenderedPixmap.setDevicePixelRatio(dpr); m_previousPagePixmap = QPixmap(); } else { m_previousPagePixmap = m_lastRenderedPixmap; } // 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 ( Okular::Settings::slidesShowProgress() && m_frameIndex != -1 ) generateOverlay(); #endif // start transition on pages that have one if ( !disableTransition && Okular::Settings::slidesTransitionsEnabled() ) { const Okular::PageTransition * transition = m_frameIndex != -1 ? m_frames[ m_frameIndex ]->page->transition() : nullptr; if ( transition ) initTransition( transition ); else { Okular::PageTransition trans = defaultTransition(); initTransition( &trans ); } } else { Okular::PageTransition trans = defaultTransition( Okular::Settings::EnumSlidesTransition::Replace ); initTransition( &trans ); } // update cursor + tooltip if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) { QPoint p = mapFromGlobal( QCursor::pos() ); testCursorOnLink( p.x(), p.y() ); } } void PresentationWidget::generateIntroPage( QPainter & p ) { qreal dpr = qApp->devicePixelRatio(); // use a vertical gray gradient background int blend1 = m_height / 10, blend2 = 9 * m_height / 10; int baseTint = QColor(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 okular logo in the four corners - QPixmap logo = DesktopIcon( QStringLiteral("okular"), 64 * dpr ); + QPixmap logo = QIcon::fromTheme( QStringLiteral("okular") ).pixmap( 64 * dpr ); logo.setDevicePixelRatio( dpr ); 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, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] ); // text body p.setPen( 128 + (127 * i) / strNum ); p.drawText( 0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::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.translate( -geom.left(), -geom.top() ); // draw the page using the shared PagePainter class int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; PagePainter::paintPageOnPainter( &p, frame->page, this, flags, geom.width(), geom.height(), geom ); // restore painter p.translate( -frame->geometry.left(), -frame->geometry.top() ); // fill unpainted areas with background color const QRegion unpainted( QRect( 0, 0, m_width, m_height ) ); const QRegion rgn = unpainted.subtracted( frame->geometry ); for ( const QRect & r : rgn ) { p.fillRect( r, Okular::Settings::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 qreal dpr = qApp->devicePixelRatio(); // calculate overlay geometry and resize pixmap if needed int side = m_width / 16; m_overlayGeometry.setRect( m_width - side - 4, 4, 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 * dpr, side * dpr ); doublePixmap.setDevicePixelRatio( dpr ); doublePixmap.fill( Qt::black ); QPainter pixmapPainter( &doublePixmap ); pixmapPainter.setRenderHints( QPainter::Antialiasing ); // 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( QColor( 0x40 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 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( QColor( 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.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); image.setDevicePixelRatio( dpr ); image = image.convertToFormat( QImage::Format_ARGB32 ); image.setDevicePixelRatio( dpr ); // draw circular shadow using the same technique doublePixmap.fill( Qt::black ); pixmapPainter.begin( &doublePixmap ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 0x80 ) ); pixmapPainter.drawEllipse( 0, 0, side, side ); pixmapPainter.end(); QImage shadow( doublePixmap.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); shadow.setDevicePixelRatio( dpr ); // generate a 2 colors pixmap using mixing shadow (made with highlight color) // and image (made with highlightedText color) QPalette pal = palette(); QColor color = pal.color( QPalette::Active, QPalette::HighlightedText ); int red = color.red(), green = color.green(), blue = color.blue(); color = pal.color( QPalette::Active, QPalette::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 = QPixmap::fromImage( image ); m_lastRenderedOverlay.setDevicePixelRatio( dpr ); // start the autohide timer //repaint( m_overlayGeometry ); // toggle with next line update( m_overlayGeometry ); m_overlayHideTimer->start( 2500 ); #endif } QRect PresentationWidget::routeMouseDrawingEvent( QMouseEvent * e ) { if ( m_frameIndex == -1 ) // Can't draw on the summary page return QRect(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; const Okular::Page * page = m_frames[ m_frameIndex ]->page; AnnotatorEngine::EventType eventType; AnnotatorEngine::Button button; // figure out the event type and button AnnotatorEngine::decodeEvent( e, &eventType, &button ); static bool hasclicked = false; if ( eventType == AnnotatorEngine::Press ) hasclicked = true; QPointF mousePos = e->localPos(); double nX = ( mousePos.x() - (double)geom.left() ) / (double)geom.width(); double nY = ( mousePos.y() - (double)geom.top() ) / (double)geom.height(); QRect ret; bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1; if ( hasclicked && !isInside ) { // Fake a move to the last border pos nX = qBound(0., nX, 1.); nY = qBound(0., nY, 1.); m_drawingEngine->event( AnnotatorEngine::Move, button, nX, nY, geom.width(), geom.height(), page ); // Fake a release in the following lines eventType = AnnotatorEngine::Release; isInside = true; } else if ( !hasclicked && isInside ) { // we're coming from the outside, pretend we started clicking at the closest border if ( nX < ( 1 - nX ) && nX < nY && nX < ( 1 - nY ) ) nX = 0; else if ( nY < ( 1 - nY ) && nY < nX && nY < ( 1 - nX ) ) nY = 0; else if ( ( 1 - nX ) < nX && ( 1 - nX ) < nY && ( 1 - nX ) < ( 1 - nY ) ) nX = 1; else nY = 1; hasclicked = true; eventType = AnnotatorEngine::Press; } if ( hasclicked && isInside ) { ret = m_drawingEngine->event( eventType, button, nX, nY, geom.width(), geom.height(), page ); } if ( eventType == AnnotatorEngine::Release ) { hasclicked = false; } if ( m_drawingEngine->creationCompleted() ) { // add drawing to current page m_frames[ m_frameIndex ]->drawings << m_drawingEngine->endSmoothPath(); // remove the actual drawer and create a new one just after // that - that gives continuous drawing delete m_drawingEngine; m_drawingRect = QRect(); m_drawingEngine = new SmoothPathEngine( m_currentDrawingToolElement ); // schedule repaint update(); } return ret; } void PresentationWidget::startAutoChangeTimer() { double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[ m_frameIndex ]->page->duration() : -1; if ( m_advanceSlides || pageDuration >= 0.0 ) { double secs; if ( pageDuration < 0.0 ) secs = Okular::SettingsCore::slidesAdvanceTime(); else if ( m_advanceSlides ) secs = qMin( pageDuration, Okular::SettingsCore::slidesAdvanceTime() ); else secs = pageDuration; m_nextPageTimer->start( (int)( secs * 1000 ) ); } } void PresentationWidget::recalcGeometry() { QDesktopWidget *desktop = QApplication::desktop(); const int preferenceScreen = Okular::Settings::slidesScreen(); int screen = 0; if ( preferenceScreen == -2 ) { screen = desktop->screenNumber( m_parentWidget ); } else if ( preferenceScreen == -1 ) { screen = desktop->primaryScreen(); } else if ( preferenceScreen >= 0 && preferenceScreen < desktop->numScreens() ) { screen = preferenceScreen; } else { screen = desktop->screenNumber( m_parentWidget ); Okular::Settings::setSlidesScreen( -2 ); } const QRect screenGeom = desktop->screenGeometry( screen ); // qCDebug(OkularUiDebug) << screen << "=>" << screenGeom; m_screen = screen; setGeometry( screenGeom ); } void PresentationWidget::repositionContent() { const QRect ourGeom = geometry(); // tool bar height in pixels, make it large enough to hold the text fields with the page numbers const int toolBarHeight = m_pagesEdit->height() * 1.5; m_topBar->setGeometry( 0, 0, ourGeom.width(), toolBarHeight ); m_topBar->setIconSize( QSize( toolBarHeight * 0.75, toolBarHeight * 0.75 ) ); } void PresentationWidget::requestPixmaps() { PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); // operation will take long: set busy cursor QApplication::setOverrideCursor( QCursor( Qt::BusyCursor ) ); // request the pixmap QLinkedList< Okular::PixmapRequest * > requests; requests.push_back( new Okular::PixmapRequest( this, m_frameIndex, pixW, pixH, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature ) ); // restore cursor QApplication::restoreOverrideCursor(); // ask for next and previous page if not in low memory usage setting if ( Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { int pagesToPreload = 1; // If greedy, preload everything if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = (int)m_document->pages(); Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; for( int j = 1; j <= pagesToPreload; j++ ) { int tailRequest = m_frameIndex + j; if ( tailRequest < (int)m_document->pages() ) { PresentationFrame *nextFrame = m_frames[ tailRequest ]; pixW = nextFrame->geometry.width(); pixH = nextFrame->geometry.height(); if ( !nextFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, tailRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } int headRequest = m_frameIndex - j; if ( headRequest >= 0 ) { PresentationFrame *prevFrame = m_frames[ headRequest ]; pixW = prevFrame->geometry.width(); pixH = prevFrame->geometry.height(); if ( !prevFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, headRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)m_document->pages() ) break; } } m_document->requestPixmaps( requests ); } void PresentationWidget::slotNextPage() { int nextIndex = m_frameIndex + 1; // loop when configured if ( nextIndex == m_frames.count() && Okular::Settings::slidesLoop() ) nextIndex = 0; if ( nextIndex < m_frames.count() ) { // go to next page changePage( nextIndex ); // auto advance to the next page if set startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; 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 startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; 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() { switch( m_currentTransition.type() ) { case Okular::PageTransition::Fade: { QPainter pixmapPainter; m_currentPixmapOpacity += 1.0 / m_transitionSteps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); update(); if( m_currentPixmapOpacity >= 1 ) return; } break; default: { 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(); } } break; } m_transitionTimer->start( m_transitionDelay ); } void PresentationWidget::slotDelayedEvents() { recalcGeometry(); repositionContent(); if ( m_screenSelect ) { m_screenSelect->setCurrentItem( m_screen ); connect( m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen ); } // show widget and take control show(); setWindowState( windowState() | Qt::WindowFullScreen ); connect( QApplication::desktop(), &QDesktopWidget::resized, this, &PresentationWidget::screenResized ); // inform user on how to exit from presentation mode 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(), QStringLiteral("presentationInfo") ); } void PresentationWidget::slotPageChanged() { bool ok = true; int p = m_pagesEdit->text().toInt( &ok ); if ( !ok ) return; changePage( p - 1 ); } void PresentationWidget::slotChangeDrawingToolEngine( const QDomElement &element ) { if ( element.isNull() ) { delete m_drawingEngine; m_drawingEngine = nullptr; m_drawingRect = QRect(); setCursor( Qt::ArrowCursor ); } else { m_drawingEngine = new SmoothPathEngine( element ); setCursor( QCursor( QPixmap( QStringLiteral("pencil") ), Qt::ArrowCursor ) ); m_currentDrawingToolElement = element; } } void PresentationWidget::slotAddDrawingToolActions() { DrawingToolActions *drawingToolActions = qobject_cast(sender()); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } } void PresentationWidget::clearDrawings() { if ( m_frameIndex != -1 ) m_frames[ m_frameIndex ]->drawings.clear(); update(); } void PresentationWidget::screenResized( int screen ) { // we can ignore if a screen was resized in the case the screen is not // where we are on if ( screen != m_screen ) return; setScreen( screen ); } void PresentationWidget::chooseScreen( QAction *act ) { if ( !act || act->data().type() != QVariant::Int ) return; const int newScreen = act->data().toInt(); setScreen( newScreen ); } void PresentationWidget::toggleBlackScreenMode( bool ) { m_inBlackScreenMode = !m_inBlackScreenMode; update(); } void PresentationWidget::setScreen( int newScreen ) { const QRect screenGeom = QApplication::desktop()->screenGeometry( newScreen ); const QSize oldSize = size(); // qCDebug(OkularUiDebug) << newScreen << "=>" << screenGeom; m_screen = newScreen; setGeometry( screenGeom ); applyNewScreenSize( oldSize ); } void PresentationWidget::applyNewScreenSize( const QSize & oldSize ) { repositionContent(); // if by chance the new screen has the same resolution of the previous, // do not invalidate pixmaps and such.. if ( size() == oldSize ) return; m_width = width(); m_height = height(); // update the frames const float screenRatio = (float)m_height / (float)m_width; for ( PresentationFrame * frame : qAsConst( m_frames ) ) { frame->recalcGeometry( m_width, m_height, screenRatio ); } if ( m_frameIndex != -1 ) { // ugliness alarm! const_cast< Okular::Page * >( m_frames[ m_frameIndex ]->page )->deletePixmap( this ); // force the regeneration of the pixmap m_lastRenderedPixmap = QPixmap(); m_blockNotifications = true; requestPixmaps(); m_blockNotifications = false; } if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); } generatePage( true /* no transitions */ ); } void PresentationWidget::inhibitPowerManagement() { #ifdef Q_OS_LINUX QString reason = i18nc( "Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation" ); if (!m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("Inhibit")); message << QCoreApplication::applicationName(); message << reason; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_screenInhibitCookie = reply.value(); qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie; } else { qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error(); } } if (m_sleepInhibitFd != -1) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit") ); message << QStringLiteral("sleep"); message << QCoreApplication::applicationName(); message << reason; message << QStringLiteral("block"); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_sleepInhibitFd = dup(reply.value().fileDescriptor()); } else { qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error(); } } #endif } void PresentationWidget::allowPowerManagement() { #ifdef Q_OS_LINUX if (m_sleepInhibitFd != -1) { ::close(m_sleepInhibitFd); m_sleepInhibitFd = -1; } if (m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit")); message << m_screenInhibitCookie; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); m_screenInhibitCookie = 0; } #endif } void PresentationWidget::showTopBar( bool show ) { if ( show ) { m_topBar->show(); // Don't autohide the mouse cursor if it's over the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, false ); } // Always show a cursor when topBar is visible if ( !m_drawingEngine ) { setCursor( QCursor( Qt::ArrowCursor ) ); } } else { m_topBar->hide(); // Reenable autohide if need be when leaving the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); } // Or hide the cursor again if hidden cursor is enabled else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { // Don't hide the cursor if drawing mode is on if ( !m_drawingEngine ) { setCursor( QCursor( Qt::BlankCursor ) ); } } } // Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls setMouseTracking( true ); } void PresentationWidget::slotFind() { if ( !m_searchBar ) { m_searchBar = new PresentationSearchBar( m_document, this, this ); m_searchBar->forceSnap(); } m_searchBar->focusOnSearchEdit(); m_searchBar->show(); } const Okular::PageTransition PresentationWidget::defaultTransition() const { return defaultTransition( Okular::Settings::slidesTransition() ); } const Okular::PageTransition PresentationWidget::defaultTransition( int type ) const { switch ( type ) { case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Horizontal ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BlindsVertical: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Vertical ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxIn: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxOut: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Dissolve: { return Okular::PageTransition( Okular::PageTransition::Dissolve ); break; } case Okular::Settings::EnumSlidesTransition::GlitterDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRight: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRightDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 315 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Random: { return defaultTransition( KRandom::random() % 18 ); break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeDown: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeRight: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeLeft: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 180 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeUp: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 90 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Fade: { return Okular::PageTransition( Okular::PageTransition::Fade ); break; } case Okular::Settings::EnumSlidesTransition::Replace: default: return Okular::PageTransition( Okular::PageTransition::Replace ); break; } // should not happen, just make gcc happy return Okular::PageTransition(); } /** ONLY the TRANSITIONS GENERATION function from here on **/ void PresentationWidget::initTransition( const Okular::PageTransition *transition ) { // if it's just a 'replace' transition, repaint the screen if ( transition->type() == Okular::PageTransition::Replace ) { update(); return; } const bool isInward = transition->direction() == Okular::PageTransition::Inward; const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal; const float totalTime = transition->duration(); m_transitionRects.clear(); m_currentTransition = *transition; m_currentPagePixmap = m_lastRenderedPixmap; switch( transition->type() ) { // split: horizontal / vertical and inward / outward case Okular::PageTransition::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 Okular::PageTransition::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 Okular::PageTransition::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++ ) { // compute shrunk 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++ ) { // compute shrunk 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 Okular::PageTransition::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 Okular::PageTransition::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++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // 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 Okular::PageTransition::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++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // 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; case Okular::PageTransition::Fade: { enum {FADE_TRANSITION_FPS = 20}; const int steps = totalTime * FADE_TRANSITION_FPS; m_transitionSteps = steps; QPainter pixmapPainter; m_currentPixmapOpacity = (double) 1 / steps; m_transitionDelay = (int)( totalTime * 1000 ) / steps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); pixmapPainter.end(); update(); } break; // implement missing transitions (a binary raster engine needed here) case Okular::PageTransition::Fly: case Okular::PageTransition::Push: case Okular::PageTransition::Cover: case Okular::PageTransition::Uncover: default: update(); return; } // send the first start to the timer m_transitionTimer->start( 0 ); } void PresentationWidget::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); if ( !vw ) return; vw->show(); switch ( action->operation() ) { case Okular::MovieAction::Play: vw->stop(); vw->play(); break; case Okular::MovieAction::Stop: vw->stop(); break; case Okular::MovieAction::Pause: vw->pause(); break; case Okular::MovieAction::Resume: vw->play(); break; }; } void PresentationWidget::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movie ); if ( !vw ) return; if ( action->operation() == Okular::RenditionAction::None ) return; vw->show(); switch ( action->operation() ) { case Okular::RenditionAction::Play: vw->stop(); vw->play(); break; case Okular::RenditionAction::Stop: vw->stop(); break; case Okular::RenditionAction::Pause: vw->pause(); break; case Okular::RenditionAction::Resume: vw->play(); break; default: return; }; } void PresentationWidget::slotTogglePlayPause() { m_advanceSlides = !m_advanceSlides; setPlayPauseIcon(); if ( m_advanceSlides ) { startAutoChangeTimer(); } else { m_nextPageTimer->stop(); } } #include "presentationwidget.moc" diff --git a/ui/thumbnaillist.cpp b/ui/thumbnaillist.cpp index 60157a40e..b4aa89260 100644 --- a/ui/thumbnaillist.cpp +++ b/ui/thumbnaillist.cpp @@ -1,1009 +1,1008 @@ /*************************************************************************** * Copyright (C) 2004-2006 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "thumbnaillist.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include -#include #include // local includes #include "pagepainter.h" #include "core/area.h" #include "core/bookmarkmanager.h" #include "core/document.h" #include "core/generator.h" #include "core/page.h" #include "settings.h" #include "priorities.h" class ThumbnailWidget; class ThumbnailListPrivate : public QWidget { public: ThumbnailListPrivate( ThumbnailList *qq, Okular::Document *document ); ~ThumbnailListPrivate() override; enum ChangePageDirection { Null, Left, Right, Up, Down }; ThumbnailList *q; Okular::Document *m_document; ThumbnailWidget *m_selected; QTimer *m_delayTimer; QPixmap *m_bookmarkOverlay; QVector m_thumbnails; QList m_visibleThumbnails; int m_vectorIndex; // Grabbing variables QPoint m_mouseGrabPos; ThumbnailWidget *m_mouseGrabItem; int m_pageCurrentlyGrabbed; // resize thumbnails to fit the width void viewportResizeEvent( QResizeEvent * ); // called by ThumbnailWidgets to get the overlay bookmark pixmap const QPixmap * getBookmarkOverlay() const; // called by ThumbnailWidgets to send (forward) the mouse move signals ChangePageDirection forwardTrack( const QPoint &, const QSize & ); ThumbnailWidget* itemFor( const QPoint & p ) const; void delayedRequestVisiblePixmaps( int delayMs = 0 ); // SLOTS: // make requests for generating pixmaps for visible thumbnails void slotRequestVisiblePixmaps( int newContentsY = -1 ); // delay timeout: resize overlays and requests pixmaps void slotDelayTimeout(); ThumbnailWidget* getPageByNumber( int page ) const; int getNewPageOffset( int n, ThumbnailListPrivate::ChangePageDirection dir ) const; ThumbnailWidget *getThumbnailbyOffset( int current, int offset ) const; protected: void mousePressEvent( QMouseEvent * e ) override; void mouseReleaseEvent( QMouseEvent * e ) override; void mouseMoveEvent( QMouseEvent * e ) override; void wheelEvent( QWheelEvent * e ) override; void contextMenuEvent( QContextMenuEvent * e ) override; void paintEvent( QPaintEvent * e ) override; }; // ThumbnailWidget represents a single thumbnail in the ThumbnailList class ThumbnailWidget { public: ThumbnailWidget( ThumbnailListPrivate * parent, const Okular::Page * page ); // set internal parameters to fit the page in the given width void resizeFitWidth( int width ); // set thumbnail's selected state void setSelected( bool selected ); // set the visible rect of the current page void setVisibleRect( const Okular::NormalizedRect & rect ); // 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 Okular::Page * page() const { return m_page; } QRect visibleRect() const { return m_visibleRect.geometry( m_pixmapWidth, m_pixmapHeight ); } void paint( QPainter &p, const QRect &clipRect ); static int margin() { return m_margin; } // simulating QWidget QRect rect() const { return m_rect; } int height() const { return m_rect.height(); } int width() const { return m_rect.width(); } QPoint pos() const { return m_rect.topLeft(); } void move( int x, int y ) { m_rect.setTopLeft( QPoint( x, y ) ); } void update() { m_parent->update( m_rect ); } void update( const QRect & rect ) { m_parent->update( rect.translated( m_rect.topLeft() ) ); } private: // the margin around the widget static int const m_margin = 16; ThumbnailListPrivate * m_parent; const Okular::Page * m_page; bool m_selected; int m_pixmapWidth, m_pixmapHeight; int m_labelHeight, m_labelNumber; Okular::NormalizedRect m_visibleRect; QRect m_rect; }; ThumbnailListPrivate::ThumbnailListPrivate( ThumbnailList *qq, Okular::Document *document ) : QWidget(), q( qq ), m_document( document ), m_selected( nullptr ), m_delayTimer( nullptr ), m_bookmarkOverlay( nullptr ), m_vectorIndex( 0 ) { setMouseTracking( true ); m_mouseGrabItem = nullptr; } ThumbnailWidget* ThumbnailListPrivate::getPageByNumber( int page ) const { QVector< ThumbnailWidget * >::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) { if ( (*tIt)->pageNumber() == page ) return (*tIt); } return nullptr; } ThumbnailListPrivate::~ThumbnailListPrivate() { } ThumbnailWidget* ThumbnailListPrivate::itemFor( const QPoint & p ) const { QVector< ThumbnailWidget * >::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) { if ( (*tIt)->rect().contains( p ) ) return (*tIt); } return nullptr; } void ThumbnailListPrivate::paintEvent( QPaintEvent * e ) { QPainter painter( this ); QVector::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) { QRect rect = e->rect().intersected( (*tIt)->rect() ); if ( !rect.isNull() ) { rect.translate( -(*tIt)->pos() ); painter.save(); painter.translate( (*tIt)->pos() ); (*tIt)->paint( painter, rect ); painter.restore(); } } } /** ThumbnailList implementation **/ ThumbnailList::ThumbnailList( QWidget *parent, Okular::Document *document ) : QScrollArea( parent ), d( new ThumbnailListPrivate( this, document ) ) { setObjectName( QStringLiteral( "okular::Thumbnails" ) ); // set scrollbars setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); verticalScrollBar()->setEnabled( false ); setAttribute( Qt::WA_StaticContents ); viewport()->setBackgroundRole( QPalette::Base ); setWidget( d ); // widget setup: can be focused by mouse click (not wheel nor tab) widget()->setFocusPolicy( Qt::ClickFocus ); widget()->show(); widget()->setBackgroundRole( QPalette::Base ); connect( verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)) ); } ThumbnailList::~ThumbnailList() { d->m_document->removeObserver( this ); delete d->m_bookmarkOverlay; } //BEGIN DocumentObserver inherited methods void ThumbnailList::notifySetup( const QVector< Okular::Page * > & pages, int setupFlags ) { // 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 ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) && d->m_selected ) { prevPage = d->m_selected->page()->number(); } else prevPage = d->m_document->viewport().pageNumber; // delete all the Thumbnails QVector::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) delete *tIt; d->m_thumbnails.clear(); d->m_visibleThumbnails.clear(); d->m_selected = nullptr; d->m_mouseGrabItem = nullptr; if ( pages.count() < 1 ) { widget()->resize( 0, 0 ); return; } // show pages containing highlighted text or bookmarked ones //RESTORE THIS int flags = Okular::Settings::filterBookmarks() ? Okular::Page::Bookmark : Okular::Page::Highlight; // if no page matches filter rule, then display all pages QVector< Okular::Page * >::const_iterator pIt = pages.constBegin(), pEnd = pages.constEnd(); 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 const int width = viewport()->width(); int height = 0; int centerHeight = 0; for ( pIt = pages.constBegin(); pIt != pEnd ; ++pIt ) //if ( skipCheck || (*pIt)->attributes() & flags ) if ( skipCheck || (*pIt)->hasHighlights( SW_SEARCH_ID ) ) { ThumbnailWidget * t = new ThumbnailWidget( d, *pIt ); t->move(0, height); // add to the internal queue d->m_thumbnails.push_back( t ); // update total height (asking widget its own height) t->resizeFitWidth( width ); // restoring the previous selected page, if any if ( (*pIt)->number() < prevPage ) { centerHeight = height + t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical)/2; } if ( (*pIt)->number() == prevPage ) { d->m_selected = t; d->m_selected->setSelected( true ); centerHeight = height + t->height() / 2; } height += t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical); } // update scrollview's contents size (sets scrollbars limits) height -= this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical); widget()->resize( width, height ); // enable scrollbar when there's something to scroll verticalScrollBar()->setEnabled( viewport()->height() < height ); verticalScrollBar()->setValue(centerHeight - viewport()->height() / 2); // request for thumbnail generation d->delayedRequestVisiblePixmaps( 200 ); } void ThumbnailList::notifyCurrentPageChanged( int previousPage, int currentPage ) { Q_UNUSED( previousPage ) // skip notifies for the current page (already selected) if ( d->m_selected && d->m_selected->pageNumber() == currentPage ) return; // deselect previous thumbnail if ( d->m_selected ) d->m_selected->setSelected( false ); d->m_selected = nullptr; // select the page with viewport and ensure it's centered in the view d->m_vectorIndex = 0; QVector::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) { if ( (*tIt)->pageNumber() == currentPage ) { d->m_selected = *tIt; d->m_selected->setSelected( true ); if ( Okular::Settings::syncThumbnailsViewport() ) { int yOffset = qMax( viewport()->height() / 4, d->m_selected->height() / 2 ); ensureVisible( 0, d->m_selected->pos().y() + d->m_selected->height()/2, 0, yOffset ); } break; } d->m_vectorIndex++; } } void ThumbnailList::notifyPageChanged( int pageNumber, int changedFlags ) { static const int interestingFlags = DocumentObserver::Pixmap | DocumentObserver::Bookmark | DocumentObserver::Highlights | DocumentObserver::Annotations; // only handle change notifications we are interested in if ( !( changedFlags & interestingFlags ) ) return; // iterate over visible items: if page(pageNumber) is one of them, repaint it QList::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd(); 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 ) d->slotRequestVisiblePixmaps(); } void ThumbnailList::notifyVisibleRectsChanged() { bool found = false; const QVector & visibleRects = d->m_document->visiblePageRects(); QVector::const_iterator tIt = d->m_thumbnails.constBegin(), tEnd = d->m_thumbnails.constEnd(); QVector::const_iterator vEnd = visibleRects.end(); for ( ; tIt != tEnd; ++tIt ) { found = false; QVector::const_iterator vIt = visibleRects.begin(); for ( ; ( vIt != vEnd ) && !found; ++vIt ) { if ( (*tIt)->pageNumber() == (*vIt)->pageNumber ) { (*tIt)->setVisibleRect( (*vIt)->rect ); found = true; } } if ( !found ) { (*tIt)->setVisibleRect( Okular::NormalizedRect() ); } } } bool ThumbnailList::canUnloadPixmap( int pageNumber ) const { // if the thumbnail 'pageNumber' is one of the visible ones, forbid unloading QList::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( (*vIt)->pageNumber() == pageNumber ) return false; // if hidden permit unloading return true; } //END DocumentObserver inherited methods void ThumbnailList::updateWidgets() { // Update all visible widgets QList::const_iterator vIt = d->m_visibleThumbnails.constBegin(), vEnd = d->m_visibleThumbnails.constEnd(); for ( ; vIt != vEnd; ++vIt ) { ThumbnailWidget * t = *vIt; t->update(); } } int ThumbnailListPrivate::getNewPageOffset(int n, ThumbnailListPrivate::ChangePageDirection dir) const { int reason = 1; int facingFirst = 0; //facingFirstCentered cornercase if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing ) reason = 2; else if (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered ) { facingFirst = 1; reason = 2; } else if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary ) reason = 3; if ( dir == ThumbnailListPrivate::Up ) { if ( facingFirst && n == 1 ) return -1; return -reason; } if ( dir == ThumbnailListPrivate::Down ) return reason; if ( dir == ThumbnailListPrivate::Left && reason > 1 && (n + facingFirst) % reason ) return -1; if ( dir == ThumbnailListPrivate::Right && reason > 1 && (n + 1 + facingFirst) % reason ) return 1; return 0; } ThumbnailWidget *ThumbnailListPrivate::getThumbnailbyOffset(int current, int offset) const { QVector::const_iterator it = m_thumbnails.begin(); QVector::const_iterator itE = m_thumbnails.end(); int idx = 0; while ( it != itE ) { if ( (*it)->pageNumber() == current ) break; ++idx; ++it; } if ( it == itE ) return nullptr; idx += offset; if ( idx < 0 || idx >= m_thumbnails.size() ) return nullptr; return m_thumbnails[idx]; } ThumbnailListPrivate::ChangePageDirection ThumbnailListPrivate::forwardTrack(const QPoint &point, const QSize &r ) { Okular::DocumentViewport vp = m_document->viewport(); const double deltaX = (double)point.x() / r.width(), deltaY = (double)point.y() / r.height(); vp.rePos.normalizedX -= deltaX; vp.rePos.normalizedY -= deltaY; if ( vp.rePos.normalizedY > 1.0 ) return ThumbnailListPrivate::Down; if ( vp.rePos.normalizedY < 0.0 ) return ThumbnailListPrivate::Up; if ( vp.rePos.normalizedX > 1.0 ) return ThumbnailListPrivate::Right; if ( vp.rePos.normalizedX < 0.0 ) return ThumbnailListPrivate::Left; vp.rePos.enabled = true; m_document->setViewport( vp ); return ThumbnailListPrivate::Null; } const QPixmap * ThumbnailListPrivate::getBookmarkOverlay() const { return m_bookmarkOverlay; } void ThumbnailList::slotFilterBookmarks( bool filterOn ) { // save state Okular::Settings::setFilterBookmarks( filterOn ); Okular::Settings::self()->save(); // ask for the 'notifySetup' with a little trick (on reinsertion the // document sends the list again) d->m_document->removeObserver( this ); d->m_document->addObserver( this ); } //BEGIN widget events void ThumbnailList::keyPressEvent( QKeyEvent * keyEvent ) { if ( d->m_thumbnails.count() < 1 ) return keyEvent->ignore(); int nextPage = -1; if ( keyEvent->key() == Qt::Key_Up ) { if ( !d->m_selected ) nextPage = 0; else if ( d->m_vectorIndex > 0 ) nextPage = d->m_thumbnails[ d->m_vectorIndex - 1 ]->pageNumber(); } else if ( keyEvent->key() == Qt::Key_Down ) { if ( !d->m_selected ) nextPage = 0; else if ( d->m_vectorIndex < (int)d->m_thumbnails.count() - 1 ) nextPage = d->m_thumbnails[ d->m_vectorIndex + 1 ]->pageNumber(); } else if ( keyEvent->key() == Qt::Key_PageUp ) verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub ); else if ( keyEvent->key() == Qt::Key_PageDown ) verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd ); else if ( keyEvent->key() == Qt::Key_Home ) nextPage = d->m_thumbnails[ 0 ]->pageNumber(); else if ( keyEvent->key() == Qt::Key_End ) nextPage = d->m_thumbnails[ d->m_thumbnails.count() - 1 ]->pageNumber(); if ( nextPage == -1 ) return keyEvent->ignore(); keyEvent->accept(); if ( d->m_selected ) d->m_selected->setSelected( false ); d->m_selected = nullptr; d->m_document->setViewportPage( nextPage ); } bool ThumbnailList::viewportEvent( QEvent * e ) { switch ( e->type() ) { case QEvent::Resize: { d->viewportResizeEvent( (QResizeEvent*)e ); break; } default: ; } return QScrollArea::viewportEvent( e ); } void ThumbnailListPrivate::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 const int newWidth = q->viewport()->width(); int newHeight = 0; QVector::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd(); for ( ; tIt != tEnd; ++tIt ) { ThumbnailWidget *t = *tIt; t->move(0, newHeight); t->resizeFitWidth( newWidth ); newHeight += t->height() + this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical); } // update scrollview's contents size (sets scrollbars limits) newHeight -= this->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical); const int oldHeight = q->widget()->height(); const int oldYCenter = q->verticalScrollBar()->value() + q->viewport()->height() / 2; q->widget()->resize( newWidth, newHeight ); // enable scrollbar when there's something to scroll q->verticalScrollBar()->setEnabled( q->viewport()->height() < newHeight ); // ensure that what was visible before remains visible now q->ensureVisible( 0, int( (qreal)oldYCenter * q->widget()->height() / oldHeight ), 0, q->viewport()->height() / 2 ); } else if ( e->size().height() <= e->oldSize().height() ) return; // invalidate the bookmark overlay if ( m_bookmarkOverlay ) { delete m_bookmarkOverlay; m_bookmarkOverlay = nullptr; } // update Thumbnails since width has changed or height has increased delayedRequestVisiblePixmaps( 500 ); } //END widget events //BEGIN internal SLOTS void ThumbnailListPrivate::slotRequestVisiblePixmaps( int /*newContentsY*/ ) { // if an update is already scheduled or the widget is hidden, don't proceed if ( ( m_delayTimer && m_delayTimer->isActive() ) || q->isHidden() ) return; // scroll from the top to the last visible thumbnail m_visibleThumbnails.clear(); QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QVector::const_iterator tIt = m_thumbnails.constBegin(), tEnd = m_thumbnails.constEnd(); const QRect viewportRect = q->viewport()->rect().translated( q->horizontalScrollBar()->value(), q->verticalScrollBar()->value() ); for ( ; tIt != tEnd; ++tIt ) { ThumbnailWidget * t = *tIt; const QRect thumbRect = t->rect(); if ( !thumbRect.intersects( viewportRect ) ) continue; // add ThumbnailWidget to visible list m_visibleThumbnails.push_back( t ); // if pixmap not present add it to requests if ( !t->page()->hasPixmap( q, t->pixmapWidth(), t->pixmapHeight() ) ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( q, t->pageNumber(), t->pixmapWidth(), t->pixmapHeight(), THUMBNAILS_PRIO, Okular::PixmapRequest::Asynchronous ); requestedPixmaps.push_back( p ); } } // actually request pixmaps if ( !requestedPixmaps.isEmpty() ) m_document->requestPixmaps( requestedPixmaps ); } void ThumbnailListPrivate::slotDelayTimeout() { // resize the bookmark overlay delete m_bookmarkOverlay; const int expectedWidth = q->viewport()->width() / 4; if ( expectedWidth > 10 ) - m_bookmarkOverlay = new QPixmap( DesktopIcon( QStringLiteral("bookmarks"), expectedWidth ) ); + m_bookmarkOverlay = new QPixmap( QIcon::fromTheme( QStringLiteral("bookmarks") ).pixmap( expectedWidth ) ); else m_bookmarkOverlay = nullptr; // request pixmaps slotRequestVisiblePixmaps(); } //END internal SLOTS void ThumbnailListPrivate::delayedRequestVisiblePixmaps( int delayMs ) { if ( !m_delayTimer ) { m_delayTimer = new QTimer( q ); m_delayTimer->setSingleShot( true ); connect( m_delayTimer, SIGNAL(timeout()), q, SLOT(slotDelayTimeout()) ); } m_delayTimer->start( delayMs ); } /** ThumbnailWidget implementation **/ ThumbnailWidget::ThumbnailWidget( ThumbnailListPrivate * parent, const Okular::Page * kp ) : m_parent( parent ), m_page( kp ), m_selected( false ), m_pixmapWidth( 10 ), m_pixmapHeight( 10 ) { m_labelNumber = m_page->number() + 1; m_labelHeight = QFontMetrics( m_parent->font() ).height(); } void ThumbnailWidget::resizeFitWidth( int width ) { m_pixmapWidth = width - m_margin; m_pixmapHeight = qRound( m_page->ratio() * (double)m_pixmapWidth ); m_rect.setSize( QSize( width, heightHint() ) ); } void ThumbnailWidget::setSelected( bool selected ) { // update selected state if ( m_selected != selected ) { m_selected = selected; update(); } } void ThumbnailWidget::setVisibleRect( const Okular::NormalizedRect & rect ) { if ( rect == m_visibleRect ) return; m_visibleRect = rect; update(); } void ThumbnailListPrivate::mousePressEvent( QMouseEvent * e ) { ThumbnailWidget* item = itemFor( e->pos() ); if ( !item ) // mouse on the spacing between items return e->ignore(); const QRect r = item->visibleRect(); const int margin = ThumbnailWidget::margin(); const QPoint p = e->pos() - item->pos(); if ( e->button() != Qt::RightButton && r.contains( p - QPoint( margin / 2, margin / 2 ) ) ) { m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); m_mouseGrabItem = item; m_pageCurrentlyGrabbed = item->pageNumber(); m_mouseGrabItem = item; } else { m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); m_mouseGrabItem = nullptr; } } void ThumbnailListPrivate::mouseReleaseEvent( QMouseEvent * e ) { ThumbnailWidget* item = itemFor( e->pos() ); m_mouseGrabItem = item; if ( !item ) // mouse on the spacing between items return e->ignore(); QRect r = item->visibleRect(); const QPoint p = e->pos() - item->pos(); // jump center of viewport to cursor if it wasn't dragged if ( m_mouseGrabPos.isNull() ) { r = item->visibleRect(); Okular::DocumentViewport vp = Okular::DocumentViewport( item->pageNumber() ); vp.rePos.normalizedX = double(p.x()) / double(item->rect().width()); vp.rePos.normalizedY = double(p.y()) / double(item->rect().height()); vp.rePos.pos = Okular::DocumentViewport::Center; vp.rePos.enabled = true; m_document->setViewport( vp, nullptr, true); } setCursor( Qt::OpenHandCursor ); m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); } void ThumbnailListPrivate::mouseMoveEvent( QMouseEvent * e ) { if ( e->buttons() == Qt::NoButton ) { ThumbnailWidget* item = itemFor( e->pos() ); if ( !item ) // mouse on the spacing between items return e->ignore(); QRect r = item->visibleRect(); const int margin = ThumbnailWidget::margin(); const QPoint p = e->pos() - item->pos(); if ( r.contains( p - QPoint( margin / 2, margin / 2 ) ) ) { setCursor( Qt::OpenHandCursor ); } else { setCursor( Qt::ArrowCursor ); } return e->ignore(); } // no item under the mouse or previously selected if ( !m_mouseGrabItem ) return e->ignore(); const QRect r = m_mouseGrabItem->rect(); if ( !m_mouseGrabPos.isNull() ) { const QPoint mousePos = e->pos(); const QPoint delta = m_mouseGrabPos - mousePos; m_mouseGrabPos = e->pos(); // don't handle the mouse move, forward it to the thumbnail list ThumbnailListPrivate::ChangePageDirection direction; if (( direction = forwardTrack( delta, r.size() )) != ThumbnailListPrivate::Null ) { // Changing the selected page const int offset = getNewPageOffset( m_pageCurrentlyGrabbed, direction ); const ThumbnailWidget *newThumb = getThumbnailbyOffset( m_pageCurrentlyGrabbed, offset ); if ( !newThumb ) return; int newPageOn = newThumb->pageNumber(); if ( newPageOn == m_pageCurrentlyGrabbed || newPageOn < 0 || newPageOn >= (int)m_document->pages() ) { return; } Okular::DocumentViewport vp = m_document->viewport(); const float origNormalX = vp.rePos.normalizedX; const float origNormalY = vp.rePos.normalizedY; vp = Okular::DocumentViewport( newPageOn ); vp.rePos.normalizedX = origNormalX; vp.rePos.normalizedY = origNormalY; if ( direction == ThumbnailListPrivate::Up ) { vp.rePos.normalizedY = 1.0; if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered && !newPageOn) { if ( m_pageCurrentlyGrabbed == 1 ) vp.rePos.normalizedX = origNormalX - 0.5; else vp.rePos.normalizedX = origNormalX + 0.5; if ( vp.rePos.normalizedX < 0.0 ) vp.rePos.normalizedX = 0.0; else if ( vp.rePos.normalizedX > 1.0 ) vp.rePos.normalizedX = 1.0; } } else if ( direction == ThumbnailListPrivate::Down ) { vp.rePos.normalizedY = 0.0; if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered && !m_pageCurrentlyGrabbed) { if ( origNormalX < 0.5 ) { vp = Okular::DocumentViewport( --newPageOn ); vp.rePos.normalizedX = origNormalX + 0.5; } else vp.rePos.normalizedX = origNormalX - 0.5; if ( vp.rePos.normalizedX < 0.0 ) vp.rePos.normalizedX = 0.0; else if ( vp.rePos.normalizedX > 1.0 ) vp.rePos.normalizedX = 1.0; } } else if ( Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single ) { if ( direction == ThumbnailListPrivate::Left ) vp.rePos.normalizedX = 1.0; else vp.rePos.normalizedX = 0.0; } vp.rePos.pos = Okular::DocumentViewport::Center; vp.rePos.enabled = true; m_document->setViewport( vp ); m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); m_pageCurrentlyGrabbed = newPageOn; m_mouseGrabItem = getPageByNumber( m_pageCurrentlyGrabbed ); } // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); QPoint currentMousePos = QCursor::pos(); if ( currentMousePos.y() <= mouseContainer.top() + 4 ) { currentMousePos.setY( mouseContainer.bottom() - 5 ); QCursor::setPos( currentMousePos ); m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); } // wrap mouse from bottom to top else if ( currentMousePos.y() >= mouseContainer.bottom() - 4 ) { currentMousePos.setY( mouseContainer.top() + 5 ); QCursor::setPos( currentMousePos ); m_mouseGrabPos.setX( 0 ); m_mouseGrabPos.setY( 0 ); } } else { setCursor( Qt::ClosedHandCursor ); m_mouseGrabPos = e->pos(); } } void ThumbnailListPrivate::wheelEvent( QWheelEvent * e ) { const ThumbnailWidget* item = itemFor( e->pos() ); if ( !item ) // wheeling on the spacing between items return e->ignore(); const QRect r = item->visibleRect(); const int margin = ThumbnailWidget::margin(); if ( r.contains( e->pos() - QPoint( margin / 2, margin / 2 ) ) && e->orientation() == Qt::Vertical && e->modifiers() == Qt::ControlModifier ) { m_document->setZoom( e->angleDelta().y() ); } else { e->ignore(); } } void ThumbnailListPrivate::contextMenuEvent( QContextMenuEvent * e ) { const ThumbnailWidget* item = itemFor( e->pos() ); if ( item ) { emit q->rightClick( item->page(), e->globalPos() ); } } void ThumbnailWidget::paint( QPainter &p, const QRect &_clipRect ) { const int width = m_pixmapWidth + m_margin; QRect clipRect = _clipRect; const QPalette pal = m_parent->palette(); // draw the bottom label + highlight mark const QColor fillColor = m_selected ? pal.color( QPalette::Active, QPalette::Highlight ) : pal.color( QPalette::Active, QPalette::Base ); p.fillRect( clipRect, fillColor ); p.setPen( m_selected ? pal.color( QPalette::Active, QPalette::HighlightedText ) : pal.color( QPalette::Active, QPalette::Text ) ); p.drawText( 0, m_pixmapHeight + (m_margin - 3), 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 const bool isBookmarked = m_parent->m_document->bookmarkManager()->isBookmarked( pageNumber() ); // draw the inner rect p.setPen( isBookmarked ? QColor( 0xFF8000 ) : Qt::black ); p.drawRect( m_margin/2 - 1, m_margin/2 - 1, m_pixmapWidth + 1, m_pixmapHeight + 1 ); // draw the clear rect p.setPen( isBookmarked ? QColor( 0x804000 ) : pal.color( QPalette::Active, QPalette::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.translate( -m_margin/2, -m_margin/2 ); clipRect = clipRect.intersected( QRect( 0, 0, m_pixmapWidth, m_pixmapHeight ) ); if ( clipRect.isValid() ) { int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; PagePainter::paintPageOnPainter( &p, m_page, m_parent->q, flags, m_pixmapWidth, m_pixmapHeight, clipRect ); } if ( !m_visibleRect.isNull() ) { p.save(); p.setPen( QColor( 255, 255, 0, 200 ) ); p.setBrush( QColor( 0, 0, 0, 100 ) ); p.drawRect( m_visibleRect.geometry( m_pixmapWidth, m_pixmapHeight ).adjusted( 0, 0, -1, -1 ) ); p.restore(); } // draw the bookmark overlay on the top-right corner const QPixmap * bookmarkPixmap = m_parent->getBookmarkOverlay(); if ( isBookmarked && bookmarkPixmap ) { int pixW = bookmarkPixmap->width(), pixH = bookmarkPixmap->height(); clipRect = clipRect.intersected( 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 ) : QToolBar( parent ) { setObjectName( QStringLiteral( "ThumbsControlBar" ) ); // change toolbar appearance setIconSize( QSize( 16, 16 ) ); setMovable( false ); QSizePolicy sp = sizePolicy(); sp.setVerticalPolicy( QSizePolicy::Minimum ); setSizePolicy( sp ); // insert a togglebutton [show only bookmarked pages] //insertSeparator(); QAction * showBoomarkOnlyAction = addAction( QIcon::fromTheme( QStringLiteral("bookmarks") ), i18n( "Show bookmarked pages only" ) ); showBoomarkOnlyAction->setCheckable( true ); connect( showBoomarkOnlyAction, &QAction::toggled, list, &ThumbnailList::slotFilterBookmarks ); showBoomarkOnlyAction->setChecked( Okular::Settings::filterBookmarks() ); //insertLineSeparator(); } #include "moc_thumbnaillist.cpp" /* kate: replace-tabs on; indent-width 4; */