diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index 79be5be..0cdac8c 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -985,7 +985,8 @@ void PDFAnnotationManager::drawWidgetAnnotationHighlight(QRectF annotationRectan { // Is it a form field? const PDFFormManager::FormAppearanceFlags flags = m_formManager->getAppearanceFlags(); - if (flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields)) + const bool isFocused = m_formManager->isFocused(annotation->getSelfReference()); + if (isFocused || flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields)) { const PDFFormField* formField = m_formManager->getFormFieldForWidget(annotation->getSelfReference()); if (!formField) @@ -1002,6 +1003,10 @@ void PDFAnnotationManager::drawWidgetAnnotationHighlight(QRectF annotationRectan { color = Qt::red; } + if (isFocused) + { + color = Qt::yellow; + } if (color.isValid()) { @@ -1453,7 +1458,14 @@ void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) } if (annotationType == AnnotationType::Widget) { - m_cursor = QCursor(Qt::ArrowCursor); + if (m_formManager && m_formManager->hasFormFieldWidgetText(pageAnnotation.annotation->getSelfReference())) + { + m_cursor = QCursor(Qt::IBeamCursor); + } + else + { + m_cursor = QCursor(Qt::ArrowCursor); + } } // Generate popup window diff --git a/PdfForQtLib/sources/pdfdrawwidget.cpp b/PdfForQtLib/sources/pdfdrawwidget.cpp index 390efde..0bee4b8 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.cpp +++ b/PdfForQtLib/sources/pdfdrawwidget.cpp @@ -20,6 +20,7 @@ #include "pdfcompiler.h" #include "pdfwidgettool.h" #include "pdfannotation.h" +#include "pdfform.h" #include #include @@ -34,6 +35,8 @@ PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int QWidget(parent), m_cmsManager(cmsManager), m_toolManager(nullptr), + m_annotationManager(nullptr), + m_formManager(nullptr), m_drawWidget(nullptr), m_horizontalScrollBar(nullptr), m_verticalScrollBar(nullptr), @@ -66,6 +69,16 @@ PDFWidget::~PDFWidget() } +bool PDFWidget::focusNextPrevChild(bool next) +{ + if (m_formManager && m_formManager->focusNextPrevFormField(next)) + { + return true; + } + + return QWidget::focusNextPrevChild(next); +} + void PDFWidget::setDocument(const PDFModifiedDocument& document) { m_proxy->setDocument(document); @@ -191,6 +204,18 @@ void PDFWidget::addInputInterface(IDrawWidgetInputInterface* inputInterface) } } +PDFFormManager* PDFWidget::getFormManager() const +{ + return m_formManager; +} + +void PDFWidget::setFormManager(PDFFormManager* formManager) +{ + removeInputInterface(m_formManager); + m_formManager = formManager; + addInputInterface(m_formManager); +} + void PDFWidget::setToolManager(PDFToolManager* toolManager) { removeInputInterface(m_toolManager); diff --git a/PdfForQtLib/sources/pdfdrawwidget.h b/PdfForQtLib/sources/pdfdrawwidget.h index 683914b..54e6017 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.h +++ b/PdfForQtLib/sources/pdfdrawwidget.h @@ -31,6 +31,7 @@ class PDFDocument; class PDFCMSManager; class PDFToolManager; class PDFDrawWidget; +class PDFFormManager; class PDFDrawWidgetProxy; class PDFModifiedDocument; class PDFWidgetAnnotationManager; @@ -59,6 +60,8 @@ public: explicit PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent); virtual ~PDFWidget() override; + virtual bool focusNextPrevChild(bool next) override; + using PageRenderingErrors = std::map>; /// Sets the document to be viewed in this widget. Document can be nullptr, @@ -93,6 +96,9 @@ public: void setToolManager(PDFToolManager* toolManager); void setAnnotationManager(PDFWidgetAnnotationManager* annotationManager); + PDFFormManager* getFormManager() const; + void setFormManager(PDFFormManager* formManager); + signals: void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); @@ -109,6 +115,7 @@ private: const PDFCMSManager* m_cmsManager; PDFToolManager* m_toolManager; PDFWidgetAnnotationManager* m_annotationManager; + PDFFormManager* m_formManager; IDrawWidget* m_drawWidget; QScrollBar* m_horizontalScrollBar; QScrollBar* m_verticalScrollBar; diff --git a/PdfForQtLib/sources/pdfform.cpp b/PdfForQtLib/sources/pdfform.cpp index 62aacfa..5b91ecd 100644 --- a/PdfForQtLib/sources/pdfform.cpp +++ b/PdfForQtLib/sources/pdfform.cpp @@ -74,7 +74,8 @@ PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object) continue; } - if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget") + if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget" && + !annotationDictionary->hasKey("Kids")) { rogueFieldFound = true; form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr)); @@ -146,6 +147,16 @@ void PDFFormField::reloadValue(const PDFObjectStorage* storage, PDFObject parent } } +void PDFFormField::apply(const std::function& functor) const +{ + functor(this); + + for (const PDFFormFieldPointer& childField : m_childFields) + { + childField->apply(functor); + } +} + PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) { PDFFormFieldPointer result; @@ -361,7 +372,8 @@ PDFFormManager::PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent) : m_proxy(proxy), m_annotationManager(nullptr), m_document(nullptr), - m_flags(getDefaultApperanceFlags()) + m_flags(getDefaultApperanceFlags()), + m_focusedEditor(nullptr) { Q_ASSERT(proxy); } @@ -403,6 +415,8 @@ void PDFFormManager::setDocument(const PDFModifiedDocument& document) // Clean the form m_form = PDFForm(); } + + updateFormWidgetEditors(); } else if (document.hasFlag(PDFModifiedDocument::FormField)) { @@ -422,6 +436,245 @@ 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::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; +} + +void PDFFormManager::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormManager::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormManager::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +const std::optional& PDFFormManager::getCursor() const +{ + static const std::optional dummy; + return dummy; +} + +void PDFFormManager::updateFormWidgetEditors() +{ + setFocusToEditor(nullptr); + qDeleteAll(m_widgetEditors); + m_widgetEditors.clear(); + + 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, this)); + break; + } + + case PDFFormFieldButton::ButtonType::RadioButton: + case PDFFormFieldButton::ButtonType::CheckBox: + { + m_widgetEditors.push_back(new PDFFormFieldCheckableButtonEditor(this, widget, this)); + break; + } + + default: + Q_ASSERT(false); + break; + } + + break; + } + + case PDFFormField::FieldType::Text: + { + m_widgetEditors.push_back(new PDFFormFieldTextBoxEditor(this, widget, this)); + 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, this)); + } + else if (formFieldChoice->isListBox()) + { + m_widgetEditors.push_back(new PDFFormFieldListBoxEditor(this, widget, this)); + } + 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) @@ -433,4 +686,48 @@ void PDFFormManager::updateFieldValues() } } +PDFFormFieldWidgetEditor::PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(parent), + m_formManager(formManager), + m_formWidget(formWidget), + m_hasFocus(false) +{ + Q_ASSERT(m_formManager); +} + +void PDFFormFieldWidgetEditor::setFocus(bool hasFocus) +{ + m_hasFocus = hasFocus; +} + +PDFFormFieldPushButtonEditor::PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(formManager, formWidget, parent) +{ + +} + +PDFFormFieldCheckableButtonEditor::PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(formManager, formWidget, parent) +{ + +} + +PDFFormFieldComboBoxEditor::PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(formManager, formWidget, parent) +{ + +} + +PDFFormFieldListBoxEditor::PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(formManager, formWidget, parent) +{ + +} + +PDFFormFieldTextBoxEditor::PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent) : + BaseClass(formManager, formWidget, parent) +{ + +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h index 347183d..e1f0937 100644 --- a/PdfForQtLib/sources/pdfform.h +++ b/PdfForQtLib/sources/pdfform.h @@ -20,12 +20,14 @@ #include "pdfobject.h" #include "pdfannotation.h" +#include "pdfdocumentdrawinterface.h" #include namespace pdf { class PDFFormField; +class PDFFormManager; class PDFObjectStorage; class PDFModifiedDocument; @@ -190,6 +192,12 @@ public: /// Reloads value from object storage. Actually stored value is lost. void reloadValue(const PDFObjectStorage* storage, PDFObject parentValue); + /// Applies function to this form field and all its descendants, + /// in pre-order (first application is to the parent, following + /// calls to apply for children). + /// \param functor Functor to apply + void apply(const std::function& functor) const; + /// Parses form field from the object reference. If some error occurs /// then null pointer is returned, no exception is thrown. /// \param storage Storage @@ -358,10 +366,99 @@ private: PDFWidgetToFormFieldMapping m_widgetToFormField; }; +/// Base class for editors of form fields. It enables editation +/// of form fields, such as entering text, clicking on check box etc. +class PDFFormFieldWidgetEditor : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldWidgetEditor() = default; + + PDFFormField* getFormField() const { return m_formWidget.getParent(); } + PDFObjectReference getWidgetAnnotation() const { return m_formWidget.getWidget(); } + + void setFocus(bool hasFocus); + +private: + PDFFormManager* m_formManager; + PDFFormWidget m_formWidget; + bool m_hasFocus; +}; + +/// Editor for push buttons +class PDFFormFieldPushButtonEditor : public PDFFormFieldWidgetEditor +{ + Q_OBJECT + +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldPushButtonEditor() = default; +}; + +/// Editor for check boxes or radio buttons +class PDFFormFieldCheckableButtonEditor : public PDFFormFieldWidgetEditor +{ + Q_OBJECT + +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldCheckableButtonEditor() = default; +}; + +/// Editor for text fields +class PDFFormFieldTextBoxEditor : public PDFFormFieldWidgetEditor +{ + Q_OBJECT + +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldTextBoxEditor() = default; +}; + +/// Editor for combo boxes +class PDFFormFieldComboBoxEditor : public PDFFormFieldWidgetEditor +{ + Q_OBJECT + +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldComboBoxEditor() = default; +}; + +/// Editor for list boxes +class PDFFormFieldListBoxEditor : public PDFFormFieldWidgetEditor +{ + Q_OBJECT + +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); + virtual ~PDFFormFieldListBoxEditor() = default; +}; + /// Form manager. Manages all form widgets functionality - triggers actions, /// edits fields, updates annotation appearances, etc. Valid pointer to annotation /// manager is requirement. -class PDFFORQTLIBSHARED_EXPORT PDFFormManager : public QObject +class PDFFORQTLIBSHARED_EXPORT PDFFormManager : public QObject, public IDrawWidgetInputInterface { Q_OBJECT @@ -394,13 +491,78 @@ public: const PDFDocument* getDocument() const; void setDocument(const PDFModifiedDocument& document); - /// Returns default form apperance flags - static constexpr FormAppearanceFlags getDefaultApperanceFlags() { return HighlightFields | HighlightRequiredFields; } - FormAppearanceFlags getAppearanceFlags() const; void setAppearanceFlags(FormAppearanceFlags flags); + /// Returns true, if form field has text (for example, it is a text box, + /// or editable combo box) + /// \param widgetAnnotation Widget annotation + bool hasFormFieldWidgetText(PDFObjectReference widgetAnnotation) const; + + /// Returns all form field widgets. This function is not simple getter, + /// call can be expensive, because all form fields are accessed. + PDFFormWidgets getWidgets() const; + + /// Applies function to all form fields present in the form, + /// in pre-order (first application is to the parent, following + /// calls to apply for children). + /// \param functor Functor to apply + void apply(const std::function& functor) const; + + /// Sets focus to the editor. Is is allowed to pass nullptr to this + /// function, it means that no editor is focused. + /// \param editor Editor to be focused + void setFocusToEditor(PDFFormFieldWidgetEditor* editor); + + /// Tries to set focus on next/previous form field in the focus chain. + /// Function returns true, if focus has been successfully set. + /// \param next Focus next (true) or previous (false) widget + bool focusNextPrevFormField(bool next); + + /// Returns true, if widget is focused. + /// \param widget Widget annotation reference + bool isFocused(PDFObjectReference widget) const; + + /// Returns default form apperance flags + static constexpr FormAppearanceFlags getDefaultApperanceFlags() { return HighlightFields | HighlightRequiredFields; } + + // interface IDrawWidgetInputInterface + + /// Handles key press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + + /// Handles mouse press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + + /// Handles mouse release event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + + /// Handles mouse move event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + + /// Handles mouse wheel event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + + /// Returns tooltip generated from annotation + virtual QString getTooltip() const override { return QString(); } + + /// Returns current cursor + virtual const std::optional& getCursor() const override; + + virtual int getInputPriority() const override { return FormPriority; } + private: + void updateFormWidgetEditors(); void updateFieldValues(); PDFDrawWidgetProxy* m_proxy; @@ -408,6 +570,9 @@ private: const PDFDocument* m_document; FormAppearanceFlags m_flags; PDFForm m_form; + + std::vector m_widgetEditors; + PDFFormFieldWidgetEditor* m_focusedEditor; }; } // namespace pdf diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index a70aaf2..7e6016b 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -275,6 +275,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : m_formManager->setAnnotationManager(m_annotationManager); m_formManager->setAppearanceFlags(m_settings->getSettings().m_formAppearanceFlags); m_annotationManager->setFormManager(m_formManager); + m_pdfWidget->setFormManager(m_formManager); connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFViewerMainWindow::onDrawSpaceChanged); connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::pageLayoutChanged, this, &PDFViewerMainWindow::onPageLayoutChanged);