// Copyright (C) 2020 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt 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
// (at your option) any later version.
//
// PdfForQt 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 PDFForQt. If not, see .
#include "pdfform.h"
#include "pdfdocument.h"
#include "pdfdrawspacecontroller.h"
#include "pdfdrawwidget.h"
#include "pdfdocumentbuilder.h"
#include "pdfpainterutils.h"
#include
#include
#include
#include
#include
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);
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); }
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 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;
};
/// Editor for button-like editors
class PDFFormFieldAbstractButtonEditor : public PDFFormFieldWidgetEditor
{
private:
using BaseClass = PDFFormFieldWidgetEditor;
public:
explicit PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldAbstractButtonEditor() = default;
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override;
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
protected:
virtual void click() = 0;
};
/// Editor for push buttons
class PDFFormFieldPushButtonEditor : public PDFFormFieldAbstractButtonEditor
{
private:
using BaseClass = PDFFormFieldAbstractButtonEditor;
public:
explicit PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldPushButtonEditor() = default;
protected:
virtual void click() override;
};
/// Editor for check boxes or radio buttons
class PDFFormFieldCheckableButtonEditor : public PDFFormFieldAbstractButtonEditor
{
private:
using BaseClass = PDFFormFieldAbstractButtonEditor;
public:
explicit PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldCheckableButtonEditor() = default;
protected:
virtual void click() override;
};
/// Editor for text fields
class PDFFormFieldTextBoxEditor : public PDFFormFieldWidgetEditor
{
private:
using BaseClass = PDFFormFieldWidgetEditor;
public:
explicit PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldTextBoxEditor() = default;
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void reloadValue() override;
virtual bool isEditorDrawEnabled() const override { return m_hasFocus; }
virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override;
/// Initializes text edit using actual form field value,
/// font, color for text edit appearance.
/// \param textEdit Text editor
void initializeTextEdit(PDFTextEditPseudowidget* textEdit) const;
protected:
virtual void setFocusImpl(bool focused);
private:
PDFTextEditPseudowidget m_textEdit;
};
/// Editor for combo boxes
class PDFFormFieldComboBoxEditor : public PDFFormFieldWidgetEditor
{
private:
using BaseClass = PDFFormFieldWidgetEditor;
public:
explicit PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldComboBoxEditor() = default;
};
/// "Pseudo" widget, which is emulating list box. It can contain scrollbar.
class PDFListBoxPseudowidget
{
public:
explicit PDFListBoxPseudowidget(PDFFormField::FieldFlags flags);
using Options = PDFFormFieldChoice::Options;
inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); }
inline bool isMultiSelect() const { return m_flags.testFlag(PDFFormField::MultiSelect); }
inline bool isCommitOnSelectionChange() const { return m_flags.testFlag(PDFFormField::CommitOnSelectionChange); }
void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
void keyPressEvent(QWidget* widget, QKeyEvent* event);
/// 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 options Options selectable by list box
/// \param topIndex Index of first visible item
void setAppearance(const PDFAnnotationDefaultAppearance& appearance,
Qt::Alignment textAlignment,
QRectF rect,
const Options& options,
int topIndex,
std::set selection);
/// Draw text edit using given parameters
/// \param parameters Parameters
void draw(AnnotationDrawParameters& parameters, bool edit) const;
/// Sets current selection. If commit on selection change flag
/// is set, then contents of the widgets are commited. New selection
/// is not set, if field is readonly and \p force is false
/// \param selection New selection
/// \param force Set selection even if readonly
void setSelection(std::set selection, bool force);
/// Returns active item selection
const std::set& getSelection() const { return m_selection; }
/// Returns top index
int getTopItemIndex() const { return m_topIndex; }
/// Set top item index
/// \param index Top item index
void setTopItemIndex(int index);
private:
int getStartItemIndex() const { return 0; }
int getEndItemIndex() const { return m_options.empty() ? 0 : int(m_options.size() - 1); }
int getSingleStep() const { return 1; }
int getPageStep() const { return qMax(getViewportRowCount() - 1, getSingleStep()); }
int getViewportRowCount() const { return qFloor(m_widgetRect.height() / m_lineSpacing); }
/// Returns true, if row with this index is visible in the widget
/// (it is in viewport).
/// \param index Index
bool isVisible(int index) const;
/// Scrolls the list box in such a way, that index is visible
/// \param index Index to scroll to
void scrollTo(int index);
/// Sets current item and updates selection based on keyboard modifiers
/// \param index New index
/// \param modifiers Keyboard modifiers
void setCurrentItem(int index, Qt::KeyboardModifiers modifiers);
/// Moves current item by offset (negative is up, positive is down)
/// \param offset Offset
/// \param modifiers Keyboard modifiers
void moveCurrentItemIndexByOffset(int offset, Qt::KeyboardModifiers modifiers) { setCurrentItem(m_currentIndex + offset, modifiers); }
/// Returns true, if list box has continuous selection
bool hasContinuousSelection() const;
/// Returns valid index from index (i.e. index which ranges from first
/// row to the last one). If index itself is valid, then it is returned.
/// \param index Index, from which we want to get valid one
int getValidIndex(int index) const;
PDFFormField::FieldFlags m_flags;
Options m_options;
Qt::Alignment m_textAlignment = 0;
int m_topIndex = 0;
int m_currentIndex = 0;
std::set m_selection;
QFont m_font;
PDFReal m_lineSpacing = 0.0;
QRectF m_widgetRect;
QColor m_textColor;
};
/// Editor for list boxes
class PDFFormFieldListBoxEditor : public PDFFormFieldWidgetEditor
{
private:
using BaseClass = PDFFormFieldWidgetEditor;
public:
explicit PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
virtual ~PDFFormFieldListBoxEditor() = default;
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override;
virtual void reloadValue() override;
virtual bool isEditorDrawEnabled() const override { return m_hasFocus; }
virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override;
/// Initializes list box using actual form field value,
/// font, color for text edit appearance.
/// \param listBox List box
void initializeListBox(PDFListBoxPseudowidget* listBox) const;
protected:
virtual void setFocusImpl(bool focused);
private:
/// Returns list of selected items parsed from the object.
/// If object is invalid, empty selection is returned. Indices are
/// primarily read from \p indices object, if it fails, indices
/// are being read from \p value object.
/// \param value Selection (string or array of strings)
/// \param indices Selected indices
std::set getSelectedItems(PDFObject value, PDFObject indices) const;
/// Commits data to the PDF document
void commit();
PDFListBoxPseudowidget m_listBox;
};
PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object)
{
PDFForm form;
if (const PDFDictionary* formDictionary = document->getDictionaryFromObject(object))
{
PDFDocumentDataLoaderDecorator loader(document);
std::vector fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields");
form.m_formFields.reserve(fieldRoots.size());
for (const PDFObjectReference& fieldRootReference : fieldRoots)
{
form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), fieldRootReference, nullptr));
}
form.m_formType = FormType::AcroForm;
form.m_needAppearances = loader.readBooleanFromDictionary(formDictionary, "NeedAppearances", false);
form.m_signatureFlags = static_cast(loader.readIntegerFromDictionary(formDictionary, "SigFlags", 0));
form.m_calculationOrder = loader.readReferenceArrayFromDictionary(formDictionary, "CO");
form.m_resources = formDictionary->get("DR");
form.m_defaultAppearance = loader.readOptionalStringFromDictionary(formDictionary, "DA");
form.m_quadding = loader.readOptionalIntegerFromDictionary(formDictionary, "Q");
form.m_xfa = formDictionary->get("XFA");
if (!form.m_xfa.isNull())
{
// Jakub Melka: handle XFA form
form.m_formType = FormType::XFAForm;
}
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
form.updateWidgetToFormFieldMapping();
// If we have form, then we must also look for 'rogue' form fields, which are
// incorrectly not in the 'Fields' entry of this form. We do this by iterating
// all pages, and their annotations and try to find these 'rogue' fields.
bool rogueFieldFound = false;
const size_t pageCount = document->getCatalog()->getPageCount();
for (size_t i = 0; i < pageCount; ++i)
{
const PDFPage* page = document->getCatalog()->getPage(i);
for (PDFObjectReference annotationReference : page->getAnnotations())
{
const PDFDictionary* annotationDictionary = document->getDictionaryFromObject(document->getObjectByReference(annotationReference));
if (form.m_widgetToFormField.count(annotationReference))
{
// This widget/form field is already present
continue;
}
if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget" &&
!annotationDictionary->hasKey("Kids"))
{
rogueFieldFound = true;
form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr));
}
}
}
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
if (rogueFieldFound)
{
form.updateWidgetToFormFieldMapping();
}
}
return form;
}
void PDFForm::updateWidgetToFormFieldMapping()
{
m_widgetToFormField.clear();
if (isAcroForm() || isXFAForm())
{
for (const PDFFormFieldPointer& formFieldPtr : getFormFields())
{
formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField);
}
}
}
Qt::Alignment PDFForm::getDefaultAlignment() const
{
Qt::Alignment alignment = Qt::AlignVCenter;
switch (getQuadding().value_or(0))
{
default:
case 0:
alignment |= Qt::AlignLeft;
break;
case 1:
alignment |= Qt::AlignHCenter;
break;
case 2:
alignment |= Qt::AlignRight;
break;
}
return alignment;
}
const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) const
{
auto it = m_widgetToFormField.find(widget);
if (it != m_widgetToFormField.cend())
{
return it->second;
}
return nullptr;
}
PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget)
{
auto it = m_widgetToFormField.find(widget);
if (it != m_widgetToFormField.cend())
{
return it->second;
}
return nullptr;
}
void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping)
{
for (const auto& childField : m_childFields)
{
childField->fillWidgetToFormFieldMapping(mapping);
}
for (const PDFFormWidget& formWidget : m_widgets)
{
mapping[formWidget.getWidget()] = formWidget.getParent();
}
}
void PDFFormField::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue)
{
Q_ASSERT(storage);
if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference())))
{
m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentValue;
}
for (const PDFFormFieldPointer& childField : m_childFields)
{
childField->reloadValue(storage, m_value);
}
}
void PDFFormField::apply(const std::function& functor) const
{
functor(this);
for (const PDFFormFieldPointer& childField : m_childFields)
{
childField->apply(functor);
}
}
void PDFFormField::modify(const std::function& functor)
{
functor(this);
for (const PDFFormFieldPointer& childField : m_childFields)
{
childField->modify(functor);
}
}
PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField)
{
PDFFormFieldPointer result;
if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference)))
{
PDFFormField* formField = nullptr;
PDFFormFieldButton* formFieldButton = nullptr;
PDFFormFieldText* formFieldText = nullptr;
PDFFormFieldChoice* formFieldChoice = nullptr;
PDFFormFieldSignature* formFieldSignature = nullptr;
constexpr const std::array, 4> fieldTypes = {
std::pair{ "Btn", FieldType::Button },
std::pair{ "Tx", FieldType::Text },
std::pair{ "Ch", FieldType::Choice },
std::pair{ "Sig", FieldType::Signature }
};
PDFDocumentDataLoaderDecorator loader(storage);
FieldType formFieldType = parentField ? parentField->getFieldType() : FieldType::Invalid;
if (fieldDictionary->hasKey("FT"))
{
formFieldType = loader.readEnumByName(fieldDictionary->get("FT"), fieldTypes.begin(), fieldTypes.end(), FieldType::Invalid);
}
switch (formFieldType)
{
case FieldType::Invalid:
formField = new PDFFormField();
break;
case FieldType::Button:
formFieldButton = new PDFFormFieldButton();
formField = formFieldButton;
break;
case FieldType::Text:
formFieldText = new PDFFormFieldText();
formField = formFieldText;
break;
case FieldType::Choice:
formFieldChoice = new PDFFormFieldChoice();
formField = formFieldChoice;
break;
case FieldType::Signature:
formFieldSignature = new PDFFormFieldSignature();
formField = formFieldSignature;
break;
default:
Q_ASSERT(false);
break;
}
result.reset(formField);
PDFObject parentV = parentField ? parentField->getValue() : PDFObject();
PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject();
FieldFlags parentFlags = parentField ? parentField->getFlags() : None;
formField->m_selfReference = reference;
formField->m_fieldType = formFieldType;
formField->m_parentField = parentField;
formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString());
formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString());
formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString());
formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags;
formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV;
formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV;
formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA"), fieldDictionary->get("A"));
// Generate fully qualified name. If partial name is empty, then fully qualified name
// is generated from parent fully qualified name (i.e. it is same as parent's name).
// This is according the PDF specification 1.7.
QStringList names;
if (parentField)
{
names << parentField->getName(FullyQualified);
}
names << formField->m_fieldNames[Partial];
names.removeAll(QString());
formField->m_fieldNames[FullyQualified] = names.join(".");
std::vector kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids");
if (kids.empty())
{
// This means, that field's dictionary is merged with annotation's dictionary,
// so, we will add pointer to self to the form widgets. But we must test, if we
// really have merged annotation's dictionary - we test it by checking for 'Subtype'
// presence.
if (loader.readNameFromDictionary(fieldDictionary, "Subtype") == "Widget")
{
formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, reference, formField));
}
}
else
{
// Otherwise we must scan all references, and determine, if kid is another form field,
// or it is a widget annotation. Widget annotations has required field 'Subtype', which
// has value 'Widget', form field has required field 'Parent' (for non-root fields).
for (const PDFObjectReference& kid : kids)
{
if (const PDFDictionary* childDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(kid)))
{
const bool isWidget = loader.readNameFromDictionary(childDictionary, "Subtype") == "Widget";
const bool isField = loader.readReferenceFromDictionary(childDictionary, "Parent").isValid();
if (isField)
{
// This is form field (potentially merged with widget)
formField->m_childFields.emplace_back(PDFFormField::parse(storage, kid, formField));
}
else if (isWidget)
{
// This is pure widget (with no form field)
formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, kid, formField));
}
}
}
}
if (formFieldButton)
{
formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt"));
}
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)
{
// Parse options - it is array of options values. Option value can be either a single text
// string, or array of two values - export value and text to be displayed.
PDFObject options = storage->getObject(fieldDictionary->get("Opt"));
if (options.isArray())
{
const PDFArray* optionsArray = options.getArray();
formFieldChoice->m_options.reserve(optionsArray->getCount());
for (size_t i = 0; i < optionsArray->getCount(); ++i)
{
PDFFormFieldChoice::Option option;
PDFObject optionItemObject = storage->getObject(optionsArray->getItem(i));
if (optionItemObject.isArray())
{
QStringList stringList = loader.readTextStringList(optionItemObject);
if (stringList.size() == 2)
{
option.exportString = stringList[0];
option.userString = stringList[1];
}
}
else
{
option.userString = loader.readTextString(optionItemObject, QString());
option.exportString = option.userString;
}
formFieldChoice->m_options.emplace_back(qMove(option));
}
}
formFieldChoice->m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0);
formFieldChoice->m_selection = fieldDictionary->get("I");
}
}
return result;
}
bool PDFFormField::setValue(const SetValueParameters& parameters)
{
Q_UNUSED(parameters);
// Default behaviour: return false, value cannot be set
return false;
}
PDFFormWidget::PDFFormWidget(PDFObjectReference page, PDFObjectReference widget, PDFFormField* parentField, PDFAnnotationAdditionalActions actions) :
m_page(page),
m_widget(widget),
m_parentField(parentField),
m_actions(qMove(actions))
{
}
PDFFormWidget PDFFormWidget::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField)
{
PDFObjectReference pageReference;
PDFAnnotationAdditionalActions actions;
if (const PDFDictionary* annotationDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference)))
{
PDFDocumentDataLoaderDecorator loader(storage);
pageReference = loader.readReferenceFromDictionary(annotationDictionary, "P");
actions = PDFAnnotationAdditionalActions::parse(storage, annotationDictionary->get("AA"), annotationDictionary->get("A"));
}
return PDFFormWidget(pageReference, reference, parentField, qMove(actions));
}
PDFFormFieldButton::ButtonType PDFFormFieldButton::getButtonType() const
{
if (m_fieldFlags.testFlag(PushButton))
{
return ButtonType::PushButton;
}
else if (m_fieldFlags.testFlag(Radio))
{
return ButtonType::RadioButton;
}
return ButtonType::CheckBox;
}
QByteArray PDFFormFieldButton::getOnAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget)
{
Q_ASSERT(formManager);
Q_ASSERT(widget);
const PDFDocument* document = formManager->getDocument();
Q_ASSERT(document);
if (const PDFDictionary* dictionary = document->getDictionaryFromObject(document->getObjectByReference(widget->getWidget())))
{
PDFAppeareanceStreams streams = PDFAppeareanceStreams::parse(&document->getStorage(), dictionary->get("AP"));
QByteArrayList states = streams.getAppearanceStates(PDFAppeareanceStreams::Appearance::Normal);
for (const QByteArray& state : states)
{
if (!state.isEmpty() && state != "Off")
{
return state;
}
}
}
return QByteArray();
}
QByteArray PDFFormFieldButton::getOffAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget)
{
Q_UNUSED(formManager);
Q_UNUSED(widget);
// 'Off' value is specified by PDF 1.7 specification. It has always value 'Off'.
// 'On' values can have different appearance states.
return "Off";
}
bool PDFFormFieldButton::setValue(const SetValueParameters& parameters)
{
// Do not allow to set value to push buttons
if (getFlags().testFlag(PushButton))
{
return false;
}
// If form field is readonly, and scope is user (form field is changed by user,
// not by calculated value), then we must not allow value change.
if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User)
{
return false;
}
Q_ASSERT(parameters.formManager);
Q_ASSERT(parameters.modifier);
Q_ASSERT(parameters.value.isName());
PDFDocumentBuilder* builder = parameters.modifier->getBuilder();
QByteArray state = parameters.value.getString();
parameters.modifier->markFormFieldChanged();
builder->setFormFieldValue(getSelfReference(), parameters.value);
// Change widget appearance states
const bool isRadio = getFlags().testFlag(Radio);
const bool isRadioInUnison = getFlags().testFlag(RadiosInUnison);
const bool isSameValueForAllWidgets = !isRadio || isRadioInUnison;
const bool isAllowedToCheckAllOff = !getFlags().testFlag(NoToggleToOff);
bool hasWidgets = !m_widgets.empty();
bool isAnyWidgetToggledOn = false;
for (const PDFFormWidget& formWidget : getWidgets())
{
QByteArray onState = PDFFormFieldButton::getOnAppearanceState(parameters.formManager, &formWidget);
// We set appearance to 'On' if following two conditions both hold:
// 1) State equals to widget's "On" state
// 2) Either we are setting value to invoking widget, or setting of same
// value to other widgets is allowed (it is a checkbox, or radio in unison)
if (state == onState && (isSameValueForAllWidgets || formWidget.getWidget() == parameters.invokingWidget))
{
isAnyWidgetToggledOn = true;
builder->setAnnotationAppearanceState(formWidget.getWidget(), onState);
}
else
{
QByteArray offState = PDFFormFieldButton::getOffAppearanceState(parameters.formManager, &formWidget);
builder->setAnnotationAppearanceState(formWidget.getWidget(), offState);
}
parameters.modifier->markAnnotationsChanged();
}
// We must check, if correct value has been set. If form field has no widgets,
// but has same qualified name, then no check is performed (just form field value is set
// to same value in all form fields with same qualified name, according to the PDF specification.
if (hasWidgets && !isAnyWidgetToggledOn && !isAllowedToCheckAllOff)
{
return false;
}
return true;
}
PDFFormManager::PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent) :
BaseClass(parent),
m_proxy(proxy),
m_annotationManager(nullptr),
m_document(nullptr),
m_flags(getDefaultApperanceFlags()),
m_focusedEditor(nullptr)
{
Q_ASSERT(proxy);
}
PDFFormManager::~PDFFormManager()
{
clearEditors();
}
PDFAnnotationManager* PDFFormManager::getAnnotationManager() const
{
return m_annotationManager;
}
void PDFFormManager::setAnnotationManager(PDFAnnotationManager* annotationManager)
{
m_annotationManager = annotationManager;
}
const PDFDocument* PDFFormManager::getDocument() const
{
return m_document;
}
void PDFFormManager::setDocument(const PDFModifiedDocument& document)
{
if (m_document != document)
{
m_document = document;
if (document.hasReset())
{
if (m_document)
{
m_form = PDFForm::parse(m_document, m_document->getCatalog()->getFormObject());
}
else
{
// Clean the form
m_form = PDFForm();
}
updateFormWidgetEditors();
}
else if (document.hasFlag(PDFModifiedDocument::FormField))
{
// Just update field values
updateFieldValues();
}
}
}
PDFFormManager::FormAppearanceFlags PDFFormManager::getAppearanceFlags() const
{
return m_flags;
}
void PDFFormManager::setAppearanceFlags(FormAppearanceFlags flags)
{
m_flags = flags;
}
bool PDFFormManager::hasFormFieldWidgetText(PDFObjectReference widgetAnnotation) const
{
if (const PDFFormField* formField = getFormFieldForWidget(widgetAnnotation))
{
switch (formField->getFieldType())
{
case PDFFormField::FieldType::Text:
return true;
case PDFFormField::FieldType::Choice:
{
PDFFormField::FieldFlags flags = formField->getFlags();
return flags.testFlag(PDFFormField::Combo) && flags.testFlag(PDFFormField::Edit);
}
default:
break;
}
}
return false;
}
PDFFormWidgets PDFFormManager::getWidgets() const
{
PDFFormWidgets result;
auto functor = [&result](const PDFFormField* formField)
{
const PDFFormWidgets& widgets = formField->getWidgets();
result.insert(result.cend(), widgets.cbegin(), widgets.cend());
};
apply(functor);
return result;
}
void PDFFormManager::apply(const std::function& functor) const
{
for (const PDFFormFieldPointer& childField : m_form.getFormFields())
{
childField->apply(functor);
}
}
void PDFFormManager::modify(const std::function& functor) const
{
for (const PDFFormFieldPointer& childField : m_form.getFormFields())
{
childField->modify(functor);
}
}
void PDFFormManager::setFocusToEditor(PDFFormFieldWidgetEditor* editor)
{
if (m_focusedEditor != editor)
{
if (m_focusedEditor)
{
m_focusedEditor->setFocus(false);
}
m_focusedEditor = editor;
if (m_focusedEditor)
{
m_focusedEditor->setFocus(true);
}
// Request repaint, because focus changed
Q_ASSERT(m_proxy);
m_proxy->repaintNeeded();
}
}
bool PDFFormManager::focusNextPrevFormField(bool next)
{
if (m_widgetEditors.empty())
{
return false;
}
std::vector::const_iterator newFocusIterator = m_widgetEditors.cend();
if (!m_focusedEditor)
{
// We are setting a new focus
if (next)
{
newFocusIterator = m_widgetEditors.cbegin();
}
else
{
newFocusIterator = std::prev(m_widgetEditors.cend());
}
}
else
{
std::vector::const_iterator it = std::find(m_widgetEditors.cbegin(), m_widgetEditors.cend(), m_focusedEditor);
Q_ASSERT(it != m_widgetEditors.cend());
if (next)
{
newFocusIterator = std::next(it);
}
else if (it != m_widgetEditors.cbegin())
{
newFocusIterator = std::prev(it);
}
}
if (newFocusIterator != m_widgetEditors.cend())
{
setFocusToEditor(*newFocusIterator);
return true;
}
else
{
// Jakub Melka: We must remove focus out of editor, because
setFocusToEditor(nullptr);
}
return false;
}
bool PDFFormManager::isFocused(PDFObjectReference widget) const
{
if (m_focusedEditor)
{
return m_focusedEditor->getWidgetAnnotation() == widget;
}
return false;
}
const PDFAction* PDFFormManager::getAction(PDFAnnotationAdditionalActions::Action actionType, const PDFFormWidget* widget)
{
if (const PDFAction* action = widget->getAction(actionType))
{
return action;
}
for (const PDFFormField* formField = widget->getParent(); formField; formField = formField->getParentField())
{
if (const PDFAction* action = formField->getAction(actionType))
{
return action;
}
}
return nullptr;
}
void PDFFormManager::setFormFieldValue(PDFFormField::SetValueParameters parameters)
{
if (!m_document)
{
// This can happen, when we are closing the document and some editor is opened
return;
}
Q_ASSERT(parameters.invokingFormField);
Q_ASSERT(parameters.invokingWidget.isValid());
parameters.formManager = this;
parameters.scope = PDFFormField::SetValueParameters::Scope::User;
PDFDocumentModifier modifier(m_document);
modifier.getBuilder()->setFormManager(this);
parameters.modifier = &modifier;
if (parameters.invokingFormField->setValue(parameters))
{
// We must also set dependent fields with same name
QString qualifiedFormFieldName = parameters.invokingFormField->getName(PDFFormField::NameType::FullyQualified);
if (!qualifiedFormFieldName.isEmpty())
{
parameters.scope = PDFFormField::SetValueParameters::Scope::Internal;
auto updateDependentField = [¶meters, &qualifiedFormFieldName](PDFFormField* formField)
{
if (parameters.invokingFormField == formField)
{
// Do not update self
return;
}
if (qualifiedFormFieldName == formField->getName(PDFFormField::NameType::FullyQualified))
{
formField->setValue(parameters);
}
};
modify(updateDependentField);
}
if (modifier.finalize())
{
emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
}
}
}
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::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
if (m_focusedEditor)
{
m_focusedEditor->shortcutOverrideEvent(widget, event);
}
}
void PDFFormManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
if (m_focusedEditor)
{
m_focusedEditor->keyPressEvent(widget, event);
}
}
void PDFFormManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
{
if (m_focusedEditor)
{
m_focusedEditor->keyReleaseEvent(widget, event);
}
}
void PDFFormManager::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
if (!hasForm())
{
return;
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
if (info.isValid())
{
Q_ASSERT(info.editor);
// We try to set focus on editor
if (event->button() == Qt::LeftButton)
{
setFocusToEditor(info.editor);
}
info.editor->mousePressEvent(widget, event, info.mousePosition);
grabMouse(info, event);
}
else if (!isMouseGrabbed())
{
// Mouse is not grabbed, user clicked elsewhere, unfocus editor
setFocusToEditor(nullptr);
}
}
void PDFFormManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
{
if (!hasForm())
{
return;
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
if (info.isValid())
{
Q_ASSERT(info.editor);
info.editor->mouseDoubleClickEvent(widget, event, info.mousePosition);
// If mouse is grabbed, then event is accepted always (because
// we get Press event, when we grabbed the mouse, then we will
// wait for corresponding release event while all mouse move events
// will be accepted, even if editor doesn't accept them.
if (isMouseGrabbed())
{
event->accept();
}
}
}
void PDFFormManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
if (!hasForm())
{
return;
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
if (info.isValid())
{
Q_ASSERT(info.editor);
info.editor->mouseReleaseEvent(widget, event, info.mousePosition);
ungrabMouse(info, event);
}
}
void PDFFormManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
if (!hasForm())
{
return;
}
MouseEventInfo info = getMouseEventInfo(widget, event->pos());
if (info.isValid())
{
Q_ASSERT(info.editor);
info.editor->mouseMoveEvent(widget, event, info.mousePosition);
// If mouse is grabbed, then event is accepted always (because
// we get Press event, when we grabbed the mouse, then we will
// wait for corresponding release event while all mouse move events
// will be accepted, even if editor doesn't accept them.
if (isMouseGrabbed())
{
event->accept();
}
}
}
void PDFFormManager::wheelEvent(QWidget* widget, QWheelEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
// We will accept mouse wheel events, if we are grabbing the mouse.
// We do not want to zoom in/zoom out while grabbing.
if (isMouseGrabbed())
{
event->accept();
}
}
void PDFFormManager::grabMouse(const MouseEventInfo& info, QMouseEvent* event)
{
if (event->type() == QEvent::MouseButtonDblClick)
{
// Double clicks doesn't grab the mouse
return;
}
Q_ASSERT(event->type() == QEvent::MouseButtonPress);
if (isMouseGrabbed())
{
// If mouse is already grabbed, then when new mouse button is pressed,
// we just increase nesting level and accept the mouse event. We are
// accepting all mouse events, if mouse is grabbed.
++m_mouseGrabInfo.mouseGrabNesting;
event->accept();
}
else if (event->isAccepted())
{
// Event is accepted and we are not grabbing the mouse. We must start
// grabbing the mouse.
Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0);
++m_mouseGrabInfo.mouseGrabNesting;
m_mouseGrabInfo.info = info;
}
}
void PDFFormManager::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event)
{
Q_UNUSED(info);
Q_ASSERT(event->type() == QEvent::MouseButtonRelease);
if (isMouseGrabbed())
{
// Mouse is being grabbed, decrease nesting level. We must also accept
// mouse release event, because mouse is being grabbed.
--m_mouseGrabInfo.mouseGrabNesting;
event->accept();
if (!isMouseGrabbed())
{
m_mouseGrabInfo.info = MouseEventInfo();
}
}
Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0);
}
PDFFormManager::MouseEventInfo PDFFormManager::getMouseEventInfo(QWidget* widget, QPoint point)
{
MouseEventInfo result;
if (isMouseGrabbed())
{
result = m_mouseGrabInfo.info;
result.mousePosition = result.deviceToWidget.map(point);
return result;
}
std::vector currentPages = m_proxy->getWidget()->getDrawWidget()->getCurrentPages();
if (!m_annotationManager->hasAnyPageAnnotation(currentPages))
{
// All pages doesn't have annotation
return result;
}
PDFWidgetSnapshot snapshot = m_proxy->getSnapshot();
for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items)
{
const PDFAnnotationManager::PageAnnotations& pageAnnotations = m_annotationManager->getPageAnnotations(snapshotItem.pageIndex);
for (const PDFAnnotationManager::PageAnnotation& pageAnnotation : pageAnnotations.annotations)
{
if (pageAnnotation.annotation->isReplyTo())
{
// Annotation is reply to another annotation, do not interact with it
continue;
}
if (pageAnnotation.annotation->getType() != AnnotationType::Widget)
{
// Annotation is not widget annotation (form field), do not interact with it
continue;
}
QRectF annotationRect = pageAnnotation.annotation->getRectangle();
QMatrix widgetToDevice = m_annotationManager->prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect);
QPainterPath path;
path.addRect(annotationRect);
path = widgetToDevice.map(path);
if (path.contains(point))
{
if (PDFFormField* formField = getFormFieldForWidget(pageAnnotation.annotation->getSelfReference()))
{
result.formField = formField;
result.deviceToWidget = widgetToDevice.inverted();
result.mousePosition = result.deviceToWidget.map(point);
result.editor = getEditor(formField);
return result;
}
}
}
}
return result;
}
const std::optional& PDFFormManager::getCursor() const
{
static const std::optional dummy;
return dummy;
}
void PDFFormManager::clearEditors()
{
qDeleteAll(m_widgetEditors);
m_widgetEditors.clear();
}
void PDFFormManager::updateFormWidgetEditors()
{
setFocusToEditor(nullptr);
clearEditors();
for (PDFFormWidget widget : getWidgets())
{
const PDFFormField* formField = widget.getParent();
switch (formField->getFieldType())
{
case PDFFormField::FieldType::Button:
{
Q_ASSERT(dynamic_cast(formField));
const PDFFormFieldButton* formFieldButton = static_cast(formField);
switch (formFieldButton->getButtonType())
{
case PDFFormFieldButton::ButtonType::PushButton:
{
m_widgetEditors.push_back(new PDFFormFieldPushButtonEditor(this, widget));
break;
}
case PDFFormFieldButton::ButtonType::RadioButton:
case PDFFormFieldButton::ButtonType::CheckBox:
{
m_widgetEditors.push_back(new PDFFormFieldCheckableButtonEditor(this, widget));
break;
}
default:
Q_ASSERT(false);
break;
}
break;
}
case PDFFormField::FieldType::Text:
{
m_widgetEditors.push_back(new PDFFormFieldTextBoxEditor(this, widget));
break;
}
case PDFFormField::FieldType::Choice:
{
Q_ASSERT(dynamic_cast(formField));
const PDFFormFieldChoice* formFieldChoice = static_cast(formField);
if (formFieldChoice->isComboBox())
{
m_widgetEditors.push_back(new PDFFormFieldComboBoxEditor(this, widget));
}
else if (formFieldChoice->isListBox())
{
m_widgetEditors.push_back(new PDFFormFieldListBoxEditor(this, widget));
}
else
{
// Uknown field choice
Q_ASSERT(false);
}
break;
}
case PDFFormField::FieldType::Signature:
// Signature fields doesn't have editor
break;
default:
Q_ASSERT(false);
break;
}
}
}
void PDFFormManager::updateFieldValues()
{
if (m_document)
{
for (const PDFFormFieldPointer& childField : m_form.getFormFields())
{
childField->reloadValue(&m_document->getStorage(), PDFObject());
}
for (PDFFormFieldWidgetEditor* editor : m_widgetEditors)
{
editor->reloadValue();
}
}
}
PDFFormFieldWidgetEditor* PDFFormManager::getEditor(const PDFFormField* formField) const
{
for (PDFFormFieldWidgetEditor* editor : m_widgetEditors)
{
if (editor->getFormField() == formField)
{
return editor;
}
}
return nullptr;
}
PDFFormFieldWidgetEditor::PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
m_formManager(formManager),
m_formWidget(formWidget),
m_hasFocus(false)
{
Q_ASSERT(m_formManager);
}
void PDFFormFieldWidgetEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFFormFieldWidgetEditor::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFFormFieldWidgetEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFFormFieldWidgetEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
Q_UNUSED(widget);
Q_UNUSED(event);
Q_UNUSED(mousePagePosition);
}
void PDFFormFieldWidgetEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
Q_UNUSED(widget);
Q_UNUSED(event);
Q_UNUSED(mousePagePosition);
}
void PDFFormFieldWidgetEditor::mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
Q_UNUSED(widget);
Q_UNUSED(event);
Q_UNUSED(mousePagePosition);
}
void PDFFormFieldWidgetEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
Q_UNUSED(widget);
Q_UNUSED(event);
Q_UNUSED(mousePagePosition);
}
void PDFFormFieldWidgetEditor::setFocus(bool hasFocus)
{
if (m_hasFocus != hasFocus)
{
m_hasFocus = hasFocus;
setFocusImpl(m_hasFocus);
}
}
void PDFFormFieldWidgetEditor::draw(AnnotationDrawParameters& parameters, bool edit) const
{
Q_UNUSED(parameters);
Q_UNUSED(edit)
}
void PDFFormFieldWidgetEditor::performKeypadNavigation(QWidget* widget, QKeyEvent* event)
{
int key = event->key();
const bool isLeft = key == Qt::Key_Left;
const bool isRight = key == Qt::Key_Right;
const bool isDown = key == Qt::Key_Down;
const bool isHorizontal = isLeft || isRight;
Qt::NavigationMode navigationMode = Qt::NavigationModeKeypadDirectional;
#ifdef QT_KEYPAD_NAVIGATION
navigationMode = QApplication::navigationMode();
#endif
switch (navigationMode)
{
case Qt::NavigationModeKeypadTabOrder:
{
// According the Qt's documentation, Up/Down arrows are used
// to change focus. So, if user pressed Left/Right, we must
// ignore this event.
if (isHorizontal)
{
return;
}
break;
}
case Qt::NavigationModeKeypadDirectional:
// Default behaviour
break;
default:
// Nothing happens
return;
}
bool next = false;
if (isHorizontal)
{
switch (widget->layoutDirection())
{
case Qt::LeftToRight:
case Qt::LayoutDirectionAuto:
next = isRight;
break;
case Qt::RightToLeft:
next = isLeft;
break;
default:
Q_ASSERT(false);
break;
}
}
else
{
// Vertical
next = isDown;
}
m_formManager->focusNextPrevFormField(next);
}
PDFFormFieldPushButtonEditor::PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget)
{
}
PDFFormFieldAbstractButtonEditor::PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget)
{
}
void PDFFormFieldAbstractButtonEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
switch (event->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
{
event->accept();
break;
}
default:
break;
}
}
void PDFFormFieldAbstractButtonEditor::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
switch (event->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
{
click();
event->accept();
break;
}
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
{
performKeypadNavigation(widget, event);
break;
}
default:
break;
}
}
void PDFFormFieldAbstractButtonEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
switch (event->key())
{
case Qt::Key_Select:
case Qt::Key_Space:
{
click();
event->accept();
break;
}
default:
break;
}
}
void PDFFormFieldAbstractButtonEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
Q_UNUSED(widget);
Q_UNUSED(mousePagePosition);
if (event->button() == Qt::LeftButton)
{
click();
event->accept();
}
}
void PDFFormFieldPushButtonEditor::click()
{
if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::MousePressed, getFormWidget()))
{
emit m_formManager->actionTriggered(action);
}
else if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::Default, getFormWidget()))
{
emit m_formManager->actionTriggered(action);
}
}
PDFFormFieldCheckableButtonEditor::PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget)
{
}
void PDFFormFieldCheckableButtonEditor::click()
{
QByteArray newState;
// First, check current state of form field
PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument());
QByteArray state = loader.readName(m_formWidget.getParent()->getValue());
QByteArray onState = PDFFormFieldButton::getOnAppearanceState(m_formManager, &m_formWidget);
if (state != onState)
{
newState = onState;
}
else
{
newState = PDFFormFieldButton::getOffAppearanceState(m_formManager, &m_formWidget);
}
// We have a new state, try to apply it to form field
PDFFormField::SetValueParameters parameters;
parameters.formManager = m_formManager;
parameters.invokingWidget = m_formWidget.getWidget();
parameters.invokingFormField = m_formWidget.getParent();
parameters.scope = PDFFormField::SetValueParameters::Scope::User;
parameters.value = PDFObject::createName(std::make_shared(qMove(newState)));
m_formManager->setFormFieldValue(parameters);
}
PDFFormFieldComboBoxEditor::PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget)
{
}
void PDFFormFieldTextBoxEditor::initializeTextEdit(PDFTextEditPseudowidget* textEdit) const
{
const PDFFormFieldText* parentField = dynamic_cast(m_formWidget.getParent());
Q_ASSERT(parentField);
PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument());
QByteArray defaultAppearance = parentField->getDefaultAppearance();
if (defaultAppearance.isEmpty())
{
defaultAppearance = m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray());
}
Qt::Alignment alignment = parentField->getAlignment();
if (!(alignment & Qt::AlignHorizontal_Mask))
{
alignment |= m_formManager->getForm()->getDefaultAlignment() & Qt::AlignHorizontal_Mask;
}
// Initialize text edit
textEdit->setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(m_formWidget), parentField->getTextMaximalLength());
textEdit->setText(loader.readTextString(parentField->getValue(), QString()));
}
void PDFFormFieldTextBoxEditor::setFocusImpl(bool focused)
{
if (focused)
{
m_textEdit.setCursorPosition(m_textEdit.getPositionEnd(), false);
m_textEdit.performSelectAll();
}
else if (!m_textEdit.isPassword()) // Passwords are not saved in the document
{
// If text has been changed, then commit it
PDFObject object = PDFObjectFactory::createTextString(m_textEdit.getText());
if (object != m_formWidget.getParent()->getValue())
{
PDFFormField::SetValueParameters parameters;
parameters.formManager = m_formManager;
parameters.invokingWidget = m_formWidget.getWidget();
parameters.invokingFormField = m_formWidget.getParent();
parameters.scope = PDFFormField::SetValueParameters::Scope::User;
parameters.value = qMove(object);
m_formManager->setFormFieldValue(parameters);
}
}
}
PDFFormFieldTextBoxEditor::PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget),
m_textEdit(formWidget.getParent()->getFlags())
{
initializeTextEdit(&m_textEdit);
}
void PDFFormFieldTextBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
m_textEdit.shortcutOverrideEvent(widget, event);
}
void PDFFormFieldTextBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
if (!m_textEdit.isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
{
// Commit the editor
m_formManager->setFocusToEditor(nullptr);
event->accept();
return;
}
if (event->key() == Qt::Key_Escape)
{
// Cancel the editor
reloadValue();
m_formManager->setFocusToEditor(nullptr);
event->accept();
return;
}
m_textEdit.keyPressEvent(widget, event);
if (event->isAccepted())
{
widget->update();
}
}
void PDFFormFieldTextBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
if (event->button() == Qt::LeftButton)
{
const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus);
m_textEdit.setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier);
event->accept();
widget->update();
}
}
void PDFFormFieldTextBoxEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
if (event->button() == Qt::LeftButton)
{
const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus);
m_textEdit.setCursorPosition(cursorPosition, false);
m_textEdit.setCursorPosition(m_textEdit.getCursorWordBackward(), false);
m_textEdit.setCursorPosition(m_textEdit.getCursorWordForward(), true);
event->accept();
widget->update();
}
}
void PDFFormFieldTextBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
// 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_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus);
m_textEdit.setCursorPosition(cursorPosition, true);
event->accept();
widget->update();
}
}
void PDFFormFieldTextBoxEditor::reloadValue()
{
PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument());
m_textEdit.setText(loader.readTextString(m_formWidget.getParent()->getValue(), QString()));
}
void PDFFormFieldTextBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const
{
if (edit)
{
m_textEdit.draw(parameters, true);
}
else
{
// Draw static contents
PDFTextEditPseudowidget pseudowidget(m_formWidget.getParent()->getFlags());
initializeTextEdit(&pseudowidget);
pseudowidget.draw(parameters, false);
}
}
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::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 PDFTextEditPseudowidget::getCursorPositions() const
{
std::vector 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);
parameters.boundingRectangle = parameters.annotation->getRectangle();
QPalette palette = QApplication::palette();
auto getAdjustedColor = [¶meters](QColor color)
{
if (parameters.invertColors)
{
return invertColor(color);
}
return color;
};
QPainter* painter = parameters.painter;
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 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 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 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;
}
bool PDFFormFieldText::setValue(const SetValueParameters& parameters)
{
// Do not allow to set value to push buttons
if (getFlags().testFlag(PushButton))
{
return false;
}
// If form field is readonly, and scope is user (form field is changed by user,
// not by calculated value), then we must not allow value change.
if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User)
{
return false;
}
Q_ASSERT(parameters.formManager);
Q_ASSERT(parameters.modifier);
PDFDocumentBuilder* builder = parameters.modifier->getBuilder();
parameters.modifier->markFormFieldChanged();
builder->setFormFieldValue(getSelfReference(), parameters.value);
m_value = parameters.value;
// Change widget appearance states
for (const PDFFormWidget& formWidget : getWidgets())
{
builder->updateAnnotationAppearanceStreams(formWidget.getWidget());
parameters.modifier->markAnnotationsChanged();
}
return true;
}
PDFListBoxPseudowidget::PDFListBoxPseudowidget(PDFFormField::FieldFlags flags) :
m_flags(flags),
m_topIndex(0),
m_currentIndex(0)
{
}
void PDFListBoxPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
if (event == QKeySequence::Copy)
{
event->accept();
return;
}
if (event == QKeySequence::SelectAll)
{
// Select All can be processed only, if multiselection is allowed
if (isMultiSelect())
{
event->accept();
}
return;
}
switch (event->key())
{
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
event->accept();
break;
default:
break;
}
}
void PDFListBoxPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
event->accept();
if (event == QKeySequence::Copy)
{
// Copy the item text
if (m_currentIndex >= 0 && m_currentIndex < m_options.size())
{
QApplication::clipboard()->setText(m_options[m_currentIndex].userString, QClipboard::Clipboard);
}
return;
}
if (event == QKeySequence::SelectAll && isMultiSelect())
{
std::set selection;
for (int i = 0; i < m_options.size(); ++i)
{
selection.insert(i);
}
setSelection(qMove(selection), false);
return;
}
switch (event->key())
{
case Qt::Key_Home:
setCurrentItem(getStartItemIndex(), event->modifiers());
break;
case Qt::Key_End:
setCurrentItem(getEndItemIndex(), event->modifiers());
break;
case Qt::Key_Up:
moveCurrentItemIndexByOffset(-getSingleStep(), event->modifiers());
break;
case Qt::Key_Down:
moveCurrentItemIndexByOffset(getSingleStep(), event->modifiers());
break;
case Qt::Key_PageUp:
moveCurrentItemIndexByOffset(-getPageStep(), event->modifiers());
break;
case Qt::Key_PageDown:
moveCurrentItemIndexByOffset(getPageStep(), event->modifiers());
break;
default:
event->ignore();
break;
}
}
void PDFListBoxPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance,
Qt::Alignment textAlignment,
QRectF rect,
const PDFListBoxPseudowidget::Options& options,
int topIndex,
std::set selection)
{
// Set appearance
qreal fontSize = appearance.getFontSize();
if (qFuzzyIsNull(fontSize))
{
fontSize = qMax(rect.height() / qMax(options.size(), 1), qreal(12.0));
}
QFont font(appearance.getFontName());
font.setHintingPreference(QFont::PreferNoHinting);
font.setPixelSize(qCeil(fontSize));
font.setStyleStrategy(QFont::ForceOutline);
m_font = font;
QFontMetricsF fontMetrics(font);
m_lineSpacing = fontMetrics.lineSpacing();
m_textColor = appearance.getFontColor();
if (!m_textColor.isValid())
{
m_textColor = Qt::black;
}
m_textAlignment = textAlignment;
m_widgetRect = rect;
m_options = options;
m_topIndex = getValidIndex(topIndex);
m_selection = qMove(selection);
m_currentIndex = m_topIndex;
}
void PDFListBoxPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const
{
pdf::PDFPainterStateGuard guard(parameters.painter);
parameters.boundingRectangle = parameters.annotation->getRectangle();
QPalette palette = QApplication::palette();
auto getAdjustedColor = [¶meters](QColor color)
{
if (parameters.invertColors)
{
return invertColor(color);
}
return color;
};
QMatrix matrix;
matrix.translate(m_widgetRect.left(), m_widgetRect.bottom());
matrix.scale(1.0, -1.0);
QPainter* painter = parameters.painter;
painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip);
painter->setWorldMatrix(matrix, true);
painter->setPen(getAdjustedColor(m_textColor));
painter->setFont(m_font);
QColor textColor = getAdjustedColor(m_textColor);
QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText));
QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight));
QRectF rect(0, 0, m_widgetRect.width(), m_lineSpacing);
for (int i = m_topIndex; i < m_options.size(); ++i)
{
if (m_selection.count(i))
{
painter->fillRect(rect, highlightColor);
painter->setPen(highlightTextColor);
}
else
{
painter->setPen(textColor);
}
painter->drawText(rect, m_textAlignment | Qt::TextSingleLine, m_options[i].userString);
if (edit && m_currentIndex == i)
{
pdf::PDFPainterStateGuard guard(parameters.painter);
painter->setBrush(Qt::NoBrush);
painter->setPen(Qt::DotLine);
painter->drawRect(rect);
}
rect.translate(0, m_lineSpacing);
}
}
void PDFListBoxPseudowidget::setSelection(std::set selection, bool force)
{
if (isReadonly() && !force)
{
// Field is readonly
return;
}
// Jakub Melka: Here should be also commit, when flag CommitOnSelectionChange is set,
// but we do it only, when widget loses focus (so no need to update appearance often).
// I hope it will be OK.
m_selection = qMove(selection);
}
void PDFListBoxPseudowidget::setTopItemIndex(int index)
{
m_topIndex = getValidIndex(index);
}
bool PDFListBoxPseudowidget::isVisible(int index) const
{
return index >= m_topIndex && index < m_topIndex + getViewportRowCount();
}
void PDFListBoxPseudowidget::scrollTo(int index)
{
while (!isVisible(index))
{
if (index < m_topIndex)
{
--m_topIndex;
}
else
{
++m_topIndex;
}
}
}
void PDFListBoxPseudowidget::setCurrentItem(int index, Qt::KeyboardModifiers modifiers)
{
index = getValidIndex(index);
if (m_currentIndex == index)
{
return;
}
std::set newSelection;
if (!isMultiSelect() || !modifiers.testFlag(Qt::ShiftModifier))
{
newSelection = { index };
}
else
{
int indexFrom = index;
int indexTo = index;
if (hasContinuousSelection())
{
indexFrom = qMin(index, *m_selection.begin());
indexTo = qMax(index, *m_selection.rbegin());
}
else
{
indexFrom = qMin(index, m_currentIndex);
indexTo = qMax(index, m_currentIndex);
}
for (int i = indexFrom; i <= indexTo; ++i)
{
newSelection.insert(i);
}
}
m_currentIndex = index;
setSelection(qMove(newSelection), false);
scrollTo(m_currentIndex);
}
bool PDFListBoxPseudowidget::hasContinuousSelection() const
{
if (m_selection.empty())
{
return false;
}
return *m_selection.rbegin() - *m_selection.begin() + 1 == m_selection.size();
}
int PDFListBoxPseudowidget::getValidIndex(int index) const
{
return qBound(getStartItemIndex(), index, getEndItemIndex());
}
PDFFormFieldListBoxEditor::PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) :
BaseClass(formManager, formWidget),
m_listBox(formWidget.getParent()->getFlags())
{
initializeListBox(&m_listBox);
}
void PDFFormFieldListBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
m_listBox.shortcutOverrideEvent(widget, event);
}
void PDFFormFieldListBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
{
// Commit the editor
m_formManager->setFocusToEditor(nullptr);
event->accept();
return;
}
if (event->key() == Qt::Key_Escape)
{
// Cancel the editor
reloadValue();
m_formManager->setFocusToEditor(nullptr);
event->accept();
return;
}
m_listBox.keyPressEvent(widget, event);
if (event->isAccepted())
{
widget->update();
}
}
void PDFFormFieldListBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
}
void PDFFormFieldListBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
}
void PDFFormFieldListBoxEditor::mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition)
{
}
void PDFFormFieldListBoxEditor::reloadValue()
{
const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent());
Q_ASSERT(parentField);
m_listBox.setTopItemIndex(parentField->getTopIndex());
m_listBox.setSelection(getSelectedItems(parentField->getValue(), parentField->getSelection()), true);
}
void PDFFormFieldListBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const
{
if (edit)
{
m_listBox.draw(parameters, true);
}
else
{
// Draw static contents
PDFListBoxPseudowidget pseudowidget(m_formWidget.getParent()->getFlags());
initializeListBox(&pseudowidget);
pseudowidget.draw(parameters, false);
}
}
void PDFFormFieldListBoxEditor::initializeListBox(PDFListBoxPseudowidget* listBox) const
{
const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent());
Q_ASSERT(parentField);
listBox->setAppearance(PDFAnnotationDefaultAppearance::parse(m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray())),
m_formManager->getForm()->getDefaultAlignment(),
m_formManager->getWidgetRectangle(m_formWidget),
parentField->getOptions(),
parentField->getTopIndex(),
getSelectedItems(parentField->getValue(), parentField->getSelection()));
}
void PDFFormFieldListBoxEditor::setFocusImpl(bool focused)
{
if (!focused && !m_listBox.isReadonly())
{
commit();
}
}
std::set PDFFormFieldListBoxEditor::getSelectedItems(PDFObject value, PDFObject indices) const
{
std::set result;
PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument());
value = m_formManager->getDocument()->getObject(value);
std::vector indicesVector = loader.readIntegerArray(indices);
if (indicesVector.empty())
{
const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent());
Q_ASSERT(parentField);
const PDFFormFieldChoice::Options& options = parentField->getOptions();
auto addOption = [&options, &result](const QString& optionString)
{
for (size_t i = 0; i < options.size(); ++i)
{
const PDFFormFieldChoice::Option& option = options[i];
if (option.userString == optionString)
{
result.insert(int(i));
}
}
};
if (value.isString())
{
addOption(loader.readTextString(value, QString()));
}
else if (value.isArray())
{
for (const QString& string : loader.readTextStringList(value))
{
addOption(string);
}
}
}
else
{
std::sort(indicesVector.begin(), indicesVector.end());
std::copy(indicesVector.cbegin(), indicesVector.cend(), std::inserter(result, result.cend()));
}
return result;
}
void PDFFormFieldListBoxEditor::commit()
{
PDFObject object;
const std::set& selection = m_listBox.getSelection();
if (!selection.empty())
{
const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent());
Q_ASSERT(parentField);
const PDFFormFieldChoice::Options& options = parentField->getOptions();
std::set values;
for (const int index : selection)
{
values.insert(options[index].userString);
}
if (values.size() == 1)
{
object = PDFObjectFactory::createTextString(*values.begin());
}
else
{
PDFObjectFactory objectFactory;
objectFactory.beginArray();
for (const QString& string : values)
{
objectFactory << string;
}
objectFactory.endArray();
object = objectFactory.takeObject();
}
}
if (object != m_formWidget.getParent()->getValue())
{
PDFFormField::SetValueParameters parameters;
parameters.formManager = m_formManager;
parameters.invokingWidget = m_formWidget.getWidget();
parameters.invokingFormField = m_formWidget.getParent();
parameters.scope = PDFFormField::SetValueParameters::Scope::User;
parameters.value = qMove(object);
parameters.listboxTopIndex = m_listBox.getTopItemIndex();
std::copy(selection.cbegin(), selection.cend(), std::back_inserter(parameters.listboxChoices));
m_formManager->setFormFieldValue(parameters);
}
}
bool PDFFormFieldChoice::setValue(const SetValueParameters& parameters)
{
// Do not allow to set value to push buttons
if (getFlags().testFlag(PushButton))
{
return false;
}
// If form field is readonly, and scope is user (form field is changed by user,
// not by calculated value), then we must not allow value change.
if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User)
{
return false;
}
Q_ASSERT(parameters.formManager);
Q_ASSERT(parameters.modifier);
PDFDocumentBuilder* builder = parameters.modifier->getBuilder();
parameters.modifier->markFormFieldChanged();
builder->setFormFieldValue(getSelfReference(), parameters.value);
if (isListBox())
{
// Listbox has special values, which must be set
builder->setFormFieldChoiceTopIndex(getSelfReference(), parameters.listboxTopIndex);
builder->setFormFieldChoiceIndices(getSelfReference(), parameters.listboxChoices);
}
m_value = parameters.value;
// Change widget appearance states
for (const PDFFormWidget& formWidget : getWidgets())
{
builder->updateAnnotationAppearanceStreams(formWidget.getWidget());
parameters.modifier->markAnnotationsChanged();
}
return true;
}
void PDFFormFieldChoice::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue)
{
BaseClass::reloadValue(storage, parentValue);
if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference())))
{
PDFDocumentDataLoaderDecorator loader(storage);
m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0);
m_selection = fieldDictionary->get("I");
}
}
} // namespace pdf