diff --git a/PdfForQtLib/sources/pdfform.cpp b/PdfForQtLib/sources/pdfform.cpp index e8681ef..410715a 100644 --- a/PdfForQtLib/sources/pdfform.cpp +++ b/PdfForQtLib/sources/pdfform.cpp @@ -25,6 +25,7 @@ #include #include #include +#include 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(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(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 diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h index de595c3..4474fd6 100644 --- a/PdfForQtLib/sources/pdfform.h +++ b/PdfForQtLib/sources/pdfform.h @@ -23,6 +23,8 @@ #include "pdfannotation.h" #include "pdfdocumentdrawinterface.h" +#include + #include 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