PDF4QT/PdfForQtLib/sources/pdfdrawwidget.cpp

421 lines
14 KiB
C++
Raw Normal View History

2020-01-18 11:38:54 +01:00
// Copyright (C) 2018-2020 Jakub Melka
2019-01-27 17:55:22 +01:00
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
2019-12-15 19:28:25 +01:00
#include "pdfcompiler.h"
2019-01-27 17:55:22 +01:00
#include <QPainter>
#include <QGridLayout>
#include <QKeyEvent>
2019-02-02 18:10:00 +01:00
#include <QApplication>
2019-12-15 19:28:25 +01:00
#include <QPixmapCache>
2019-01-27 17:55:22 +01:00
namespace pdf
{
2019-12-25 17:56:17 +01:00
PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) :
2019-01-27 17:55:22 +01:00
QWidget(parent),
2019-12-25 17:56:17 +01:00
m_cmsManager(cmsManager),
2019-01-27 17:55:22 +01:00
m_drawWidget(nullptr),
m_horizontalScrollBar(nullptr),
m_verticalScrollBar(nullptr),
m_proxy(nullptr)
{
2019-09-08 11:13:59 +02:00
m_drawWidget = createDrawWidget(engine, samplesCount);
2019-01-27 17:55:22 +01:00
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
QGridLayout* layout = new QGridLayout(this);
layout->setSpacing(0);
2019-09-01 18:26:52 +02:00
layout->addWidget(m_drawWidget->getWidget(), 0, 0);
2019-01-27 17:55:22 +01:00
layout->addWidget(m_horizontalScrollBar, 1, 0);
layout->addWidget(m_verticalScrollBar, 0, 1);
layout->setMargin(0);
setLayout(layout);
2019-09-01 18:26:52 +02:00
setFocusProxy(m_drawWidget->getWidget());
2019-01-27 17:55:22 +01:00
m_proxy = new PDFDrawWidgetProxy(this);
m_proxy->init(this);
2019-02-24 19:42:00 +01:00
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
2019-09-01 18:26:52 +02:00
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged);
2019-12-15 16:45:49 +01:00
updateRendererImpl();
2019-01-27 17:55:22 +01:00
}
PDFWidget::~PDFWidget()
{
}
void PDFWidget::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity)
2019-01-27 17:55:22 +01:00
{
m_proxy->setDocument(document, optionalContentActivity);
2019-04-27 14:14:07 +02:00
m_pageRenderingErrors.clear();
2019-01-27 17:55:22 +01:00
}
2019-09-08 11:13:59 +02:00
void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount)
{
PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast<PDFOpenGLDrawWidget*>(m_drawWidget->getWidget());
PDFDrawWidget* softwareDrawWidget = qobject_cast<PDFDrawWidget*>(m_drawWidget->getWidget());
// Do we need to change renderer?
if ((openglDrawWidget && engine != RendererEngine::OpenGL) || (softwareDrawWidget && engine != RendererEngine::Software))
{
QGridLayout* layout = qobject_cast<QGridLayout*>(this->layout());
layout->removeWidget(m_drawWidget->getWidget());
delete m_drawWidget->getWidget();
m_drawWidget = createDrawWidget(engine, samplesCount);
layout->addWidget(m_drawWidget->getWidget(), 0, 0);
setFocusProxy(m_drawWidget->getWidget());
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
}
else if (openglDrawWidget)
{
// Just check the samples count
QSurfaceFormat format = openglDrawWidget->format();
if (format.samples() != samplesCount)
{
format.setSamples(samplesCount);
openglDrawWidget->setFormat(format);
}
}
2019-12-15 16:45:49 +01:00
updateRendererImpl();
2019-09-08 11:13:59 +02:00
}
2019-12-15 19:28:25 +01:00
void PDFWidget::updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit)
{
m_proxy->getCompiler()->setCacheLimit(compiledPageCacheLimit);
QPixmapCache::setCacheLimit(thumbnailsCacheLimit);
m_proxy->getFontCache()->setCacheLimits(fontCacheLimit, instancedFontCacheLimit);
}
2019-02-24 19:42:00 +01:00
int PDFWidget::getPageRenderingErrorCount() const
{
int count = 0;
for (const auto& item : m_pageRenderingErrors)
{
count += item.second.size();
}
return count;
}
2019-12-15 16:45:49 +01:00
void PDFWidget::updateRendererImpl()
{
PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast<PDFOpenGLDrawWidget*>(m_drawWidget->getWidget());
m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat());
}
2019-02-24 19:42:00 +01:00
void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors)
{
// Empty list of error should not be reported!
Q_ASSERT(!errors.empty());
m_pageRenderingErrors[pageIndex] = errors;
emit pageRenderingErrorsChanged(pageIndex, errors.size());
}
void PDFWidget::onPageImageChanged(bool all, const std::vector<PDFInteger>& pages)
{
if (all)
{
m_drawWidget->getWidget()->update();
}
else
{
std::vector<PDFInteger> currentPages = m_drawWidget->getCurrentPages();
Q_ASSERT(std::is_sorted(pages.cbegin(), pages.cend()));
for (PDFInteger pageIndex : currentPages)
{
if (std::binary_search(pages.cbegin(), pages.cend(), pageIndex))
{
m_drawWidget->getWidget()->update();
return;
}
}
}
}
2019-09-08 11:13:59 +02:00
IDrawWidget* PDFWidget::createDrawWidget(RendererEngine rendererEngine, int samplesCount)
{
switch (rendererEngine)
{
case RendererEngine::Software:
return new PDFDrawWidget(this, this);
case RendererEngine::OpenGL:
return new PDFOpenGLDrawWidget(this, samplesCount, this);
default:
Q_ASSERT(false);
break;
}
return nullptr;
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
PDFDrawWidgetBase<BaseWidget>::PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent) :
BaseWidget(parent),
2019-02-02 18:10:00 +01:00
m_widget(widget),
m_mouseOperation(MouseOperation::None)
2019-01-27 17:55:22 +01:00
{
2019-09-01 18:26:52 +02:00
this->setFocusPolicy(Qt::StrongFocus);
2019-01-27 17:55:22 +01:00
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
std::vector<PDFInteger> PDFDrawWidgetBase<BaseWidget>::getCurrentPages() const
2019-02-24 19:42:00 +01:00
{
2019-09-01 18:26:52 +02:00
return this->m_widget->getDrawWidgetProxy()->getPagesIntersectingRect(this->rect());
2019-02-24 19:42:00 +01:00
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
QSize PDFDrawWidgetBase<BaseWidget>::minimumSizeHint() const
2019-01-27 17:55:22 +01:00
{
return QSize(200, 200);
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::performMouseOperation(QPoint currentMousePosition)
2019-01-27 17:55:22 +01:00
{
2019-09-01 18:26:52 +02:00
switch (m_mouseOperation)
{
case MouseOperation::None:
// No operation performed
break;
2019-01-27 17:55:22 +01:00
2019-09-01 18:26:52 +02:00
case MouseOperation::Translate:
{
QPoint difference = currentMousePosition - m_lastMousePosition;
m_widget->getDrawWidgetProxy()->scrollByPixels(difference);
m_lastMousePosition = currentMousePosition;
break;
}
2019-01-27 17:55:22 +01:00
2019-09-01 18:26:52 +02:00
default:
Q_ASSERT(false);
}
2019-01-27 17:55:22 +01:00
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
2019-01-27 17:55:22 +01:00
{
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
2019-12-01 13:02:25 +01:00
event->ignore();
2019-01-27 17:55:22 +01:00
// Vertical navigation
if (verticalScrollbar->isVisible())
{
2019-02-02 18:10:00 +01:00
constexpr std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation> keyToOperations[] =
2019-01-27 17:55:22 +01:00
{
2019-02-02 18:10:00 +01:00
{ QKeySequence::MoveToStartOfDocument, PDFDrawWidgetProxy::NavigateDocumentStart },
{ QKeySequence::MoveToEndOfDocument, PDFDrawWidgetProxy::NavigateDocumentEnd },
{ QKeySequence::MoveToNextPage, PDFDrawWidgetProxy::NavigateNextPage },
{ QKeySequence::MoveToPreviousPage, PDFDrawWidgetProxy::NavigatePreviousPage },
{ QKeySequence::MoveToNextLine, PDFDrawWidgetProxy::NavigateNextStep },
{ QKeySequence::MoveToPreviousLine, PDFDrawWidgetProxy::NavigatePreviousStep }
};
for (const std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation>& keyToOperation : keyToOperations)
{
if (event->matches(keyToOperation.first))
{
m_widget->getDrawWidgetProxy()->performOperation(keyToOperation.second);
2019-12-01 13:02:25 +01:00
event->accept();
2019-02-02 18:10:00 +01:00
}
2019-01-27 17:55:22 +01:00
}
2019-02-02 18:10:00 +01:00
}
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mousePressEvent(QMouseEvent* event)
2019-02-02 18:10:00 +01:00
{
if (event->button() == Qt::LeftButton)
{
m_mouseOperation = MouseOperation::Translate;
m_lastMousePosition = event->pos();
setCursor(Qt::ClosedHandCursor);
}
event->accept();
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
2019-02-02 18:10:00 +01:00
{
performMouseOperation(event->pos());
switch (m_mouseOperation)
{
case MouseOperation::None:
break;
case MouseOperation::Translate:
2019-01-27 17:55:22 +01:00
{
2019-02-02 18:10:00 +01:00
m_mouseOperation = MouseOperation::None;
unsetCursor();
break;
2019-01-27 17:55:22 +01:00
}
2019-02-02 18:10:00 +01:00
default:
Q_ASSERT(false);
}
event->accept();
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseMoveEvent(QMouseEvent* event)
2019-02-02 18:10:00 +01:00
{
performMouseOperation(event->pos());
event->accept();
}
2019-09-01 18:26:52 +02:00
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::wheelEvent(QWheelEvent* event)
2019-02-02 18:10:00 +01:00
{
Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
2019-05-04 18:22:40 +02:00
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();
2019-02-02 18:10:00 +01:00
if (keyboardModifiers.testFlag(Qt::ControlModifier))
{
// Zoom in/Zoom out
const int angleDeltaY = event->angleDelta().y();
const PDFReal zoom = m_widget->getDrawWidgetProxy()->getZoom();
const PDFReal zoomStep = std::pow(PDFDrawWidgetProxy::ZOOM_STEP, static_cast<PDFReal>(angleDeltaY) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep));
const PDFReal newZoom = zoom * zoomStep;
2019-05-04 18:22:40 +02:00
proxy->zoom(newZoom);
2019-02-02 18:10:00 +01:00
}
else
{
// Move Up/Down. Angle is negative, if wheel is scrolled down. First we try to scroll by pixel delta.
// Otherwise we compute scroll using angle.
QPoint scrollByPixels = event->pixelDelta();
if (scrollByPixels.isNull())
2019-01-27 17:55:22 +01:00
{
2019-02-02 18:10:00 +01:00
const QPoint angleDelta = event->angleDelta();
const bool shiftModifier = keyboardModifiers.testFlag(Qt::ShiftModifier);
2019-05-04 18:22:40 +02:00
int stepVertical = 0;
int stepHorizontal = shiftModifier ? m_widget->getHorizontalScrollbar()->pageStep() : m_widget->getHorizontalScrollbar()->singleStep();
if (proxy->isBlockMode())
{
// In block mode, we must calculate pixel offsets differently - scrollbars corresponds to indices of blocks,
// not to the pixels.
QRect boundingBox = proxy->getPagesIntersectingRectBoundingBox(this->rect());
if (boundingBox.isEmpty())
{
// This occurs, when we have not opened a document
boundingBox = this->rect();
}
stepVertical = shiftModifier ? boundingBox.height() : boundingBox.height() / 10;
}
else
{
stepVertical = shiftModifier ? m_widget->getVerticalScrollbar()->pageStep() : m_widget->getVerticalScrollbar()->singleStep();
}
2019-02-02 18:10:00 +01:00
const int scrollVertical = stepVertical * static_cast<PDFReal>(angleDelta.y()) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep);
const int scrollHorizontal = stepHorizontal * static_cast<PDFReal>(angleDelta.x()) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep);
scrollByPixels = QPoint(scrollHorizontal, scrollVertical);
2019-01-27 17:55:22 +01:00
}
2019-02-02 18:10:00 +01:00
2019-05-04 18:22:40 +02:00
QPoint offset = proxy->scrollByPixels(scrollByPixels);
if (offset.y() == 0 && scrollByPixels.y() != 0 && proxy->isBlockMode())
{
// We must move to another block (we are in block mode)
bool up = scrollByPixels.y() > 0;
m_widget->getVerticalScrollbar()->setValue(m_widget->getVerticalScrollbar()->value() + (up ? -1 : 1));
proxy->scrollByPixels(QPoint(0, up ? std::numeric_limits<int>::min() : std::numeric_limits<int>::max()));
}
2019-01-27 17:55:22 +01:00
}
event->accept();
}
2019-09-08 11:13:59 +02:00
PDFOpenGLDrawWidget::PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent) :
2019-09-01 18:26:52 +02:00
BaseClass(widget, parent)
2019-02-02 18:10:00 +01:00
{
2019-09-01 18:26:52 +02:00
QSurfaceFormat format = this->format();
format.setProfile(QSurfaceFormat::CoreProfile);
2019-09-08 11:13:59 +02:00
format.setSamples(samplesCount);
2019-09-01 18:26:52 +02:00
format.setColorSpace(QSurfaceFormat::sRGBColorSpace);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
setFormat(format);
}
2019-02-02 18:10:00 +01:00
2019-09-01 18:26:52 +02:00
PDFOpenGLDrawWidget::~PDFOpenGLDrawWidget()
{
2019-02-02 18:10:00 +01:00
}
2019-09-01 18:26:52 +02:00
void PDFOpenGLDrawWidget::resizeGL(int w, int h)
{
QOpenGLWidget::resizeGL(w, h);
getPDFWidget()->getDrawWidgetProxy()->update();
}
void PDFOpenGLDrawWidget::initializeGL()
{
QOpenGLWidget::initializeGL();
}
void PDFOpenGLDrawWidget::paintGL()
{
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
}
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
BaseClass(widget, parent)
{
}
PDFDrawWidget::~PDFDrawWidget()
{
}
void PDFDrawWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
}
void PDFDrawWidget::resizeEvent(QResizeEvent* event)
{
BaseClass::resizeEvent(event);
getPDFWidget()->getDrawWidgetProxy()->update();
}
template class PDFDrawWidgetBase<QOpenGLWidget>;
template class PDFDrawWidgetBase<QWidget>;
2019-01-27 17:55:22 +01:00
} // namespace pdf