qpdfviewsb/sources/presentationview.cpp

641 lines
14 KiB
C++

/*
Copyright 2012-2013 Adam Reichold
This file is part of qpdfview.
qpdfview 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.
qpdfview 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
*/
#include "presentationview.h"
#include <QKeyEvent>
#include <QShortcut>
#include <QTimer>
#include "settings.h"
#include "model.h"
#include "pageitem.h"
#include "documentview.h"
namespace
{
using namespace qpdfview;
inline void adjustScaleFactor(RenderParam& renderParam, qreal scaleFactor)
{
if(!qFuzzyCompare(renderParam.scaleFactor(), scaleFactor))
{
renderParam.setScaleFactor(scaleFactor);
}
}
} // anonymous
namespace qpdfview
{
Settings* PresentationView::s_settings = 0;
PresentationView::PresentationView(const QVector< Model::Page* >& pages, QWidget* parent) : QGraphicsView(parent),
m_prefetchTimer(0),
m_pages(pages),
m_currentPage(1),
m_past(),
m_future(),
m_scaleMode(FitToPageSizeMode),
m_scaleFactor(1.0),
m_rotation(RotateBy0),
m_renderFlags(0),
m_pageItems()
{
if(s_settings == 0)
{
s_settings = Settings::instance();
}
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setWindowState(windowState() | Qt::WindowFullScreen);
setFrameShape(QFrame::NoFrame);
setAcceptDrops(false);
setDragMode(QGraphicsView::ScrollHandDrag);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setScene(new QGraphicsScene(this));
preparePages();
prepareBackground();
// prefetch
m_prefetchTimer = new QTimer(this);
m_prefetchTimer->setInterval(250);
m_prefetchTimer->setSingleShot(true);
connect(this, SIGNAL(currentPageChanged(int)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(scaleModeChanged(ScaleMode)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(scaleFactorChanged(qreal)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(rotationChanged(Rotation)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(renderFlagsChanged(qpdfview::RenderFlags)), m_prefetchTimer, SLOT(start()));
connect(m_prefetchTimer, SIGNAL(timeout()), SLOT(on_prefetch_timeout()));
if(s_settings->documentView().prefetch())
{
m_prefetchTimer->blockSignals(false);
m_prefetchTimer->start();
}
else
{
m_prefetchTimer->blockSignals(true);
m_prefetchTimer->stop();
}
prepareScene();
prepareView();
}
PresentationView::~PresentationView()
{
qDeleteAll(m_pageItems);
}
void PresentationView::setScaleMode(ScaleMode scaleMode)
{
if(m_scaleMode != scaleMode && scaleMode >= 0 && scaleMode < NumberOfScaleModes)
{
m_scaleMode = scaleMode;
prepareScene();
prepareView();
emit scaleModeChanged(m_scaleMode);
}
}
void PresentationView::setScaleFactor(qreal scaleFactor)
{
if(!qFuzzyCompare(m_scaleFactor, scaleFactor)
&& scaleFactor >= s_settings->documentView().minimumScaleFactor()
&& scaleFactor <= s_settings->documentView().maximumScaleFactor())
{
m_scaleFactor = scaleFactor;
if(m_scaleMode == ScaleFactorMode)
{
prepareScene();
prepareView();
}
emit scaleFactorChanged(m_scaleFactor);
}
}
void PresentationView::setRotation(Rotation rotation)
{
if(m_rotation != rotation)
{
m_rotation = rotation;
prepareScene();
prepareView();
emit rotationChanged(m_rotation);
}
}
void PresentationView::setRenderFlags(qpdfview::RenderFlags renderFlags)
{
if(m_renderFlags != renderFlags)
{
const qpdfview::RenderFlags changedFlags = m_renderFlags ^ renderFlags;
m_renderFlags = renderFlags;
prepareScene();
prepareView();
if(changedFlags.testFlag(InvertColors))
{
prepareBackground();
}
emit renderFlagsChanged(m_renderFlags);
}
}
void PresentationView::show()
{
QWidget::show();
prepareView();
}
void PresentationView::previousPage()
{
jumpToPage(m_currentPage - 1);
}
void PresentationView::nextPage()
{
jumpToPage(m_currentPage + 1);
}
void PresentationView::firstPage()
{
jumpToPage(1);
}
void PresentationView::lastPage()
{
jumpToPage(m_pages.count());
}
void PresentationView::jumpToPage(int page, bool trackChange)
{
if(m_currentPage != page && page >= 1 && page <= m_pages.count())
{
if(trackChange)
{
m_past.append(m_currentPage);
}
m_currentPage = page;
prepareView();
emit currentPageChanged(m_currentPage, trackChange);
}
}
void PresentationView::jumpBackward()
{
if(!m_past.isEmpty())
{
m_future.prepend(m_currentPage);
jumpToPage(m_past.takeLast(), false);
}
}
void PresentationView::jumpForward()
{
if(!m_future.isEmpty())
{
m_past.append(m_currentPage);
jumpToPage(m_future.takeFirst(), false);
}
}
void PresentationView::zoomIn()
{
if(scaleMode() != ScaleFactorMode)
{
const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
setScaleFactor(qMin(currentScaleFactor * s_settings->documentView().zoomFactor(),
s_settings->documentView().maximumScaleFactor()));
setScaleMode(ScaleFactorMode);
}
else
{
setScaleFactor(qMin(m_scaleFactor * s_settings->documentView().zoomFactor(),
s_settings->documentView().maximumScaleFactor()));
}
}
void PresentationView::zoomOut()
{
if(scaleMode() != ScaleFactorMode)
{
const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
setScaleFactor(qMax(currentScaleFactor / s_settings->documentView().zoomFactor(),
s_settings->documentView().minimumScaleFactor()));
setScaleMode(ScaleFactorMode);
}
else
{
setScaleFactor(qMax(m_scaleFactor / s_settings->documentView().zoomFactor(),
s_settings->documentView().minimumScaleFactor()));
}
}
void PresentationView::originalSize()
{
setScaleFactor(1.0);
setScaleMode(ScaleFactorMode);
}
void PresentationView::rotateLeft()
{
switch(m_rotation)
{
default:
case RotateBy0:
setRotation(RotateBy270);
break;
case RotateBy90:
setRotation(RotateBy0);
break;
case RotateBy180:
setRotation(RotateBy90);
break;
case RotateBy270:
setRotation(RotateBy180);
break;
}
}
void PresentationView::rotateRight()
{
switch(m_rotation)
{
default:
case RotateBy0:
setRotation(RotateBy90);
break;
case RotateBy90:
setRotation(RotateBy180);
break;
case RotateBy180:
setRotation(RotateBy270);
break;
case RotateBy270:
setRotation(RotateBy0);
break;
}
}
void PresentationView::on_prefetch_timeout()
{
int fromPage = m_currentPage, toPage = m_currentPage;
fromPage -= s_settings->documentView().prefetchDistance() / 2;
toPage += s_settings->documentView().prefetchDistance();
fromPage = qMax(fromPage, 1);
toPage = qMin(toPage, m_pages.count());
const int maxCost = toPage - fromPage + 1;
int cost = 0;
for(int index = m_currentPage - 1; index <= toPage - 1; ++index)
{
cost += m_pageItems.at(index)->startRender(true);
if(cost >= maxCost)
{
return;
}
}
for(int index = m_currentPage - 1; index >= fromPage - 1; --index)
{
cost += m_pageItems.at(index)->startRender(true);
if(cost >= maxCost)
{
return;
}
}
}
void PresentationView::on_pages_cropRectChanged()
{
prepareScene();
prepareView();
}
void PresentationView::on_pages_linkClicked(bool newTab, int page, qreal left, qreal top)
{
Q_UNUSED(newTab);
Q_UNUSED(left);
Q_UNUSED(top);
page = qMax(page, 1);
page = qMin(page, m_pages.count());
jumpToPage(page, true);
}
void PresentationView::resizeEvent(QResizeEvent* event)
{
QGraphicsView::resizeEvent(event);
prepareScene();
prepareView();
}
void PresentationView::keyPressEvent(QKeyEvent* event)
{
switch(event->modifiers() + event->key())
{
case Qt::Key_PageUp:
case Qt::Key_Up:
case Qt::Key_Left:
case Qt::Key_Backspace:
previousPage();
event->accept();
return;
case Qt::Key_PageDown:
case Qt::Key_Down:
case Qt::Key_Right:
case Qt::Key_Space:
nextPage();
event->accept();
return;
case Qt::Key_Home:
firstPage();
event->accept();
return;
case Qt::Key_End:
lastPage();
event->accept();
return;
case Qt::CTRL + Qt::Key_Return:
jumpBackward();
event->accept();
return;
case Qt::CTRL + Qt::SHIFT + Qt::Key_Return:
jumpForward();
event->accept();
return;
case Qt::CTRL + Qt::Key_0:
originalSize();
event->accept();
return;
case Qt::CTRL + Qt::Key_9:
setScaleMode(FitToPageWidthMode);
event->accept();
return;
case Qt::CTRL + Qt::Key_8:
setScaleMode(FitToPageSizeMode);
event->accept();
return;
case Qt::CTRL + Qt::Key_Up:
zoomIn();
event->accept();
return;
case Qt::CTRL + Qt::Key_Down:
zoomOut();
event->accept();
return;
case Qt::CTRL + Qt::Key_Left:
rotateLeft();
event->accept();
return;
case Qt::CTRL + Qt::Key_Right:
rotateRight();
event->accept();
return;
case Qt::CTRL + Qt::Key_I:
setRenderFlags(renderFlags() ^ InvertColors);
event->accept();
return;
case Qt::CTRL + Qt::Key_U:
setRenderFlags(renderFlags() ^ ConvertToGrayscale);
event->accept();
return;
case Qt::CTRL + Qt::SHIFT + Qt::Key_U:
setRenderFlags(renderFlags() ^ TrimMargins);
event->accept();
return;
case Qt::Key_F12:
case Qt::Key_Escape:
close();
event->accept();
return;
}
QWidget::keyPressEvent(event);
}
void PresentationView::wheelEvent(QWheelEvent* event)
{
if(event->modifiers() == s_settings->documentView().zoomModifiers())
{
if(event->delta() > 0)
{
zoomIn();
}
else
{
zoomOut();
}
event->accept();
return;
}
else if(event->modifiers() == s_settings->documentView().rotateModifiers())
{
if(event->delta() > 0)
{
rotateLeft();
}
else
{
rotateRight();
}
event->accept();
return;
}
else if(event->modifiers() == Qt::NoModifier)
{
if(event->delta() > 0 && m_currentPage != 1)
{
previousPage();
event->accept();
return;
}
else if(event->delta() < 0 && m_currentPage != m_pages.count())
{
nextPage();
event->accept();
return;
}
}
QGraphicsView::wheelEvent(event);
}
void PresentationView::preparePages()
{
for(int index = 0; index < m_pages.count(); ++index)
{
PageItem* page = new PageItem(m_pages.at(index), index, PageItem::PresentationMode);
scene()->addItem(page);
m_pageItems.append(page);
connect(page, SIGNAL(cropRectChanged()), SLOT(on_pages_cropRectChanged()));
connect(page, SIGNAL(linkClicked(bool,int,qreal,qreal)), SLOT(on_pages_linkClicked(bool,int,qreal,qreal)));
}
}
void PresentationView::prepareBackground()
{
QColor backgroundColor = s_settings->presentationView().backgroundColor();
if(!backgroundColor.isValid())
{
backgroundColor = s_settings->pageItem().paperColor();
}
if(m_renderFlags.testFlag(InvertColors))
{
backgroundColor.setRgb(~backgroundColor.rgb());
}
scene()->setBackgroundBrush(QBrush(backgroundColor));
}
void PresentationView::prepareScene()
{
RenderParam renderParam(logicalDpiX(), logicalDpiY(), 1.0,
scaleFactor(), rotation(), renderFlags());
#if QT_VERSION >= QT_VERSION_CHECK(5,1,0)
if(s_settings->pageItem().useDevicePixelRatio())
{
#if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
renderParam.setDevicePixelRatio(devicePixelRatioF());
#else
renderParam.setDevicePixelRatio(devicePixelRatio());
#endif // QT_VERSION
}
#endif // QT_VERSION
const qreal visibleWidth = viewport()->width();
const qreal visibleHeight = viewport()->height();
foreach(PageItem* page, m_pageItems)
{
const QSizeF displayedSize = page->displayedSize(renderParam);
if(m_scaleMode == FitToPageWidthMode)
{
adjustScaleFactor(renderParam, visibleWidth / displayedSize.width());
}
else if(m_scaleMode == FitToPageSizeMode)
{
adjustScaleFactor(renderParam, qMin(visibleWidth / displayedSize.width(), visibleHeight / displayedSize.height()));
}
page->setRenderParam(renderParam);
}
}
void PresentationView::prepareView()
{
for(int index = 0; index < m_pageItems.count(); ++index)
{
PageItem* page = m_pageItems.at(index);
if(index == m_currentPage - 1)
{
page->setVisible(true);
setSceneRect(page->boundingRect().translated(page->pos()));
}
else
{
page->setVisible(false);
page->cancelRender();
}
}
viewport()->update();
}
} // qpdfview