Signature plugin: text edit box

This commit is contained in:
Jakub Melka 2022-03-26 19:26:32 +01:00
parent 5a8b1ca670
commit 22e8fd4522
13 changed files with 1679 additions and 1060 deletions

View File

@ -89,6 +89,7 @@ SOURCES += \
sources/pdfsignaturehandler.cpp \
sources/pdfsnapper.cpp \
sources/pdfstructuretree.cpp \
sources/pdftexteditpseudowidget.cpp \
sources/pdftextlayout.cpp \
sources/pdftransparencyrenderer.cpp \
sources/pdfutils.cpp \
@ -170,6 +171,7 @@ HEADERS += \
sources/pdfsignaturehandler_impl.h \
sources/pdfsnapper.h \
sources/pdfstructuretree.h \
sources/pdftexteditpseudowidget.h \
sources/pdftextlayout.h \
sources/pdftransparencyrenderer.h \
sources/pdfwidgettool.h \

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Jakub Melka
// Copyright (C) 2020-2022 Jakub Melka
//
// This file is part of PDF4QT.
//

View File

@ -18,10 +18,12 @@
#include "pdfpagecontenteditortools.h"
#include "pdfpagecontentelements.h"
#include "pdfpainterutils.h"
#include "pdftexteditpseudowidget.h"
#include <QPen>
#include <QPainter>
#include <QMouseEvent>
#include <QGuiApplication>
namespace pdf
{
@ -494,4 +496,261 @@ void PDFCreatePCElementFreehandCurveTool::resetTool()
m_element->clear();
}
PDFCreatePCElementTextTool::PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy,
PDFPageContentScene* scene,
QAction* action,
QObject* parent) :
BaseClass(proxy, scene, action, parent),
m_pickTool(nullptr),
m_element(nullptr),
m_textEditWidget(nullptr)
{
m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this);
m_pickTool->setDrawSelectionRectangle(true);
connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementTextTool::onRectanglePicked);
QFont font = QGuiApplication::font();
font.setPixelSize(16.0);
m_element = new PDFPageContentElementTextBox();
m_element->setBrush(Qt::NoBrush);
m_element->setPen(QPen(Qt::SolidLine));
m_element->setFont(font);
m_textEditWidget = new PDFTextEditPseudowidget(PDFFormField::FieldFlags());
}
PDFCreatePCElementTextTool::~PDFCreatePCElementTextTool()
{
delete m_textEditWidget;
delete m_element;
}
void PDFCreatePCElementTextTool::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
if (pageIndex != m_element->getPageIndex())
{
return;
}
if (isEditing())
{
PDFPainterStateGuard guard(painter);
AnnotationDrawParameters parameters;
parameters.painter = painter;
parameters.boundingRectangle = m_element->getRectangle();
parameters.key.first = PDFAppeareanceStreams::Appearance::Normal;
parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
m_textEditWidget->draw(parameters, true);
}
}
void PDFCreatePCElementTextTool::setActiveImpl(bool active)
{
BaseClass::setActiveImpl(active);
if (active)
{
Q_ASSERT(!getTopToolstackTool());
addTool(m_pickTool);
}
else
{
m_textEditWidget->setText(QString());
m_element->setText(QString());
if (getTopToolstackTool())
{
removeTool();
}
}
m_pickTool->setActive(active);
}
void PDFCreatePCElementTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle)
{
if (pageRectangle.isEmpty())
{
return;
}
m_element->setPageIndex(pageIndex);
m_element->setRectangle(pageRectangle);
m_textEditWidget->setAppearance(m_element->getFont(),
m_element->getAlignment(),
m_element->getRectangle(),
std::numeric_limits<int>::max(),
m_element->getPen().color());
removeTool();
}
void PDFCreatePCElementTextTool::finishEditing()
{
setActive(false);
}
std::optional<QPointF> PDFCreatePCElementTextTool::getPagePointUnderMouse(QMouseEvent* event) const
{
QPointF pagePoint;
PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (pageIndex == m_element->getPageIndex() &&
m_element->getRectangle().contains(pagePoint))
{
return pagePoint;
}
return std::nullopt;
}
bool PDFCreatePCElementTextTool::isEditing() const
{
return isActive() && !getTopToolstackTool();
}
void PDFCreatePCElementTextTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
if (isEditing())
{
m_textEditWidget->shortcutOverrideEvent(widget, event);
}
}
void PDFCreatePCElementTextTool::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
event->ignore();
if (!isEditing())
{
BaseClass::keyPressEvent(widget, event);
return;
}
if (event->key() == Qt::Key_Escape)
{
return;
}
if (!m_textEditWidget->isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
{
// Commit the editor and create element
finishEditing();
event->accept();
return;
}
m_textEditWidget->keyPressEvent(widget, event);
if (event->isAccepted())
{
widget->update();
}
}
void PDFCreatePCElementTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
if (isEditing())
{
if (event->button() == Qt::LeftButton)
{
std::optional<QPointF> pagePoint = getPagePointUnderMouse(event);
if (pagePoint)
{
const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true);
m_textEditWidget->setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier);
}
else
{
finishEditing();
}
event->accept();
widget->update();
}
}
else
{
BaseClass::mousePressEvent(widget, event);
}
}
void PDFCreatePCElementTextTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
if (isEditing())
{
if (event->button() == Qt::LeftButton)
{
std::optional<QPointF> pagePoint = getPagePointUnderMouse(event);
if (pagePoint)
{
const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true);
m_textEditWidget->setCursorPosition(cursorPosition, false);
m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordBackward(), false);
m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordForward(), true);
}
else
{
finishEditing();
}
event->accept();
widget->update();
}
}
else
{
BaseClass::mousePressEvent(widget, event);
}
}
void PDFCreatePCElementTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
if (isEditing())
{
std::optional<QPointF> pagePoint = getPagePointUnderMouse(event);
if (pagePoint)
{
// We must test, if left mouse button is pressed while
// we are moving the mouse - if yes, then select the text.
if (event->buttons() & Qt::LeftButton)
{
const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true);
m_textEditWidget->setCursorPosition(cursorPosition, true);
event->accept();
widget->update();
}
}
}
else
{
BaseClass::mouseMoveEvent(widget, event);
}
}
void PDFCreatePCElementTextTool::wheelEvent(QWidget* widget, QWheelEvent* event)
{
if (isEditing())
{
}
else
{
BaseClass::wheelEvent(widget, event);
}
}
} // namespace pdf

