PDF4QT/Pdf4QtLibWidgets/sources/pdfdrawwidget.cpp

640 lines
18 KiB
C++

// Copyright (C) 2018-2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include "pdfcompiler.h"
#include "pdfwidgettool.h"
#include "pdfannotation.h"
#include "pdfwidgetannotation.h"
#include "pdfwidgetformmanager.h"
#include "pdfblpainter.h"
#include <QPainter>
#include <QGridLayout>
#include <QKeyEvent>
#include <QApplication>
#include <QPixmapCache>
#include <QColorSpace>
#include "pdfdbgheap.h"
namespace pdf
{
PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, QWidget* parent) :
QWidget(parent),
m_cmsManager(cmsManager),
m_toolManager(nullptr),
m_annotationManager(nullptr),
m_formManager(nullptr),
m_drawWidget(nullptr),
m_horizontalScrollBar(nullptr),
m_verticalScrollBar(nullptr),
m_proxy(nullptr),
m_rendererEngine(engine)
{
m_drawWidget = new PDFDrawWidget(this, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
QGridLayout* layout = new QGridLayout(this);
layout->setSpacing(0);
layout->addWidget(m_drawWidget->getWidget(), 0, 0);
layout->addWidget(m_horizontalScrollBar, 1, 0);
layout->addWidget(m_verticalScrollBar, 0, 1);
layout->setContentsMargins(QMargins());
setLayout(layout);
setFocusProxy(m_drawWidget->getWidget());
m_proxy = new PDFDrawWidgetProxy(this);
m_proxy->init(this);
m_proxy->updateRenderer(m_rendererEngine);
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged);
}
PDFWidget::~PDFWidget()
{
}
bool PDFWidget::focusNextPrevChild(bool next)
{
if (m_formManager && m_formManager->focusNextPrevFormField(next))
{
return true;
}
return QWidget::focusNextPrevChild(next);
}
void PDFWidget::setDocument(const PDFModifiedDocument& document)
{
m_proxy->setDocument(document);
m_pageRenderingErrors.clear();
m_drawWidget->getWidget()->update();
}
void PDFWidget::updateRenderer(RendererEngine engine)
{
m_rendererEngine = engine;
m_proxy->updateRenderer(m_rendererEngine);
}
void PDFWidget::updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit)
{
m_proxy->getCompiler()->setCacheLimit(compiledPageCacheLimit);
QPixmapCache::setCacheLimit(qMax(thumbnailsCacheLimit, 16384));
m_proxy->getFontCache()->setCacheLimits(fontCacheLimit, instancedFontCacheLimit);
}
int PDFWidget::getPageRenderingErrorCount() const
{
int count = 0;
for (const auto& item : m_pageRenderingErrors)
{
count += item.second.size();
}
return count;
}
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;
Q_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;
}
}
}
}
void PDFWidget::removeInputInterface(IDrawWidgetInputInterface* inputInterface)
{
auto it = std::find(m_inputInterfaces.begin(), m_inputInterfaces.end(), inputInterface);
if (it != m_inputInterfaces.end())
{
m_inputInterfaces.erase(it);
}
}
void PDFWidget::addInputInterface(IDrawWidgetInputInterface* inputInterface)
{
if (inputInterface)
{
m_inputInterfaces.push_back(inputInterface);
std::sort(m_inputInterfaces.begin(), m_inputInterfaces.end(), IDrawWidgetInputInterface::Comparator());
}
}
PDFWidgetFormManager* PDFWidget::getFormManager() const
{
return m_formManager;
}
void PDFWidget::setFormManager(PDFWidgetFormManager* formManager)
{
removeInputInterface(m_formManager);
m_formManager = formManager;
addInputInterface(m_formManager);
}
void PDFWidget::setToolManager(PDFToolManager* toolManager)
{
removeInputInterface(m_toolManager);
m_toolManager = toolManager;
addInputInterface(m_toolManager);
}
void PDFWidget::setAnnotationManager(PDFWidgetAnnotationManager* annotationManager)
{
removeInputInterface(m_annotationManager);
m_annotationManager = annotationManager;
addInputInterface(m_annotationManager);
}
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
BaseClass(parent),
m_widget(widget),
m_mouseOperation(MouseOperation::None)
{
this->setFocusPolicy(Qt::StrongFocus);
this->setMouseTracking(true);
QObject::connect(&m_autoScrollTimer, &QTimer::timeout, this, &PDFDrawWidget::onAutoScrollTimeout);
}
std::vector<PDFInteger> PDFDrawWidget::getCurrentPages() const
{
return this->m_widget->getDrawWidgetProxy()->getPagesIntersectingRect(this->rect());
}
QSize PDFDrawWidget::minimumSizeHint() const
{
return QSize(200, 200);
}
bool PDFDrawWidget::event(QEvent* event)
{
if (event->type() == QEvent::ShortcutOverride)
{
return processEvent<QKeyEvent, &IDrawWidgetInputInterface::shortcutOverrideEvent>(static_cast<QKeyEvent*>(event));
}
return BaseClass::event(event);
}
void PDFDrawWidget::performMouseOperation(QPoint currentMousePosition)
{
switch (m_mouseOperation)
{
case MouseOperation::None:
// No operation performed
break;
case MouseOperation::Translate:
{
QPoint difference = currentMousePosition - m_lastMousePosition;
m_widget->getDrawWidgetProxy()->scrollByPixels(difference);
m_lastMousePosition = currentMousePosition;
break;
}
case MouseOperation::AutoScroll:
{
m_lastMousePosition = currentMousePosition;
onAutoScrollTimeout();
break;
}
default:
Q_ASSERT(false);
}
}
template<typename Event, void (IDrawWidgetInputInterface::* Function)(QWidget*, Event*)>
bool PDFDrawWidget::processEvent(Event* event)
{
QString tooltip;
for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces())
{
(inputInterface->*Function)(this, event);
// Update tooltip
if (tooltip.isEmpty())
{
tooltip = inputInterface->getTooltip();
}
// If event is accepted, then update cursor/tooltip and return
if (event->isAccepted())
{
this->setToolTip(tooltip);
this->updateCursor();
return true;
}
}
this->setToolTip(tooltip);
return false;
}
void PDFDrawWidget::keyPressEvent(QKeyEvent* event)
{
event->ignore();
if (processEvent<QKeyEvent, &IDrawWidgetInputInterface::keyPressEvent>(event))
{
return;
}
// Vertical navigation
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
if (verticalScrollbar->isVisible())
{
constexpr std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation> keyToOperations[] =
{
{ 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);
event->accept();
}
}
}
updateCursor();
}
void PDFDrawWidget::keyReleaseEvent(QKeyEvent* event)
{
event->ignore();
if (processEvent<QKeyEvent, &IDrawWidgetInputInterface::keyReleaseEvent>(event))
{
return;
}
event->accept();
}
void PDFDrawWidget::mousePressEvent(QMouseEvent* event)
{
event->ignore();
if (processEvent<QMouseEvent, &IDrawWidgetInputInterface::mousePressEvent>(event))
{
return;
}
if (event->button() == Qt::LeftButton)
{
m_mouseOperation = MouseOperation::Translate;
m_lastMousePosition = event->pos();
}
if (event->button() == Qt::MiddleButton)
{
if (m_mouseOperation == MouseOperation::AutoScroll)
{
m_mouseOperation = MouseOperation::None;
m_autoScrollTimer.stop();
m_autoScrollLastElapsedTimer.restart();
m_autoScrollOffset = QPointF(0.0, 0.0);
}
else
{
m_mouseOperation = MouseOperation::AutoScroll;
m_autoScrollMousePosition = event->pos();
m_autoScrollLastElapsedTimer.restart();
m_autoScrollOffset = QPointF(0.0, 0.0);
m_lastMousePosition = event->pos();
m_autoScrollTimer.setInterval(10);
m_autoScrollTimer.start();
}
}
updateCursor();
event->accept();
}
void PDFDrawWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
event->ignore();
if (processEvent<QMouseEvent, &IDrawWidgetInputInterface::mouseDoubleClickEvent>(event))
{
return;
}
}
void PDFDrawWidget::mouseReleaseEvent(QMouseEvent* event)
{
event->ignore();
if (processEvent<QMouseEvent, &IDrawWidgetInputInterface::mouseReleaseEvent>(event))
{
return;
}
performMouseOperation(event->pos());
switch (m_mouseOperation)
{
case MouseOperation::None:
break;
case MouseOperation::Translate:
{
if (event->button() != Qt::MiddleButton)
{
m_mouseOperation = MouseOperation::None;
}
break;
}
case MouseOperation::AutoScroll:
break;
default:
Q_ASSERT(false);
break;
}
updateCursor();
event->accept();
}
void PDFDrawWidget::mouseMoveEvent(QMouseEvent* event)
{
event->ignore();
if (processEvent<QMouseEvent, &IDrawWidgetInputInterface::mouseMoveEvent>(event))
{
return;
}
performMouseOperation(event->pos());
updateCursor();
event->accept();
}
void PDFDrawWidget::updateCursor()
{
std::optional<QCursor> cursor;
for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces())
{
cursor = inputInterface->getCursor();
if (cursor)
{
// We have found cursor
break;
}
}
if (!cursor)
{
switch (m_mouseOperation)
{
case MouseOperation::None:
cursor = QCursor(Qt::OpenHandCursor);
break;
case MouseOperation::Translate:
cursor = QCursor(Qt::ClosedHandCursor);
break;
case MouseOperation::AutoScroll:
cursor = QCursor(Qt::SizeAllCursor);
break;
default:
Q_ASSERT(false);
break;
}
}
if (cursor)
{
this->setCursor(*cursor);
}
else
{
this->unsetCursor();
}
}
void PDFDrawWidget::onAutoScrollTimeout()
{
if (m_mouseOperation != MouseOperation::AutoScroll)
{
return;
}
QPointF offset = m_autoScrollMousePosition - m_lastMousePosition;
QPointF scrollOffset = m_autoScrollOffset;
qreal secondsElapsed = qreal(m_autoScrollLastElapsedTimer.nsecsElapsed()) * 0.000000001;
m_autoScrollLastElapsedTimer.restart();
scrollOffset += offset * secondsElapsed;
int scrollX = qFloor(scrollOffset.x());
int scrollY = qFloor(scrollOffset.y());
scrollOffset -= QPointF(scrollX, scrollY);
m_autoScrollOffset = scrollOffset;
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();
proxy->scrollByPixels(QPoint(scrollX, scrollY));
}
void PDFDrawWidget::wheelEvent(QWheelEvent* event)
{
event->ignore();
if (processEvent<QWheelEvent, &IDrawWidgetInputInterface::wheelEvent>(event))
{
return;
}
Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();
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;
proxy->zoom(newZoom);
}
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())
{
const QPoint angleDelta = event->angleDelta();
const bool shiftModifier = keyboardModifiers.testFlag(Qt::ShiftModifier);
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();
}
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);
}
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;
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
const int newValue = verticalScrollbar->value() + (up ? -1 : 1);
if (newValue >= verticalScrollbar->minimum() && newValue <= verticalScrollbar->maximum())
{
verticalScrollbar->setValue(newValue);
proxy->scrollByPixels(QPoint(0, up ? std::numeric_limits<int>::min() : std::numeric_limits<int>::max()));
}
}
}
event->accept();
}
void PDFDrawWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
RendererEngine rendererEngine = getPDFWidget()->getDrawWidgetProxy()->getRendererEngine();
switch (rendererEngine)
{
case RendererEngine::Blend2D_MultiThread:
case RendererEngine::Blend2D_SingleThread:
{
QRect rect = this->rect();
qreal devicePixelRatio = devicePixelRatioF();
m_blend2DframeBuffer.setDevicePixelRatio(devicePixelRatio);
qreal dpmX = logicalDpiX() / 0.0254;
qreal dpmY = logicalDpiY() / 0.0254;
m_blend2DframeBuffer.setDotsPerMeterX(qCeil(dpmX));
m_blend2DframeBuffer.setDotsPerMeterY(qCeil(dpmY));
QSize requiredSize = rect.size() * devicePixelRatio;
if (m_blend2DframeBuffer.size() != requiredSize)
{
m_blend2DframeBuffer = QImage(requiredSize, QImage::Format_ARGB32_Premultiplied);
}
const bool multithreaded = rendererEngine == RendererEngine::Blend2D_MultiThread;
PDFBLPaintDevice blPaintDevice(m_blend2DframeBuffer, multithreaded);
QPainter blPainter;
if (blPainter.begin(&blPaintDevice))
{
getPDFWidget()->getDrawWidgetProxy()->draw(&blPainter, rect);
blPainter.end();
}
QPainter painter(this);
painter.drawImage(QPoint(0, 0), m_blend2DframeBuffer);
break;
}
case RendererEngine::QPainter:
{
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
m_blend2DframeBuffer = QImage();
break;
}
default:
Q_ASSERT(false);
break;
}
}
void PDFDrawWidget::resizeEvent(QResizeEvent* event)
{
BaseClass::resizeEvent(event);
getPDFWidget()->getDrawWidgetProxy()->update();
}
} // namespace pdf