mirror of https://github.com/JakubMelka/PDF4QT.git
Form field text edit (basic editation)
This commit is contained in:
parent
9f405316a9
commit
29372b6cd0
|
@ -25,6 +25,7 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QClipboard>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
@ -319,13 +320,48 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
|
|||
if (formFieldText)
|
||||
{
|
||||
PDFInteger maxLengthDefault = 0;
|
||||
QByteArray defaultAppearance;
|
||||
Qt::Alignment alignment = 0;
|
||||
|
||||
if (PDFFormFieldText* parentTextField = dynamic_cast<PDFFormFieldText*>(parentField))
|
||||
{
|
||||
maxLengthDefault = parentTextField->getTextMaximalLength();
|
||||
defaultAppearance = parentTextField->getDefaultAppearance();
|
||||
alignment = parentTextField->getAlignment();
|
||||
}
|
||||
|
||||
if (fieldDictionary->hasKey("DA"))
|
||||
{
|
||||
defaultAppearance = loader.readStringFromDictionary(fieldDictionary, "DA");
|
||||
}
|
||||
if (fieldDictionary->hasKey("Q"))
|
||||
{
|
||||
const PDFInteger quadding = loader.readIntegerFromDictionary(fieldDictionary, "Q", -1);
|
||||
switch (quadding)
|
||||
{
|
||||
case 0:
|
||||
alignment = Qt::AlignLeft;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
alignment = Qt::AlignHCenter;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
alignment = Qt::AlignRight;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
alignment |= Qt::AlignVCenter;
|
||||
|
||||
formFieldText->m_maxLength = loader.readIntegerFromDictionary(fieldDictionary, "MaxLen", maxLengthDefault);
|
||||
formFieldText->m_defaultAppearance = defaultAppearance;
|
||||
formFieldText->m_alignment = alignment;
|
||||
formFieldText->m_defaultStyle = loader.readTextStringFromDictionary(fieldDictionary, "DS", QString());
|
||||
formFieldText->m_richTextValue = loader.readTextStringFromDictionary(fieldDictionary, "RV", QString());
|
||||
}
|
||||
|
||||
if (formFieldChoice)
|
||||
|
@ -775,6 +811,17 @@ void PDFFormManager::setFormFieldValue(PDFFormField::SetValueParameters paramete
|
|||
}
|
||||
}
|
||||
|
||||
QRectF PDFFormManager::getWidgetRectangle(const PDFFormWidget& widget) const
|
||||
{
|
||||
if (const PDFDictionary* dictionary = m_document->getDictionaryFromObject(m_document->getObjectByReference(widget.getWidget())))
|
||||
{
|
||||
PDFDocumentDataLoaderDecorator loader(m_document);
|
||||
return loader.readRectangle(dictionary->get("Rect"), QRectF());
|
||||
}
|
||||
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
void PDFFormManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
|
||||
{
|
||||
if (m_focusedEditor)
|
||||
|
@ -1323,9 +1370,618 @@ PDFFormFieldListBoxEditor::PDFFormFieldListBoxEditor(PDFFormManager* formManager
|
|||
}
|
||||
|
||||
PDFFormFieldTextBoxEditor::PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) :
|
||||
BaseClass(formManager, formWidget, parent)
|
||||
BaseClass(formManager, formWidget, parent),
|
||||
m_textEdit(formWidget.getParent()->getFlags())
|
||||
{
|
||||
const PDFFormFieldText* parentField = dynamic_cast<const PDFFormFieldText*>(formWidget.getParent());
|
||||
Q_ASSERT(parentField);
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(formManager->getDocument());
|
||||
|
||||
QByteArray defaultAppearance = parentField->getDefaultAppearance();
|
||||
if (defaultAppearance.isEmpty())
|
||||
{
|
||||
defaultAppearance = formManager->getForm()->getDefaultAppearance().value_or(QByteArray());
|
||||
}
|
||||
Qt::Alignment alignment = parentField->getAlignment();
|
||||
if (!(alignment & Qt::AlignHorizontal_Mask))
|
||||
{
|
||||
switch (formManager->getForm()->getQuadding().value_or(0))
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
alignment |= Qt::AlignLeft;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
alignment |= Qt::AlignHCenter;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
alignment |= Qt::AlignRight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize text edit
|
||||
m_textEdit.setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(formWidget), parentField->getTextMaximalLength());
|
||||
m_textEdit.setText(loader.readTextString(parentField->getValue(), QString()));
|
||||
}
|
||||
|
||||
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::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(getCursorNextLine(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToPreviousLine)
|
||||
{
|
||||
setCursorPosition(getCursorPreviousLine(), 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(getCursorNextLine(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectPreviousLine)
|
||||
{
|
||||
setCursorPosition(getCursorPreviousLine(), 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)
|
||||
{
|
||||
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
|
||||
{
|
||||
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));
|
||||
m_textLayout.setFont(font);
|
||||
|
||||
QTextOption option = m_textLayout.textOption();
|
||||
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
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::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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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 = m_widgetRect.topLeft();
|
||||
|
||||
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
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "pdfannotation.h"
|
||||
#include "pdfdocumentdrawinterface.h"
|
||||
|
||||
#include <QTextLayout>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace pdf
|
||||
|
@ -309,6 +311,10 @@ public:
|
|||
explicit inline PDFFormFieldText() = default;
|
||||
|
||||
PDFInteger getTextMaximalLength() const { return m_maxLength; }
|
||||
const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; }
|
||||
Qt::Alignment getAlignment() const { return m_alignment; }
|
||||
const QString& getRichTextDefaultStyle() const { return m_defaultStyle; }
|
||||
const QString& getRichTextValue() const { return m_richTextValue; }
|
||||
|
||||
private:
|
||||
friend static PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField);
|
||||
|
@ -316,6 +322,18 @@ private:
|
|||
/// Maximal length of text in the field. If zero,
|
||||
/// no maximal length is specified.
|
||||
PDFInteger m_maxLength = 0;
|
||||
|
||||
/// Default appearance
|
||||
QByteArray m_defaultAppearance;
|
||||
|
||||
/// Text field alignment
|
||||
Qt::Alignment m_alignment = 0;
|
||||
|
||||
/// Default style
|
||||
QString m_defaultStyle;
|
||||
|
||||
/// Rich text value
|
||||
QString m_richTextValue;
|
||||
};
|
||||
|
||||
class PDFFormFieldChoice : public PDFFormField
|
||||
|
@ -426,6 +444,148 @@ private:
|
|||
PDFWidgetToFormFieldMapping m_widgetToFormField;
|
||||
};
|
||||
|
||||
/// "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 inline PDFTextEditPseudowidget(PDFFormField::FieldFlags flags);
|
||||
|
||||
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 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);
|
||||
|
||||
void performCut();
|
||||
void performCopy();
|
||||
void performPaste();
|
||||
void performClear();
|
||||
void performSelectAll();
|
||||
void performBackspace();
|
||||
void performDelete();
|
||||
void performRemoveSelectedText();
|
||||
void performInsertText(const QString& text);
|
||||
|
||||
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;
|
||||
|
||||
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); }
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/// Base class for editors of form fields. It enables editation
|
||||
/// of form fields, such as entering text, clicking on check box etc.
|
||||
class PDFFormFieldWidgetEditor : public QObject
|
||||
|
@ -522,6 +682,9 @@ private:
|
|||
public:
|
||||
explicit PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent);
|
||||
virtual ~PDFFormFieldTextBoxEditor() = default;
|
||||
|
||||
private:
|
||||
PDFTextEditPseudowidget m_textEdit;
|
||||
};
|
||||
|
||||
/// Editor for combo boxes
|
||||
|
@ -576,6 +739,8 @@ public:
|
|||
bool hasAcroForm() const { return m_form.getFormType() == PDFForm::FormType::AcroForm; }
|
||||
bool hasXFAForm() const { return m_form.getFormType() == PDFForm::FormType::XFAForm; }
|
||||
|
||||
const PDFForm* getForm() const { return &m_form; }
|
||||
|
||||
/// Returns form field for widget. If widget doesn't have attached form field,
|
||||
/// then nullptr is returned.
|
||||
/// \param widget Widget annotation
|
||||
|
@ -663,6 +828,9 @@ public:
|
|||
/// Tries to set value to the form field
|
||||
void setFormFieldValue(PDFFormField::SetValueParameters parameters);
|
||||
|
||||
/// Get widget rectangle (from annotation)
|
||||
QRectF getWidgetRectangle(const PDFFormWidget& widget) const;
|
||||
|
||||
// interface IDrawWidgetInputInterface
|
||||
|
||||
/// Handles key press event from widget
|
||||
|
|
Loading…
Reference in New Issue