View File

@ -27,8 +27,10 @@ class PDFPageContentScene;
class PDFPageContentSvgElement;
class PDFPageContentElementDot;
class PDFPageContentElementLine;
class PDFPageContentElementTextBox;
class PDFPageContentElementRectangle;
class PDFPageContentElementFreehandCurve;
class PDFTextEditPseudowidget;
class PDFCreatePCElementTool : public PDFWidgetTool
{
@ -201,6 +203,49 @@ private:
PDFPageContentElementFreehandCurve* m_element;
};
/// Tool that displays SVG image
class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementTextTool : public PDFCreatePCElementTool
{
Q_OBJECT
private:
using BaseClass = PDFCreatePCElementTool;
public:
explicit PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy,
PDFPageContentScene* scene,
QAction* action,
QObject* parent);
virtual ~PDFCreatePCElementTextTool() override;
virtual void drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const override;
virtual void setActiveImpl(bool active) override;
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override;
virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override;
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override;
virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override;
private:
void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle);
void finishEditing();
std::optional<QPointF> getPagePointUnderMouse(QMouseEvent* event) const;
bool isEditing() const;
PDFPickTool* m_pickTool;
PDFPageContentElementTextBox* m_element;
PDFTextEditPseudowidget* m_textEditWidget;
};
} // namespace pdf
#endif // PDFPAGECONTENTEDITORTOOLS_H

View File

@ -2233,4 +2233,114 @@ void PDFPageContentElementManipulator::eraseSelectedElementById(PDFInteger id)
}
}
PDFPageContentElementTextBox* PDFPageContentElementTextBox::clone() const
{
PDFPageContentElementTextBox* copy = new PDFPageContentElementTextBox();
copy->setElementId(getElementId());
copy->setPageIndex(getPageIndex());
copy->setPen(getPen());
copy->setBrush(getBrush());
copy->setRectangle(getRectangle());
copy->setText(getText());
copy->setFont(getFont());
copy->setAngle(getAngle());
copy->setAlignment(getAlignment());
return copy;
}
void PDFPageContentElementTextBox::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;
}
QRectF rect = getRectangle();
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->setPen(getPen());
painter->setBrush(getBrush());
painter->setFont(getFont());
painter->setRenderHint(QPainter::Antialiasing);
painter->setClipRect(rect, Qt::IntersectClip);
painter->translate(rect.center());
painter->rotate(getAngle());
QTextOption option;
option.setAlignment(getAlignment());
QRectF textRect(-rect.width() * 0.5, -rect.height() * 0.5, rect.width(), rect.height());
painter->drawText(textRect, getText(), option);
}
uint PDFPageContentElementTextBox::getManipulationMode(const QPointF& point,
PDFReal snapPointDistanceThreshold) const
{
return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold);
}
void PDFPageContentElementTextBox::performManipulation(uint mode, const QPointF& offset)
{
performRectangleManipulation(m_rectangle, mode, offset);
}
QRectF PDFPageContentElementTextBox::getBoundingBox() const
{
return m_rectangle;
}
void PDFPageContentElementTextBox::setSize(QSizeF size)
{
performRectangleSetSize(m_rectangle, size);
}
const QString& PDFPageContentElementTextBox::getText() const
{
return m_text;
}
void PDFPageContentElementTextBox::setText(const QString& newText)
{
m_text = newText;
}
const QFont& PDFPageContentElementTextBox::getFont() const
{
return m_font;
}
void PDFPageContentElementTextBox::setFont(const QFont& newFont)
{
m_font = newFont;
}
PDFReal PDFPageContentElementTextBox::getAngle() const
{
return m_angle;
}
void PDFPageContentElementTextBox::setAngle(PDFReal newAngle)
{
m_angle = newAngle;
}
const Qt::Alignment& PDFPageContentElementTextBox::getAlignment() const
{
return m_alignment;
}
void PDFPageContentElementTextBox::setAlignment(const Qt::Alignment& newAlignment)
{
m_alignment = newAlignment;
}
} // namespace pdf

