PDF4QT/Pdf4QtLib/sources/pdfpagecontentelements.cpp

1597 lines
43 KiB
C++
Raw Normal View History

2022-02-11 19:15:57 +01:00
// Copyright (C) 2022 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 "pdfpagecontentelements.h"
#include "pdfpainterutils.h"
2022-03-04 19:32:33 +01:00
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
2022-03-05 19:24:35 +01:00
#include "pdfwidgetutils.h"
2022-02-11 19:15:57 +01:00
#include <QPainter>
#include <QKeyEvent>
#include <QMouseEvent>
2022-02-19 19:30:52 +01:00
#include <QSvgRenderer>
2022-03-04 19:32:33 +01:00
#include <QApplication>
2022-02-11 19:15:57 +01:00
namespace pdf
{
PDFInteger PDFPageContentElement::getPageIndex() const
{
return m_pageIndex;
}
void PDFPageContentElement::setPageIndex(PDFInteger newPageIndex)
{
m_pageIndex = newPageIndex;
}
2022-03-04 19:32:33 +01:00
PDFInteger PDFPageContentElement::getElementId() const
{
return m_elementId;
}
void PDFPageContentElement::setElementId(PDFInteger newElementId)
{
m_elementId = newElementId;
}
Qt::CursorShape PDFPageContentElement::getCursorShapeForManipulationMode(uint mode)
{
switch (mode)
{
case None:
case Pt1:
case Pt2:
case Translate:
return Qt::ArrowCursor;
case Top:
case Bottom:
return Qt::SizeVerCursor;
case Left:
case Right:
return Qt::SizeHorCursor;
case TopLeft:
case BottomRight:
return Qt::SizeBDiagCursor;
case TopRight:
case BottomLeft:
return Qt::SizeFDiagCursor;
default:
Q_ASSERT(false);
break;
}
return Qt::ArrowCursor;
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentElement::getRectangleManipulationMode(const QRectF& rectangle,
const QPointF& point,
PDFReal snapPointDistanceThreshold) const
{
if ((rectangle.topLeft() - point).manhattanLength() < snapPointDistanceThreshold)
{
return TopLeft;
}
if ((rectangle.topRight() - point).manhattanLength() < snapPointDistanceThreshold)
{
return TopRight;
}
if ((rectangle.bottomLeft() - point).manhattanLength() < snapPointDistanceThreshold)
{
return BottomLeft;
}
if ((rectangle.bottomRight() - point).manhattanLength() < snapPointDistanceThreshold)
{
return BottomRight;
}
if (rectangle.left() <= point.x() &&
point.x() <= rectangle.right() &&
(qAbs(rectangle.top() - point.y()) < snapPointDistanceThreshold))
{
return Top;
}
if (rectangle.left() <= point.x() &&
point.x() <= rectangle.right() &&
(qAbs(rectangle.bottom() - point.y()) < snapPointDistanceThreshold))
{
return Bottom;
}
if (rectangle.top() <= point.y() &&
point.y() <= rectangle.bottom() &&
(qAbs(rectangle.left() - point.x()) < snapPointDistanceThreshold))
2022-03-05 19:24:35 +01:00
{
return Left;
}
if (rectangle.top() <= point.y() &&
point.y() <= rectangle.bottom() &&
(qAbs(rectangle.right() - point.x()) < snapPointDistanceThreshold))
2022-03-05 19:24:35 +01:00
{
return Right;
}
if (rectangle.contains(point))
{
return Translate;
}
return None;
}
void PDFPageContentElement::performRectangleManipulation(QRectF& rectangle,
uint mode,
const QPointF& offset)
{
switch (mode)
{
case None:
break;
case Translate:
rectangle.translate(offset);
break;
case Top:
rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y()));
break;
case Left:
rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x()));
break;
case Right:
rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x()));
break;
case Bottom:
rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y()));
break;
case TopLeft:
rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y()));
rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x()));
break;
case TopRight:
rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y()));
rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x()));
break;
case BottomLeft:
rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y()));
rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x()));
break;
case BottomRight:
rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y()));
rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x()));
break;
default:
Q_ASSERT(false);
break;
}
}
2022-02-11 19:15:57 +01:00
const QPen& PDFPageContentStyledElement::getPen() const
{
return m_pen;
}
void PDFPageContentStyledElement::setPen(const QPen& newPen)
{
m_pen = newPen;
}
const QBrush& PDFPageContentStyledElement::getBrush() const
{
return m_brush;
}
void PDFPageContentStyledElement::setBrush(const QBrush& newBrush)
{
m_brush = newBrush;
}
PDFPageContentElementRectangle* PDFPageContentElementRectangle::clone() const
{
PDFPageContentElementRectangle* copy = new PDFPageContentElementRectangle();
2022-03-04 19:32:33 +01:00
copy->setElementId(getElementId());
2022-02-11 19:15:57 +01:00
copy->setPageIndex(getPageIndex());
copy->setPen(getPen());
copy->setBrush(getBrush());
copy->setRectangle(getRectangle());
copy->setRounded(isRounded());
return copy;
}
bool PDFPageContentElementRectangle::isRounded() const
{
return m_rounded;
}
void PDFPageContentElementRectangle::setRounded(bool newRounded)
{
m_rounded = newRounded;
}
const QRectF& PDFPageContentElementRectangle::getRectangle() const
{
return m_rectangle;
}
void PDFPageContentElementRectangle::setRectangle(const QRectF& newRectangle)
{
m_rectangle = newRectangle;
}
void PDFPageContentElementRectangle::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (pageIndex != getPageIndex())
{
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setPen(getPen());
painter->setBrush(getBrush());
painter->setRenderHint(QPainter::Antialiasing);
QRectF rect = getRectangle();
if (isRounded())
{
qreal radius = qMin(rect.width(), rect.height()) * 0.25;
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
}
else
{
painter->drawRect(rect);
}
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentElementRectangle::getManipulationMode(const QPointF& point,
PDFReal snapPointDistanceThreshold) const
{
return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold);
}
void PDFPageContentElementRectangle::performManipulation(uint mode, const QPointF& offset)
{
performRectangleManipulation(m_rectangle, mode, offset);
}
2022-03-08 19:36:52 +01:00
QRectF PDFPageContentElementRectangle::getBoundingBox() const
{
return getRectangle();
}
2022-02-11 19:15:57 +01:00
PDFPageContentScene::PDFPageContentScene(QObject* parent) :
2022-02-24 20:25:18 +01:00
QObject(parent),
2022-03-04 19:32:33 +01:00
m_firstFreeId(1),
m_isActive(false),
2022-03-05 19:24:35 +01:00
m_widget(nullptr),
2022-03-04 19:32:33 +01:00
m_manipulator(this, nullptr)
2022-02-11 19:15:57 +01:00
{
2022-03-08 19:36:52 +01:00
connect(&m_manipulator, &PDFPageContentElementManipulator::selectionChanged, this, &PDFPageContentScene::onSelectionChanged);
2022-02-11 19:15:57 +01:00
}
PDFPageContentScene::~PDFPageContentScene()
{
}
void PDFPageContentScene::addElement(PDFPageContentElement* element)
{
2022-03-04 19:32:33 +01:00
element->setElementId(m_firstFreeId++);
2022-02-11 19:15:57 +01:00
m_elements.emplace_back(element);
emit sceneChanged(false);
2022-02-11 19:15:57 +01:00
}
2022-03-05 19:24:35 +01:00
void PDFPageContentScene::replaceElement(PDFPageContentElement* element)
{
std::unique_ptr<PDFPageContentElement> elementPtr(element);
for (size_t i = 0; i < m_elements.size(); ++i)
{
if (m_elements[i]->getElementId() == element->getElementId())
{
m_elements[i] = std::move(elementPtr);
emit sceneChanged(false);
2022-03-05 19:24:35 +01:00
break;
}
}
}
2022-03-04 19:32:33 +01:00
PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const
{
auto it = std::find_if(m_elements.cbegin(), m_elements.cend(), [id](const auto& element) { return element->getElementId() == id; });
if (it != m_elements.cend())
{
return it->get();
}
return nullptr;
}
2022-02-11 19:15:57 +01:00
void PDFPageContentScene::clear()
{
if (!m_elements.empty())
{
m_elements.clear();
emit sceneChanged(false);
2022-02-11 19:15:57 +01:00
}
}
void PDFPageContentScene::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
2022-03-09 20:42:05 +01:00
constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete,
QKeySequence::SelectAll,
QKeySequence::Deselect,
QKeySequence::Cancel };
if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; }))
{
event->accept();
return;
}
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
event->ignore();
2022-03-09 20:42:05 +01:00
if (event == QKeySequence::Delete)
{
if (!m_manipulator.isSelectionEmpty())
{
m_manipulator.performDeleteSelection();
event->accept();
}
}
else if (event == QKeySequence::SelectAll)
{
if (!isEmpty())
{
m_manipulator.selectAll();
event->accept();
}
}
else if (event == QKeySequence::Deselect)
{
if (!m_manipulator.isSelectionEmpty())
{
m_manipulator.deselectAll();
event->accept();
}
}
else if (event == QKeySequence::Cancel)
{
if (m_manipulator.isManipulationInProgress())
{
m_manipulator.cancelManipulation();
m_manipulator.deselectAll();
event->accept();
}
}
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
event->ignore();
}
void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
2022-03-04 19:32:33 +01:00
if (!isActive())
{
return;
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
if (info.isValid() || isMouseGrabbed())
{
// We to handle selecting/deselecting the active
// item. After that, accept the item.
if (info.isValid() && event->button() == Qt::LeftButton)
{
info.widgetMouseStartPos = event->pos();
info.timer.start();
if (!m_manipulator.isManipulationInProgress())
{
Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
2022-03-05 19:24:35 +01:00
const bool isCtrl = keyboardModifiers.testFlag(Qt::ControlModifier);
const bool isShift = keyboardModifiers.testFlag(Qt::ShiftModifier);
2022-03-04 19:32:33 +01:00
if (isCtrl && !isShift)
{
m_manipulator.select(info.hoveredElementIds);
}
else if (!isCtrl && isShift)
{
m_manipulator.deselect(info.hoveredElementIds);
}
else
{
m_manipulator.selectNew(info.hoveredElementIds);
}
}
event->accept();
}
grabMouse(info, event);
}
2022-03-05 19:24:35 +01:00
else if (event->button() == Qt::LeftButton)
{
m_manipulator.deselectAll();
}
updateMouseCursor(info, getSnapPointDistanceThreshold());
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
2022-03-04 19:32:33 +01:00
if (!isActive())
{
return;
}
// If mouse is grabbed, then event is accepted always (because
// we get Press event, when we grabbed the mouse, then we will
// wait for corresponding release event while all mouse move events
// will be accepted, even if editor doesn't accept them.
if (isMouseGrabbed())
{
event->accept();
}
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
2022-03-04 19:32:33 +01:00
if (!isActive())
{
return;
}
if (isMouseGrabbed())
{
if (event->button() == Qt::LeftButton)
{
event->accept();
2022-03-05 19:24:35 +01:00
if (m_manipulator.isManipulationInProgress())
{
QPointF pagePoint;
const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint);
m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos();
bool isCopyCreated = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
m_manipulator.finishManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, isCopyCreated);
}
2022-03-04 19:32:33 +01:00
}
2022-03-05 19:24:35 +01:00
ungrabMouse(getMouseEventInfo(widget, event->pos()), event);
2022-03-04 19:32:33 +01:00
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
updateMouseCursor(info, getSnapPointDistanceThreshold());
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
2022-03-05 19:24:35 +01:00
Q_UNUSED(widget);
2022-03-04 19:32:33 +01:00
if (!isActive())
{
return;
}
2022-03-05 19:24:35 +01:00
const PDFReal threshold = getSnapPointDistanceThreshold();
2022-03-04 19:32:33 +01:00
2022-03-05 19:24:35 +01:00
QPointF pagePoint;
const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (m_mouseGrabInfo.info.isValid() &&
event->buttons().testFlag(Qt::LeftButton) &&
m_mouseGrabInfo.info.pageIndex == pageIndex &&
m_manipulator.isManipulationAllowed(pageIndex))
{
// Jakub Melka: Start grab?
m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos();
2022-03-04 19:32:33 +01:00
2022-03-05 19:24:35 +01:00
if (!m_manipulator.isManipulationInProgress())
{
QPoint vector = m_mouseGrabInfo.info.widgetMouseCurrentPos - m_mouseGrabInfo.info.widgetMouseStartPos;
if (vector.manhattanLength() > QApplication::startDragDistance() ||
m_mouseGrabInfo.info.timer.hasExpired(QApplication::startDragTime()))
{
m_manipulator.startManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, threshold);
}
}
else
{
m_manipulator.updateManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint);
}
2022-03-04 19:32:33 +01:00
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
updateMouseCursor(info, threshold);
2022-03-04 19:32:33 +01:00
// If mouse is grabbed, then event is accepted always (because
// we get Press event, when we grabbed the mouse, then we will
// wait for corresponding release event while all mouse move events
// will be accepted, even if editor doesn't accept them.
if (isMouseGrabbed())
{
event->accept();
}
if (m_manipulator.isManipulationInProgress())
{
emit sceneChanged(true);
}
2022-02-11 19:15:57 +01:00
}
void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event)
{
Q_UNUSED(widget);
2022-03-04 19:32:33 +01:00
if (!isActive())
{
return;
}
// We will accept mouse wheel events, if we are grabbing the mouse.
// We do not want to zoom in/zoom out while grabbing.
if (isMouseGrabbed())
{
event->accept();
}
2022-02-11 19:15:57 +01:00
}
QString PDFPageContentScene::getTooltip() const
{
return QString();
}
const std::optional<QCursor>& PDFPageContentScene::getCursor() const
{
2022-02-13 19:46:09 +01:00
return m_cursor;
2022-02-11 19:15:57 +01:00
}
int PDFPageContentScene::getInputPriority() const
{
return ToolPriority + 1;
}
void PDFPageContentScene::drawPage(QPainter* painter,
2022-02-13 19:46:09 +01:00
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
2022-02-11 19:15:57 +01:00
{
2022-02-24 20:25:18 +01:00
if (!m_isActive)
{
return;
}
2022-02-13 19:46:09 +01:00
for (const auto& element : m_elements)
{
if (element->getPageIndex() != pageIndex)
{
continue;
}
element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
}
m_manipulator.drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
2022-02-13 19:46:09 +01:00
}
2022-03-04 19:32:33 +01:00
PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidget* widget, QPoint point)
{
MouseEventInfo result;
2022-03-05 19:24:35 +01:00
Q_UNUSED(widget);
2022-03-04 19:32:33 +01:00
Q_ASSERT(isActive());
if (isMouseGrabbed())
{
result = m_mouseGrabInfo.info;
result.widgetMouseCurrentPos = point;
return result;
}
2022-03-05 19:24:35 +01:00
result.widgetMouseStartPos = point;
result.widgetMouseCurrentPos = point;
result.timer = m_mouseGrabInfo.info.timer;
result.pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(point, &result.pagePos);
const PDFReal threshold = getSnapPointDistanceThreshold();
for (const auto& elementItem : m_elements)
2022-03-04 19:32:33 +01:00
{
2022-03-05 19:24:35 +01:00
PDFPageContentElement* element = elementItem.get();
2022-03-04 19:32:33 +01:00
2022-03-05 19:24:35 +01:00
if (element->getPageIndex() != result.pageIndex)
{
// Different page
continue;
}
if (element->getManipulationMode(result.pagePos, threshold) != 0)
{
result.hoveredElementIds.insert(element->getElementId());
}
2022-03-04 19:32:33 +01:00
}
return result;
}
2022-03-05 19:24:35 +01:00
PDFReal PDFPageContentScene::getSnapPointDistanceThreshold() const
{
const PDFReal snapPointDistanceThresholdPixels = PDFWidgetUtils::scaleDPI_x(m_widget, 6.0);
const PDFReal snapPointDistanceThreshold = m_widget->getDrawWidgetProxy()->transformPixelToDeviceSpace(snapPointDistanceThresholdPixels);
return snapPointDistanceThreshold;
}
2022-03-04 19:32:33 +01:00
void PDFPageContentScene::grabMouse(const MouseEventInfo& info, QMouseEvent* event)
{
Q_ASSERT(isActive());
if (event->type() == QEvent::MouseButtonDblClick)
{
// Double clicks doesn't grab the mouse
return;
}
Q_ASSERT(event->type() == QEvent::MouseButtonPress);
if (isMouseGrabbed())
{
// If mouse is already grabbed, then when new mouse button is pressed,
// we just increase nesting level and accept the mouse event. We are
// accepting all mouse events, if mouse is grabbed.
++m_mouseGrabInfo.mouseGrabNesting;
event->accept();
}
else if (event->isAccepted())
{
// Event is accepted and we are not grabbing the mouse. We must start
// grabbing the mouse.
Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0);
++m_mouseGrabInfo.mouseGrabNesting;
m_mouseGrabInfo.info = info;
}
}
void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event)
{
Q_UNUSED(info);
Q_ASSERT(isActive());
Q_ASSERT(event->type() == QEvent::MouseButtonRelease);
if (isMouseGrabbed())
{
// Mouse is being grabbed, decrease nesting level. We must also accept
// mouse release event, because mouse is being grabbed.
--m_mouseGrabInfo.mouseGrabNesting;
event->accept();
if (!isMouseGrabbed())
{
m_mouseGrabInfo.info = MouseEventInfo();
}
}
Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0);
}
void PDFPageContentScene::updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold)
{
std::optional<Qt::CursorShape> cursorShapeValue;
for (const PDFInteger id : info.hoveredElementIds)
{
PDFPageContentElement* element = getElementById(id);
uint manipulationMode = element->getManipulationMode(info.pagePos, snapPointDistanceThreshold);
if (manipulationMode > 0)
{
Qt::CursorShape cursorShape = PDFPageContentElement::getCursorShapeForManipulationMode(manipulationMode);
if (!cursorShapeValue)
{
cursorShapeValue = cursorShape;
}
else if (cursorShapeValue.value() != cursorShape)
{
cursorShapeValue = Qt::ArrowCursor;
break;
}
}
}
if (cursorShapeValue && cursorShapeValue.value() == Qt::ArrowCursor &&
m_manipulator.isManipulationInProgress())
{
const bool isCopy = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
cursorShapeValue = isCopy ? Qt::DragCopyCursor : Qt::DragMoveCursor;
}
// Update cursor shape
if (!cursorShapeValue)
{
m_cursor = std::nullopt;
}
else if (!m_cursor.has_value() || m_cursor.value().shape() != cursorShapeValue.value())
{
m_cursor = QCursor(cursorShapeValue.value());
}
}
2022-03-08 19:36:52 +01:00
void PDFPageContentScene::onSelectionChanged()
{
emit sceneChanged(true);
}
2022-03-05 19:24:35 +01:00
PDFWidget* PDFPageContentScene::widget() const
{
return m_widget;
}
void PDFPageContentScene::setWidget(PDFWidget* newWidget)
{
m_widget = newWidget;
}
2022-02-24 20:25:18 +01:00
bool PDFPageContentScene::isActive() const
{
return m_isActive;
}
void PDFPageContentScene::setActive(bool newIsActive)
{
if (m_isActive != newIsActive)
{
m_isActive = newIsActive;
2022-03-04 19:32:33 +01:00
if (!newIsActive)
{
m_mouseGrabInfo = MouseGrabInfo();
m_manipulator.reset();
}
emit sceneChanged(false);
2022-03-04 19:32:33 +01:00
}
}
2022-03-09 20:42:05 +01:00
std::set<PDFInteger> PDFPageContentScene::getElementIds() const
{
std::set<PDFInteger> result;
for (const auto& element : m_elements)
{
result.insert(element->getElementId());
}
return result;
}
2022-03-04 19:32:33 +01:00
void PDFPageContentScene::removeElementsById(const std::set<PDFInteger>& selection)
{
const size_t oldSize = m_elements.size();
m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return selection.count(element->getElementId()); }), m_elements.end());
const size_t newSize = m_elements.size();
if (newSize < oldSize)
{
emit sceneChanged(false);
2022-02-24 20:25:18 +01:00
}
}
2022-02-13 19:46:09 +01:00
PDFPageContentElementLine* PDFPageContentElementLine::clone() const
{
PDFPageContentElementLine* copy = new PDFPageContentElementLine();
2022-03-04 19:32:33 +01:00
copy->setElementId(getElementId());
2022-02-13 19:46:09 +01:00
copy->setPageIndex(getPageIndex());
copy->setPen(getPen());
copy->setBrush(getBrush());
copy->setGeometry(getGeometry());
copy->setLine(getLine());
return copy;
}
void PDFPageContentElementLine::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (pageIndex != getPageIndex())
{
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setPen(getPen());
painter->setBrush(getBrush());
painter->setRenderHint(QPainter::Antialiasing);
painter->drawLine(getLine());
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentElementLine::getManipulationMode(const QPointF& point,
PDFReal snapPointDistanceThreshold) const
{
if ((m_line.p1() - point).manhattanLength() < snapPointDistanceThreshold)
{
return Pt1;
}
if ((m_line.p2() - point).manhattanLength() < snapPointDistanceThreshold)
{
return Pt2;
}
QPointF vl = m_line.p2() - m_line.p1();
QPointF vp = point - m_line.p1();
2022-03-08 19:36:52 +01:00
const qreal lengthSquared = QPointF::dotProduct(vl, vl);
2022-03-05 19:24:35 +01:00
if (qFuzzyIsNull(lengthSquared))
{
return None;
}
const qreal t = QPointF::dotProduct(vl, vp) / lengthSquared;
if (t >= 0.0 && t <= 1.0)
{
QPointF projectedPoint = m_line.p1() + t * vl;
if ((point - projectedPoint).manhattanLength() < snapPointDistanceThreshold)
{
return Translate;
}
}
return None;
}
void PDFPageContentElementLine::performManipulation(uint mode, const QPointF& offset)
{
switch (mode)
{
case None:
break;
case Pt1:
m_line.setP1(m_line.p1() + offset);
break;
case Pt2:
m_line.setP2(m_line.p2() + offset);
break;
case Translate:
m_line.translate(offset);
break;
default:
Q_ASSERT(false);
break;
}
}
2022-03-08 19:36:52 +01:00
QRectF PDFPageContentElementLine::getBoundingBox() const
{
if (!qFuzzyIsNull(m_line.length()))
{
const qreal xMin = qMin(m_line.p1().x(), m_line.p2().x());
const qreal xMax = qMax(m_line.p1().x(), m_line.p2().x());
const qreal yMin = qMin(m_line.p1().y(), m_line.p2().y());
const qreal yMax = qMax(m_line.p1().y(), m_line.p2().y());
return QRectF(xMin, yMin, xMax - xMin, yMax - yMin);
}
return QRectF();
}
2022-02-13 19:46:09 +01:00
PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const
{
return m_geometry;
}
void PDFPageContentElementLine::setGeometry(LineGeometry newGeometry)
{
m_geometry = newGeometry;
}
const QLineF& PDFPageContentElementLine::getLine() const
{
return m_line;
}
void PDFPageContentElementLine::setLine(const QLineF& newLine)
{
m_line = newLine;
if (m_geometry == LineGeometry::Horizontal)
{
m_line.setP2(QPointF(newLine.p2().x(), newLine.p1().y()));
}
if (m_geometry == LineGeometry::Vertical)
{
m_line.setP2(QPointF(newLine.p1().x(), newLine.p2().y()));
}
2022-02-11 19:15:57 +01:00
}
2022-02-19 19:30:52 +01:00
PDFPageContentSvgElement::PDFPageContentSvgElement() :
m_renderer(std::make_unique<QSvgRenderer>())
{
}
PDFPageContentSvgElement::~PDFPageContentSvgElement()
{
}
PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const
{
PDFPageContentSvgElement* copy = new PDFPageContentSvgElement();
2022-03-04 19:32:33 +01:00
copy->setElementId(getElementId());
2022-02-19 19:30:52 +01:00
copy->setPageIndex(getPageIndex());
copy->setRectangle(getRectangle());
copy->setContent(getContent());
return copy;
}
void PDFPageContentSvgElement::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (pageIndex != getPageIndex() || !getRectangle().isValid())
{
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setRenderHint(QPainter::Antialiasing);
QRectF viewBox = m_renderer->viewBoxF();
if (!viewBox.isValid())
{
return;
}
QRectF renderBox = getRectangle();
QSizeF viewBoxSize = viewBox.size();
QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio);
QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize);
targetRenderBox.moveCenter(renderBox.center());
painter->translate(targetRenderBox.bottomLeft());
painter->scale(1.0, -1.0);
targetRenderBox.moveTopLeft(QPointF(0, 0));
m_renderer->render(painter, targetRenderBox);
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentSvgElement::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const
{
return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold);
}
void PDFPageContentSvgElement::performManipulation(uint mode, const QPointF& offset)
{
performRectangleManipulation(m_rectangle, mode, offset);
}
2022-03-08 19:36:52 +01:00
QRectF PDFPageContentSvgElement::getBoundingBox() const
{
return getRectangle();
}
2022-02-19 19:30:52 +01:00
const QByteArray& PDFPageContentSvgElement::getContent() const
{
return m_content;
}
void PDFPageContentSvgElement::setContent(const QByteArray& newContent)
{
if (m_content != newContent)
{
m_content = newContent;
m_renderer->load(m_content);
}
}
const QRectF& PDFPageContentSvgElement::getRectangle() const
{
return m_rectangle;
}
void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle)
{
m_rectangle = newRectangle;
}
2022-02-24 20:25:18 +01:00
PDFPageContentElementDot* PDFPageContentElementDot::clone() const
{
PDFPageContentElementDot* copy = new PDFPageContentElementDot();
2022-03-04 19:32:33 +01:00
copy->setElementId(getElementId());
2022-02-24 20:25:18 +01:00
copy->setPageIndex(getPageIndex());
copy->setPen(getPen());
copy->setBrush(getBrush());
copy->setPoint(getPoint());
return copy;
}
void PDFPageContentElementDot::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (pageIndex != getPageIndex())
{
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setRenderHint(QPainter::Antialiasing);
2022-02-27 17:08:24 +01:00
painter->setPen(getPen());
painter->setBrush(getBrush());
2022-02-24 20:25:18 +01:00
painter->drawPoint(m_point);
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentElementDot::getManipulationMode(const QPointF& point,
PDFReal snapPointDistanceThreshold) const
{
if ((m_point - point).manhattanLength() < snapPointDistanceThreshold)
{
return Translate;
}
return None;
}
void PDFPageContentElementDot::performManipulation(uint mode, const QPointF& offset)
{
switch (mode)
{
case None:
break;
case Translate:
m_point += offset;
break;
default:
Q_ASSERT(false);
break;
}
}
2022-03-08 19:36:52 +01:00
QRectF PDFPageContentElementDot::getBoundingBox() const
{
return QRectF(m_point, QSizeF(0.001, 0.001));
}
2022-02-24 20:25:18 +01:00
QPointF PDFPageContentElementDot::getPoint() const
{
return m_point;
}
void PDFPageContentElementDot::setPoint(QPointF newPoint)
{
m_point = newPoint;
}
2022-02-27 19:59:53 +01:00
PDFPageContentElementFreehandCurve* PDFPageContentElementFreehandCurve::clone() const
{
PDFPageContentElementFreehandCurve* copy = new PDFPageContentElementFreehandCurve();
2022-03-04 19:32:33 +01:00
copy->setElementId(getElementId());
2022-02-27 19:59:53 +01:00
copy->setPageIndex(getPageIndex());
copy->setPen(getPen());
copy->setBrush(getBrush());
copy->setCurve(getCurve());
return copy;
}
void PDFPageContentElementFreehandCurve::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (pageIndex != getPageIndex())
{
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setPen(getPen());
painter->setBrush(getBrush());
painter->setRenderHint(QPainter::Antialiasing);
painter->drawPath(getCurve());
}
2022-03-05 19:24:35 +01:00
uint PDFPageContentElementFreehandCurve::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const
{
Q_UNUSED(snapPointDistanceThreshold);
if (m_curve.isEmpty())
{
return None;
}
if (m_curve.controlPointRect().contains(point))
{
return Translate;
}
return None;
}
void PDFPageContentElementFreehandCurve::performManipulation(uint mode, const QPointF& offset)
{
switch (mode)
{
case None:
break;
case Translate:
m_curve.translate(offset);
break;
default:
Q_ASSERT(false);
break;
}
}
2022-03-08 19:36:52 +01:00
QRectF PDFPageContentElementFreehandCurve::getBoundingBox() const
{
return m_curve.controlPointRect();
}
2022-02-27 19:59:53 +01:00
QPainterPath PDFPageContentElementFreehandCurve::getCurve() const
{
return m_curve;
}
void PDFPageContentElementFreehandCurve::setCurve(QPainterPath newCurve)
{
m_curve = newCurve;
}
void PDFPageContentElementFreehandCurve::addStartPoint(const QPointF& point)
{
m_curve.moveTo(point);
}
void PDFPageContentElementFreehandCurve::addPoint(const QPointF& point)
{
m_curve.lineTo(point);
}
void PDFPageContentElementFreehandCurve::clear()
{
setPageIndex(-1);
m_curve = QPainterPath();
}
2022-03-04 19:32:33 +01:00
PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent) :
QObject(parent),
m_scene(scene),
m_isManipulationInProgress(false)
{
}
void PDFPageContentElementManipulator::reset()
{
2022-03-05 19:24:35 +01:00
cancelManipulation();
2022-03-04 19:32:33 +01:00
deselectAll();
}
void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes modes)
{
bool modified = false;
if (modes.testFlag(Clear))
{
modified = !m_selection.empty();
m_selection.clear();
}
// Is id valid?
if (id > 0)
{
if (modes.testFlag(Select))
{
if (!isSelected(id))
{
modified = true;
m_selection.insert(id);
}
}
if (modes.testFlag(Deselect))
{
if (isSelected(id))
{
modified = true;
m_selection.erase(id);
}
}
if (modes.testFlag(Toggle))
{
if (isSelected(id))
{
m_selection.erase(id);
}
else
{
m_selection.insert(id);
}
// When toggle is performed, selection is always changed
modified = true;
}
}
if (modified)
{
emit selectionChanged();
}
}
void PDFPageContentElementManipulator::update(const std::set<PDFInteger>& ids, SelectionModes modes)
{
bool modified = false;
if (modes.testFlag(Clear))
{
modified = !m_selection.empty();
m_selection.clear();
}
// Is id valid?
if (!ids.empty())
{
if (modes.testFlag(Select))
{
for (auto id : ids)
{
if (!isSelected(id))
{
modified = true;
m_selection.insert(id);
}
}
}
if (modes.testFlag(Deselect))
{
for (auto id : ids)
{
if (isSelected(id))
{
modified = true;
m_selection.erase(id);
}
}
}
if (modes.testFlag(Toggle))
{
for (auto id : ids)
{
if (isSelected(id))
{
m_selection.erase(id);
}
else
{
m_selection.insert(id);
}
// When toggle is performed, selection is always changed
modified = true;
}
}
}
if (modified)
{
emit selectionChanged();
}
}
void PDFPageContentElementManipulator::select(PDFInteger id)
{
update(id, Select);
}
void PDFPageContentElementManipulator::select(const std::set<PDFInteger>& ids)
{
update(ids, Select);
}
void PDFPageContentElementManipulator::selectNew(PDFInteger id)
{
2022-03-05 19:24:35 +01:00
update(id, SelectionModes(Select | Clear));
2022-03-04 19:32:33 +01:00
}
void PDFPageContentElementManipulator::selectNew(const std::set<PDFInteger>& ids)
{
2022-03-05 19:24:35 +01:00
update(ids, SelectionModes(Select | Clear));
2022-03-04 19:32:33 +01:00
}
void PDFPageContentElementManipulator::deselect(PDFInteger id)
{
update(id, Deselect);
}
void PDFPageContentElementManipulator::deselect(const std::set<PDFInteger>& ids)
{
update(ids, Deselect);
}
2022-03-09 20:42:05 +01:00
void PDFPageContentElementManipulator::selectAll()
{
std::set<PDFInteger> ids = m_scene->getElementIds();
update(ids, Select);
}
2022-03-04 19:32:33 +01:00
void PDFPageContentElementManipulator::deselectAll()
{
update(-1, Clear);
}
2022-03-05 19:24:35 +01:00
bool PDFPageContentElementManipulator::isManipulationAllowed(PDFInteger pageIndex) const
{
for (const PDFInteger id : m_selection)
{
if (const PDFPageContentElement* element = m_scene->getElementById(id))
{
if (element->getPageIndex() == pageIndex)
{
return true;
}
}
}
return false;
}
2022-03-09 20:42:05 +01:00
void PDFPageContentElementManipulator::performDeleteSelection()
2022-03-04 19:32:33 +01:00
{
2022-03-05 19:24:35 +01:00
cancelManipulation();
2022-03-04 19:32:33 +01:00
m_scene->removeElementsById(m_selection);
deselectAll();
}
2022-03-05 19:24:35 +01:00
void PDFPageContentElementManipulator::startManipulation(PDFInteger pageIndex,
const QPointF& startPoint,
const QPointF& currentPoint,
PDFReal snapPointDistanceThreshold)
2022-03-04 19:32:33 +01:00
{
2022-03-05 19:24:35 +01:00
Q_ASSERT(!isManipulationInProgress());
// Collect elements to be manipulated
for (const PDFInteger id : m_selection)
{
const PDFPageContentElement* element = m_scene->getElementById(id);
if (!element || element->getPageIndex() != pageIndex)
{
continue;
}
const uint manipulationMode = element->getManipulationMode(startPoint, snapPointDistanceThreshold);
if (manipulationMode)
{
// Jakub Melka: yes, we can manipulate this element
m_manipulatedElements.emplace_back(element->clone());
m_manipulationModes[id] = manipulationMode;
}
}
if (!m_manipulatedElements.empty())
{
m_isManipulationInProgress = true;
m_lastUpdatedPoint = startPoint;
updateManipulation(pageIndex, startPoint, currentPoint);
emit stateChanged();
}
2022-03-05 19:24:35 +01:00
}
void PDFPageContentElementManipulator::updateManipulation(PDFInteger pageIndex,
const QPointF& startPoint,
const QPointF& currentPoint)
{
Q_UNUSED(startPoint);
QPointF offset = currentPoint - m_lastUpdatedPoint;
for (const auto& element : m_manipulatedElements)
{
if (element->getPageIndex() == pageIndex)
2022-03-05 19:24:35 +01:00
{
element->performManipulation(m_manipulationModes[element->getElementId()], offset);
}
}
m_lastUpdatedPoint = currentPoint;
2022-03-05 19:24:35 +01:00
emit stateChanged();
}
void PDFPageContentElementManipulator::finishManipulation(PDFInteger pageIndex,
const QPointF& startPoint,
const QPointF& currentPoint,
bool createCopy)
{
Q_ASSERT(isManipulationInProgress());
updateManipulation(pageIndex, startPoint, currentPoint);
if (createCopy)
{
for (const auto& element : m_manipulatedElements)
{
m_scene->addElement(element->clone());
}
}
else
{
for (const auto& element : m_manipulatedElements)
{
m_scene->replaceElement(element->clone());
}
}
cancelManipulation();
}
void PDFPageContentElementManipulator::cancelManipulation()
{
if (isManipulationInProgress())
2022-03-04 19:32:33 +01:00
{
m_isManipulationInProgress = false;
m_manipulatedElements.clear();
m_manipulationModes.clear();
emit stateChanged();
}
2022-03-05 19:24:35 +01:00
Q_ASSERT(!m_isManipulationInProgress);
Q_ASSERT(m_manipulatedElements.empty());
Q_ASSERT(m_manipulationModes.empty());
2022-03-04 19:32:33 +01:00
}
void PDFPageContentElementManipulator::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
// Draw selection
2022-03-08 19:36:52 +01:00
if (!isSelectionEmpty())
{
QPainterPath selectionPath;
for (const PDFInteger id : m_selection)
{
if (PDFPageContentElement* element = m_scene->getElementById(id))
{
QPainterPath tempPath;
tempPath.addRect(element->getBoundingBox());
selectionPath = selectionPath.united(tempPath);
}
}
2022-03-08 19:36:52 +01:00
if (!selectionPath.isEmpty())
{
PDFPainterStateGuard guard(painter);
QPen pen(Qt::SolidLine);
pen.setWidthF(2.0);
pen.setColor(QColor::fromRgbF(0.8, 0.8, 0.1, 0.7));
QBrush brush(Qt::SolidPattern);
brush.setColor(QColor::fromRgbF(1.0, 1.0, 0.0, 0.2));
painter->setPen(std::move(pen));
painter->setBrush(std::move(brush));
selectionPath = pagePointToDevicePointMatrix.map(selectionPath);
painter->drawPath(selectionPath);
}
}
// Draw dragged items
if (isManipulationInProgress())
{
PDFPainterStateGuard guard(painter);
painter->setOpacity(0.2);
for (const auto& manipulatedElement : m_manipulatedElements)
{
manipulatedElement->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
}
}
}
2022-02-11 19:15:57 +01:00
} // namespace pdf