View File

@ -21,6 +21,7 @@
#include "pdfdocumentdrawinterface.h"
#include <QPen>
#include <QFont>
#include <QBrush>
#include <QCursor>
#include <QPainterPath>
@ -294,6 +295,50 @@ private:
std::unique_ptr<QSvgRenderer> m_renderer;
};
class PDF4QTLIBSHARED_EXPORT PDFPageContentElementTextBox : public PDFPageContentStyledElement
{
public:
virtual ~PDFPageContentElementTextBox() = default;
virtual PDFPageContentElementTextBox* clone() const override;
const QRectF& getRectangle() const { return m_rectangle; }
void setRectangle(const QRectF& newRectangle) { m_rectangle = newRectangle; }
virtual void drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const override;
virtual uint getManipulationMode(const QPointF& point,
PDFReal snapPointDistanceThreshold) const override;
virtual void performManipulation(uint mode, const QPointF& offset) override;
virtual QRectF getBoundingBox() const override;
virtual void setSize(QSizeF size);
const QString& getText() const;
void setText(const QString& newText);
const QFont& getFont() const;
void setFont(const QFont& newFont);
PDFReal getAngle() const;
void setAngle(PDFReal newAngle);
const Qt::Alignment& getAlignment() const;
void setAlignment(const Qt::Alignment& newAlignment);
private:
QString m_text;
QRectF m_rectangle;
QFont m_font;
PDFReal m_angle = 0.0;
Qt::Alignment m_alignment = Qt::AlignCenter;
};
class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject
{
Q_OBJECT

View File

@ -0,0 +1,947 @@
// 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 "pdftexteditpseudowidget.h"
#include "pdfpainterutils.h"
#include <QStyle>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QClipboard>
namespace pdf
{
PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) :
m_flags(flags),
m_selectionStart(0),
m_selectionEnd(0),
m_positionCursor(0),
m_maxTextLength(0)
{
m_textLayout.setCacheEnabled(true);
m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter);
}
void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste,
QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar,
QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine,
QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine,
QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument,
QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar,
QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine,
QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine,
QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument,
QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord,
QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace };
if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; }))
{
event->accept();
return;
}
switch (event->key())
{
case Qt::Key_Direction_L:
case Qt::Key_Direction_R:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
event->accept();
break;
default:
break;
}
if (!event->text().isEmpty())
{
event->accept();
for (const QChar& character : event->text())
{
if (!character.isPrint())
{
event->ignore();
break;
}
}
}
}
void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
/*
We will support following key sequences:
Delete
Cut,
Copy,
Paste,
SelectAll,
MoveToNextChar,
MoveToPreviousChar,
MoveToNextWord,
MoveToPreviousWord,
MoveToNextLine,
MoveToPreviousLine,
MoveToStartOfLine,
MoveToEndOfLine,
MoveToStartOfBlock,
MoveToEndOfBlock,
MoveToStartOfDocument,
MoveToEndOfDocument,
SelectNextChar,
SelectPreviousChar,
SelectNextWord,
SelectPreviousWord,
SelectNextLine,
SelectPreviousLine,
SelectStartOfLine,
SelectEndOfLine,
SelectStartOfBlock,
SelectEndOfBlock,
SelectStartOfDocument,
SelectEndOfDocument,
DeleteStartOfWord,
DeleteEndOfWord,
DeleteEndOfLine,
Deselect,
DeleteCompleteLine,
Backspace,
* */
event->accept();
if (event == QKeySequence::Delete)
{
performDelete();
}
else if (event == QKeySequence::Cut)
{
performCut();
}
else if (event == QKeySequence::Copy)
{
performCopy();
}
else if (event == QKeySequence::Paste)
{
performPaste();
}
else if (event == QKeySequence::SelectAll)
{
setSelection(0, getTextLength());
}
else if (event == QKeySequence::MoveToNextChar)
{
setCursorPosition(getCursorCharacterForward(), false);
}
else if (event == QKeySequence::MoveToPreviousChar)
{
setCursorPosition(getCursorCharacterBackward(), false);
}
else if (event == QKeySequence::MoveToNextWord)
{
setCursorPosition(getCursorWordForward(), false);
}
else if (event == QKeySequence::MoveToPreviousWord)
{
setCursorPosition(getCursorWordBackward(), false);
}
else if (event == QKeySequence::MoveToNextLine)
{
setCursorPosition(getCursorLineDown(), false);
}
else if (event == QKeySequence::MoveToPreviousLine)
{
setCursorPosition(getCursorLineUp(), false);
}
else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock)
{
setCursorPosition(getCursorLineStart(), false);
}
else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock)
{
setCursorPosition(getCursorLineEnd(), false);
}
else if (event == QKeySequence::MoveToStartOfDocument)
{
setCursorPosition(getCursorDocumentStart(), false);
}
else if (event == QKeySequence::MoveToEndOfDocument)
{
setCursorPosition(getCursorDocumentEnd(), false);
}
else if (event == QKeySequence::SelectNextChar)
{
setCursorPosition(getCursorCharacterForward(), true);
}
else if (event == QKeySequence::SelectPreviousChar)
{
setCursorPosition(getCursorCharacterBackward(), true);
}
else if (event == QKeySequence::SelectNextWord)
{
setCursorPosition(getCursorWordForward(), true);
}
else if (event == QKeySequence::SelectPreviousWord)
{
setCursorPosition(getCursorWordBackward(), true);
}
else if (event == QKeySequence::SelectNextLine)
{
setCursorPosition(getCursorLineDown(), true);
}
else if (event == QKeySequence::SelectPreviousLine)
{
setCursorPosition(getCursorLineUp(), true);
}
else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock)
{
setCursorPosition(getCursorLineStart(), true);
}
else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock)
{
setCursorPosition(getCursorLineEnd(), true);
}
else if (event == QKeySequence::SelectStartOfDocument)
{
setCursorPosition(getCursorDocumentStart(), true);
}
else if (event == QKeySequence::SelectEndOfDocument)
{
setCursorPosition(getCursorDocumentEnd(), true);
}
else if (event == QKeySequence::DeleteStartOfWord)
{
if (!isReadonly())
{
setCursorPosition(getCursorWordBackward(), true);
performRemoveSelectedText();
}
}
else if (event == QKeySequence::DeleteEndOfWord)
{
if (!isReadonly())
{
setCursorPosition(getCursorWordForward(), true);
performRemoveSelectedText();
}
}
else if (event == QKeySequence::DeleteEndOfLine)
{
if (!isReadonly())
{
setCursorPosition(getCursorLineEnd(), true);
performRemoveSelectedText();
}
}
else if (event == QKeySequence::Deselect)
{
clearSelection();
}
else if (event == QKeySequence::DeleteCompleteLine)
{
if (!isReadonly())
{
m_selectionStart = getCurrentLineTextStart();
m_selectionEnd = getCurrentLineTextEnd();
performRemoveSelectedText();
}
}
else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace)
{
performBackspace();
}
else if (event->key() == Qt::Key_Direction_L)
{
QTextOption option = m_textLayout.textOption();
option.setTextDirection(Qt::LeftToRight);
m_textLayout.setTextOption(qMove(option));
updateTextLayout();
}
else if (event->key() == Qt::Key_Direction_R)
{
QTextOption option = m_textLayout.textOption();
option.setTextDirection(Qt::LeftToRight);
m_textLayout.setTextOption(qMove(option));
updateTextLayout();
}
else if (event->key() == Qt::Key_Up)
{
setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier));
}
else if (event->key() == Qt::Key_Down)
{
setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier));
}
else if (event->key() == Qt::Key_Left)
{
const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward();
setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier));
}
else if (event->key() == Qt::Key_Right)
{
const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward();
setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier));
}
else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
{
performInsertText(QString::fromUtf16(u"\u2028"));
}
else
{
QString text = event->text();
if (!text.isEmpty())
{
performInsertText(text);
}
else
{
event->ignore();
}
}
}
void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength)
{
if (selectionLength > 0)
{
// We are selecting to the right
m_selectionStart = startPosition;
m_selectionEnd = qMin(startPosition + selectionLength, getTextLength());
m_positionCursor = m_selectionEnd;
}
else if (selectionLength < 0)
{
// We are selecting to the left
m_selectionStart = qMax(startPosition + selectionLength, 0);
m_selectionEnd = startPosition;
m_positionCursor = m_selectionStart;
}
else
{
// Clear the selection
m_selectionStart = 0;
m_selectionEnd = 0;
m_positionCursor = startPosition;
}
}
void PDFTextEditPseudowidget::setCursorPosition(int position, bool select)
{
if (select)
{
const bool isTextSelected = this->isTextSelected();
const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor;
const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor;
// Do we have selected text, and cursor is at the end of selected text?
// In this case, we must preserve start of the selection (we are manipulating
// with the end of selection.
if (isCursorAtEndOfSelection)
{
m_selectionStart = qMin(m_selectionStart, position);
m_selectionEnd = qMax(m_selectionStart, position);
}
else if (isCursorAtStartOfSelection)
{
// We must preserve end of the text selection, because we are manipulating
// with start of text selection.
m_selectionStart = qMin(m_selectionEnd, position);
m_selectionEnd = qMax(m_selectionEnd, position);
}
else
{
// Otherwise we are manipulating with cursor
m_selectionStart = qMin(m_positionCursor, position);
m_selectionEnd = qMax(m_positionCursor, position);
}
}
// Why we are clearing text selection, even if we doesn't have it?
// We can have, for example m_selectionStart == m_selectionEnd == 3,
// and we want to have it both zero.
if (!select || !isTextSelected())
{
clearSelection();
}
m_positionCursor = position;
}
void PDFTextEditPseudowidget::setText(const QString& text)
{
clearSelection();
m_editText = text;
setCursorPosition(getPositionEnd(), false);
updateTextLayout();
}
void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength)
{
// Set appearance
qreal fontSize = appearance.getFontSize();
if (qFuzzyIsNull(fontSize))
{
fontSize = rect.height();
}
QFont font(appearance.getFontName());
font.setHintingPreference(QFont::PreferNoHinting);
font.setPixelSize(qCeil(fontSize));
font.setStyleStrategy(QFont::ForceOutline);
m_textLayout.setFont(font);
QTextOption option = m_textLayout.textOption();
option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
option.setAlignment(textAlignment);
option.setUseDesignMetrics(true);
m_textLayout.setTextOption(option);
m_textColor = appearance.getFontColor();
if (!m_textColor.isValid())
{
m_textColor = Qt::black;
}
m_maxTextLength = maxTextLength;
m_widgetRect = rect;
}
void PDFTextEditPseudowidget::setAppearance(const QFont& font,
Qt::Alignment textAlignment,
QRectF rect,
int maxTextLength,
QColor textColor)
{
// Set appearance
m_textLayout.setFont(font);
QTextOption option = m_textLayout.textOption();
option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
option.setAlignment(textAlignment);
option.setUseDesignMetrics(true);
m_textLayout.setTextOption(option);
m_textColor = textColor;
if (!m_textColor.isValid())
{
m_textColor = Qt::black;
}
m_maxTextLength = maxTextLength;
m_widgetRect = rect;
}
void PDFTextEditPseudowidget::performCut()
{
if (isReadonly())
{
return;
}
performCopy();
performRemoveSelectedText();
}
void PDFTextEditPseudowidget::performCopy()
{
if (isTextSelected() && !isPassword())
{
QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard);
}
}
void PDFTextEditPseudowidget::performPaste()
{
// We always insert text, even if it is empty. Because we want
// to erase selected text.
performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard));
}
void PDFTextEditPseudowidget::performClear()
{
if (isReadonly())
{
return;
}
performSelectAll();
performRemoveSelectedText();
}
void PDFTextEditPseudowidget::performSelectAll()
{
m_selectionStart = getPositionStart();
m_selectionEnd = getPositionEnd();
}
void PDFTextEditPseudowidget::performBackspace()
{
if (isReadonly())
{
return;
}
// If we have selection, then delete selected text. If we do not have
// selection, then we delete previous character.
if (!isTextSelected())
{
setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true);
}
performRemoveSelectedText();
}
void PDFTextEditPseudowidget::performDelete()
{
if (isReadonly())
{
return;
}
// If we have selection, then delete selected text. If we do not have
// selection, then we delete previous character.
if (!isTextSelected())
{
setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true);
}
performRemoveSelectedText();
}
void PDFTextEditPseudowidget::performRemoveSelectedText()
{
if (isTextSelected())
{
m_editText.remove(m_selectionStart, getSelectionLength());
setCursorPosition(m_selectionStart, false);
clearSelection();
updateTextLayout();
}
}
void PDFTextEditPseudowidget::performInsertText(const QString& text)
{
if (isReadonly())
{
return;
}
// Insert text at the cursor
performRemoveSelectedText();
m_editText.insert(m_positionCursor, text);
setCursorPosition(m_positionCursor + text.length(), false);
updateTextLayout();
}
QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const
{
QMatrix matrix;
matrix.translate(m_widgetRect.left(), m_widgetRect.bottom());
matrix.scale(1.0, -1.0);
if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor))
{
// Jakub Melka: we must scroll the control, so cursor is always
// visible in the widget area. If we are not editing, this not the
// case, because we always show the text from the beginning.
const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
if (line.isValid())
{
const qreal xCursorPosition = line.cursorToX(m_positionCursor);
if (xCursorPosition >= m_widgetRect.width())
{
const qreal delta = xCursorPosition - m_widgetRect.width();
matrix.translate(-delta, 0.0);
}
// Check, if we aren't outside of y position
const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height();
const qreal lineBottom = lineSpacing * (line.lineNumber() + 1);
if (lineBottom >= m_widgetRect.height())
{
const qreal delta = lineBottom - m_widgetRect.height();
matrix.translate(0.0, -delta);
}
}
}
if (!isMultiline() && !isComb())
{
// If text is single line, then adjust text position to the vertical center
QTextLine textLine = m_textLayout.lineAt(0);
if (textLine.isValid())
{
const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height();
const qreal textBoxHeight = m_widgetRect.height();
if (lineSpacing < textBoxHeight)
{
const qreal delta = (textBoxHeight - lineSpacing) * 0.5;
matrix.translate(0.0, delta);
}
}
}
return matrix;
}
std::vector<int> PDFTextEditPseudowidget::getCursorPositions() const
{
std::vector<int> result;
result.reserve(m_editText.length());
result.push_back(0);
int currentPos = 0;
while (currentPos < m_editText.length())
{
currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters);
result.push_back(currentPos);
}
return result;
}
void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const
{
pdf::PDFPainterStateGuard guard(parameters.painter);
if (parameters.annotation)
{
parameters.boundingRectangle = parameters.annotation->getRectangle();
}
QPalette palette = QApplication::palette();
auto getAdjustedColor = [&parameters](QColor color)
{
if (parameters.invertColors)
{
return invertColor(color);
}
return color;
};
QPainter* painter = parameters.painter;
if (edit)
{
pdf::PDFPainterStateGuard guard(painter);
painter->setPen(getAdjustedColor(Qt::black));
painter->setBrush(Qt::NoBrush);
painter->drawRect(parameters.boundingRectangle);
}
painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip);
painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true);
painter->setPen(getAdjustedColor(Qt::black));
if (isComb())
{
const qreal combCount = qMax(m_maxTextLength, 1);
qreal combWidth = m_widgetRect.width() / combCount;
QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height());
painter->setFont(m_textLayout.font());
QColor textColor = getAdjustedColor(m_textColor);
QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText));
QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight));
std::vector<int> positions = getCursorPositions();
for (size_t i = 1; i < positions.size(); ++i)
{
if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd)
{
// We are in text selection
painter->fillRect(combRect, highlightColor);
painter->setPen(highlightTextColor);
}
else
{
// We are not in text selection
painter->setPen(textColor);
}
int length = positions[i] - positions[i - 1];
QString text = m_displayText.mid(positions[i - 1], length);
painter->drawText(combRect, Qt::AlignCenter, text);
// Draw the cursor?
if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i])
{
QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1);
painter->fillRect(cursorRect, textColor);
}
combRect.translate(combWidth, 0.0);
}
// Draw the cursor onto next unfilled cell?
if (edit && m_positionCursor == getPositionEnd())
{
QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1);
painter->fillRect(cursorRect, textColor);
}
}
else
{
QVector<QTextLayout::FormatRange> selections;
QTextLayout::FormatRange defaultFormat;
defaultFormat.start = getPositionStart();
defaultFormat.length = getTextLength();
defaultFormat.format.clearBackground();
defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern));
// If we are editing, draw selections
if (edit && isTextSelected())
{
QTextLayout::FormatRange before = defaultFormat;
QTextLayout::FormatRange after = defaultFormat;
before.start = getPositionStart();
before.length = m_selectionStart;
after.start = m_selectionEnd;
after.length = getTextLength() - m_selectionEnd;
QTextLayout::FormatRange selectedFormat = defaultFormat;
selectedFormat.start = m_selectionStart;
selectedFormat.length = getSelectionLength();
selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern));
selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern));
selections = { before, selectedFormat, after};
}
else
{
selections.push_back(defaultFormat);
}
// Draw text
m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF());
// If we are editing, also draw text
if (edit && !isReadonly())
{
m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor);
}
}
}
int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const
{
QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit);
QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted();
QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point);
if (isComb())
{
// If it is comb, then characters are spaced equidistantly
const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width());
const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width()));
std::vector<int> positions = getCursorPositions();
if (position < positions.size())
{
return positions[position];
}
return positions.back();
}
else if (m_textLayout.lineCount() > 0)
{
QTextLine line;
qreal yPos = 0.0;
// Find line under cursor
for (int i = 0; i < m_textLayout.lineCount(); ++i)
{
QTextLine currentLine = m_textLayout.lineAt(i);
const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height();
const qreal yNextPos = yPos + lineSpacing;
if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos)
{
line = currentLine;
break;
}
yPos = yNextPos;
}
// If no line is found, select last line
if (!line.isValid())
{
if (textBoxPoint.y() < 0.0)
{
line = m_textLayout.lineAt(0);
}
else
{
line = m_textLayout.lineAt(m_textLayout.lineCount() - 1);
}
}
return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters);
}
return 0;
}
void PDFTextEditPseudowidget::updateTextLayout()
{
// Prepare display text
if (isPassword())
{
m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter);
}
else
{
m_displayText = m_editText;
}
// Perform text layout
m_textLayout.clearLayout();
m_textLayout.setText(m_displayText);
m_textLayout.beginLayout();
QPointF textLinePosition(0.0, 0.0);
while (true)
{
QTextLine textLine = m_textLayout.createLine();
if (!textLine.isValid())
{
// We are finished with layout
break;
}
textLinePosition.ry() += textLine.leading();
textLine.setLineWidth(m_widgetRect.width());
textLine.setPosition(textLinePosition);
textLinePosition.ry() += textLine.height();
}
m_textLayout.endLayout();
// Check length
if (m_maxTextLength > 0)
{
int length = 0;
int currentPos = 0;
while (currentPos < m_editText.length())
{
currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters);
++length;
if (length == m_maxTextLength)
{
break;
}
}
if (currentPos < m_editText.length())
{
m_editText = m_editText.left(currentPos);
m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd());
updateTextLayout();
}
}
}
int PDFTextEditPseudowidget::getSingleStepForward() const
{
// If direction is right-to-left, then move backward (because
// text is painted from right to left.
return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1;
}
int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const
{
int cursor = referencePosition;
if (steps > 0)
{
for (int i = 0; i < steps; ++i)
{
cursor = m_textLayout.nextCursorPosition(cursor, mode);
}
}
else if (steps < 0)
{
for (int i = 0; i < -steps; ++i)
{
cursor = m_textLayout.previousCursorPosition(cursor, mode);
}
}
return cursor;
}
int PDFTextEditPseudowidget::getCurrentLineTextStart() const
{
return m_textLayout.lineForTextPosition(m_positionCursor).textStart();
}
int PDFTextEditPseudowidget::getCurrentLineTextEnd() const
{
QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor);
return textLine.textStart() + textLine.textLength();
}
int PDFTextEditPseudowidget::getCursorLineUp() const
{
QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
const int lineIndex = line.lineNumber() - 1;
if (lineIndex >= 0)
{
QTextLine upLine = m_textLayout.lineAt(lineIndex);
return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters);
}
return m_positionCursor;
}
int PDFTextEditPseudowidget::getCursorLineDown() const
{
QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
const int lineIndex = line.lineNumber() + 1;
if (lineIndex < m_textLayout.lineCount())
{
QTextLine downLine = m_textLayout.lineAt(lineIndex);
return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters);
}
return m_positionCursor;
}
} // namespace pdf

View File

@ -0,0 +1,217 @@
// 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/>.
#ifndef PDFTEXTEDITPSEUDOWIDGET_H
#define PDFTEXTEDITPSEUDOWIDGET_H
#include "pdfglobal.h"
#include "pdfform.h"
#include <QRectF>
#include <QColor>
class QWidget;
class QKeyEvent;
namespace pdf
{
/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline.
/// Passwords can also be edited and editor can be read only.
class PDFTextEditPseudowidget
{
public:
explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags);
void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
void keyPressEvent(QWidget* widget, QKeyEvent* event);
inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); }
inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); }
inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); }
inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); }
inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); }
inline bool isEmpty() const { return m_editText.isEmpty(); }
inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; }
inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); }
inline int getTextLength() const { return m_editText.length(); }
inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; }
inline int getPositionCursor() const { return m_positionCursor; }
inline int getPositionStart() const { return 0; }
inline int getPositionEnd() const { return getTextLength(); }
inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; }
inline const QString& getText() const { return m_editText; }
inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); }
/// Sets (updates) text selection
/// \param startPosition From where we are selecting text
/// \param selectionLength Selection length (positive - to the right, negative - to the left)
void setSelection(int startPosition, int selectionLength);
/// Moves cursor position. It behaves as usual in text boxes,
/// when user moves the cursor. If \p select is true, then
/// selection is updated.
/// \param position New position of the cursor
/// \param select Select text when moving the cursor?
void setCursorPosition(int position, bool select);
/// Sets text content of the widget. This functions sets the text,
/// even if widget is readonly.
/// \param text Text to be set
void setText(const QString& text);
/// Sets widget appearance, such as font, font size, color, text alignment,
/// and rectangle, in which widget resides on page (in page coordinates)
/// \param appearance Appearance
/// \param textAlignment Text alignment
/// \param rect Widget rectangle in page coordinates
/// \param maxTextLength Maximal text length
void setAppearance(const PDFAnnotationDefaultAppearance& appearance,
Qt::Alignment textAlignment,
QRectF rect,
int maxTextLength);
/// Sets widget appearance, such as font, font size, color, text alignment,
/// and rectangle, in which widget resides on page (in page coordinates)
/// \param font Font
/// \param textAlignment Text alignment
/// \param rect Widget rectangle in page coordinates
/// \param maxTextLength Maximal text length
/// \param textColor Color
void setAppearance(const QFont& font,
Qt::Alignment textAlignment,
QRectF rect,
int maxTextLength,
QColor textColor);
void performCut();
void performCopy();
void performPaste();
void performClear();
void performSelectAll();
void performBackspace();
void performDelete();
void performRemoveSelectedText();
void performInsertText(const QString& text);
/// Draw text edit using given parameters
/// \param parameters Parameters
void draw(AnnotationDrawParameters& parameters, bool edit) const;
/// Returns valid cursor position retrieved from position in the widget.
/// \param point Point in page coordinate space
/// \param edit Are we using edit transformations?
/// \returns Cursor position
int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const;
inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); }
inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); }
inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); }
inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); }
inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); }
inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); }
inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); }
inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); }
inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); }
inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); }
inline int getCursorNextLine() const { return getCurrentLineTextEnd(); }
inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); }
const QRectF& getWidgetRect() const { return m_widgetRect; }
QFont getFont() const { return m_textLayout.font(); }
QColor getFontColor() const { return m_textColor; }
private:
/// This function does following things:
/// 1) Clamps edit text to fit maximum length
/// 2) Creates display string from edit string
/// 3) Updates text layout
void updateTextLayout();
/// Returns single step forward, which is determined
/// by cursor move style and layout direction.
int getSingleStepForward() const;
/// Returns single step backward, which is determined
/// by cursor move style and layout direction.
int getSingleStepBackward() const { return -getSingleStepForward(); }
/// Returns next/previous position, by number of steps,
/// using given cursor mode (skipping characters or whole words).
/// \param steps Number of steps to proceed (can be negative number)
/// \param mode Skip mode - letters or words?
int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); }
/// Returns next/previous position from reference cursor position, by number of steps,
/// using given cursor mode (skipping characters or whole words).
/// \param referencePosition Reference cursor position
/// \param steps Number of steps to proceed (can be negative number)
/// \param mode Skip mode - letters or words?
int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const;
/// Returns current line text start position
int getCurrentLineTextStart() const;
/// Returns current line text end position
int getCurrentLineTextEnd() const;
/// Creates text box transform matrix, which transforms from
/// widget space to page space.
/// \param edit Create matrix for text editing?
QMatrix createTextBoxTransformMatrix(bool edit) const;
/// Returns vector of cursor positions (which may be not equal
/// to edit string length, because edit string can contain surrogates,
/// or graphemes, which are single glyphs, but represented by more
/// 16-bit unicode codes).
std::vector<int> getCursorPositions() const;
int getCursorLineUp() const;
int getCursorLineDown() const;
PDFFormField::FieldFlags m_flags;
/// Text edited by the user
QString m_editText;
/// Text, which is displayed. It can differ from text
/// edited by user, in case password is being entered.
QString m_displayText;
/// Text layout
QTextLayout m_textLayout;
/// Character for displaying passwords
QChar m_passwordReplacementCharacter;
int m_selectionStart;
int m_selectionEnd;
int m_positionCursor;
int m_maxTextLength;
QRectF m_widgetRect;
QColor m_textColor;
};
} // namespace pdf
#endif // PDFTEXTEDITPSEUDOWIDGET_H

View File

@ -106,6 +106,14 @@ void PDFWidgetTool::setActive(bool active)
}
}
void PDFWidgetTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
if (PDFWidgetTool* tool = getTopToolstackTool())
{
tool->shortcutOverrideEvent(widget, event);
}
}
void PDFWidgetTool::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
if (PDFWidgetTool* tool = getTopToolstackTool())
@ -130,6 +138,14 @@ void PDFWidgetTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
}
}
void PDFWidgetTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
if (PDFWidgetTool* tool = getTopToolstackTool())
{
tool->mouseDoubleClickEvent(widget, event);
}
}
void PDFWidgetTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
if (PDFWidgetTool* tool = getTopToolstackTool())
@ -194,11 +210,13 @@ PDFWidgetTool* PDFWidgetTool::getTopToolstackTool() const
void PDFWidgetTool::addTool(PDFWidgetTool* tool)
{
tool->setActive(isActive());
m_toolStack.push_back(tool);
}
void PDFWidgetTool::removeTool()
{
m_toolStack.back()->setActive(false);
m_toolStack.pop_back();
}
@ -809,8 +827,12 @@ PDFMagnifierTool* PDFToolManager::getMagnifierTool() const
void PDFToolManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->shortcutOverrideEvent(widget, event);
}
}
void PDFToolManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
@ -854,8 +876,12 @@ void PDFToolManager::mousePressEvent(QWidget* widget, QMouseEvent* event)
void PDFToolManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->mouseDoubleClickEvent(widget, event);
}
}
void PDFToolManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
@ -1070,6 +1096,11 @@ void PDFPickTool::drawPage(QPainter* painter,
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (!isActive())
{
return;
}
// If we are picking rectangles, then draw current selection rectangle
if (m_mode == Mode::Rectangles && m_drawSelectionRectangle && m_pageIndex == pageIndex && !m_pickedPoints.empty())
{
@ -1097,6 +1128,11 @@ void PDFPickTool::drawPage(QPainter* painter,
void PDFPickTool::drawPostRendering(QPainter* painter, QRect rect) const
{
if (!isActive())
{
return;
}
if (m_mode != Mode::Images)
{
m_snapper.drawSnapPoints(painter);

View File

@ -62,6 +62,11 @@ public:
/// Returns action for activating/deactivating this tool
QAction* getAction() const { return m_action; }
/// Handles shortcut override event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
/// Handles key press event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
@ -77,6 +82,11 @@ public:
/// \param event Event
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse double click event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse release event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event

View File

@ -122,6 +122,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget)
rejectMarkFile.close();
}
m_tools[TextTool] = new pdf::PDFCreatePCElementTextTool(widget->getDrawWidgetProxy(), &m_scene, createTextAction, this);
m_tools[FreehandCurveTool] = new pdf::PDFCreatePCElementFreehandCurveTool(widget->getDrawWidgetProxy(), &m_scene, createFreehandCurveAction, this);
m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, this);
m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, this);

View File

@ -79,6 +79,7 @@ private:
enum Tools
{
TextTool,
FreehandCurveTool,
AcceptMarkTool,
RejectMarkTool,