From bfb26c48077dbbeb3721584e498fc3a13c5a8f19 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Thu, 30 Apr 2020 19:33:50 +0200 Subject: [PATCH] Mouse grabbing --- PdfForQtLib/sources/pdfannotation.cpp | 2 + PdfForQtLib/sources/pdfannotation.h | 2 +- PdfForQtLib/sources/pdfform.cpp | 215 +++++++++++++++++++++++++- PdfForQtLib/sources/pdfform.h | 63 +++++++- 4 files changed, 273 insertions(+), 9 deletions(-) diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index d23c967..7099c55 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -1172,6 +1172,8 @@ void PDFAnnotationManager::drawPage(QPainter* painter, // printing to the printer. if (isContentVisible && m_target == Target::View) { + PDFPainterStateGuard guard(painter); + painter->resetMatrix(); drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, userSpaceToDeviceSpace); } } diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index de59fda..b5d6ea2 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -1312,7 +1312,6 @@ public: PDFFormManager* getFormManager() const; void setFormManager(PDFFormManager* formManager); -protected: struct PageAnnotation { PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal; @@ -1374,6 +1373,7 @@ protected: /// Returns true, if any page in the given indices has annotation bool hasAnyPageAnnotation(const std::vector& pageIndices) const; +protected: void drawWidgetAnnotationHighlight(QRectF annotationRectangle, const PDFAnnotation* annotation, QPainter* painter, diff --git a/PdfForQtLib/sources/pdfform.cpp b/PdfForQtLib/sources/pdfform.cpp index 704d806..601ba4e 100644 --- a/PdfForQtLib/sources/pdfform.cpp +++ b/PdfForQtLib/sources/pdfform.cpp @@ -18,6 +18,7 @@ #include "pdfform.h" #include "pdfdocument.h" #include "pdfdrawspacecontroller.h" +#include "pdfdrawwidget.h" #include #include @@ -123,6 +124,17 @@ const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) co 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) @@ -609,26 +621,193 @@ void PDFFormManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event) void PDFFormManager::mousePressEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(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); + grabMouse(info, event); + } + else if (!isMouseGrabbed()) + { + // Mouse is not grabbed, user clicked elsewhere, unfocus editor + setFocusToEditor(nullptr); + } } void PDFFormManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->mouseReleaseEvent(widget, event); + ungrabMouse(info, event); + } } void PDFFormManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->mouseMoveEvent(widget, event); + + // 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 @@ -724,6 +903,19 @@ void PDFFormManager::updateFieldValues() } } +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, QObject* parent) : BaseClass(parent), m_formManager(formManager), @@ -885,6 +1077,17 @@ void PDFFormFieldPushButtonEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* e } } +void PDFFormFieldPushButtonEditor::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + click(); + event->accept(); + } +} + void PDFFormFieldPushButtonEditor::click() { if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::MousePressed, getFormWidget())) diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h index ae4daef..3b031a2 100644 --- a/PdfForQtLib/sources/pdfform.h +++ b/PdfForQtLib/sources/pdfform.h @@ -351,6 +351,11 @@ public: /// \param widget Widget annotation const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const; + /// Returns form field for widget. If widget doesn't have attached form field, + /// then nullptr is returned. + /// \param widget Widget annotation + PDFFormField* getFormFieldForWidget(PDFObjectReference widget); + /// Parses form from the object. If some error occurs /// then empty form is returned, no exception is thrown. /// \param document Document @@ -417,8 +422,9 @@ public: explicit PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget, QObject* parent); virtual ~PDFFormFieldPushButtonEditor() = default; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event); - virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event); + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; private: void click(); @@ -498,6 +504,7 @@ public: }; Q_DECLARE_FLAGS(FormAppearanceFlags, FormAppearanceFlag) + bool hasForm() const { return hasAcroForm() || hasXFAForm(); } bool hasAcroForm() const { return m_form.getFormType() == PDFForm::FormType::AcroForm; } bool hasXFAForm() const { return m_form.getFormType() == PDFForm::FormType::XFAForm; } @@ -506,6 +513,11 @@ public: /// \param widget Widget annotation const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const { return m_form.getFormFieldForWidget(widget); } + /// Returns form field for widget. If widget doesn't have attached form field, + /// then nullptr is returned. + /// \param widget Widget annotation + PDFFormField* getFormFieldForWidget(PDFObjectReference widget) { return m_form.getFormFieldForWidget(widget); } + PDFAnnotationManager* getAnnotationManager() const; void setAnnotationManager(PDFAnnotationManager* annotationManager); @@ -553,6 +565,27 @@ public: /// Returns default form apperance flags static constexpr FormAppearanceFlags getDefaultApperanceFlags() { return HighlightFields | HighlightRequiredFields; } + struct MouseEventInfo + { + /// Form field under mouse event, nullptr, if + /// no form field is under mouse button. + PDFFormField* formField = nullptr; + + /// Form field widget editor, which is associated + /// with given form field. + PDFFormFieldWidgetEditor* editor = nullptr; + + /// Mouse position in form field coordinate space + QPointF mousePosition; + + /// Matrix, which maps from device space to widget space + QMatrix deviceToWidget; + + /// Returns true, if mouse event info is valid, i.e. + /// mouse event occurs above some form field. + bool isValid() const { return editor != nullptr; } + }; + // interface IDrawWidgetInputInterface /// Handles key press event from widget @@ -600,6 +633,31 @@ private: void updateFormWidgetEditors(); void updateFieldValues(); + PDFFormFieldWidgetEditor* getEditor(const PDFFormField* formField) const; + MouseEventInfo getMouseEventInfo(QWidget* widget, QPoint point); + + struct MouseGrabInfo + { + MouseEventInfo info; + int mouseGrabNesting = 0; + + bool isMouseGrabbed() const { return mouseGrabNesting > 0; } + }; + + bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); } + + /// Grabs mouse input, if mouse is already grabbed, or if event + /// is accepted. When mouse is grabbed, then mouse input events + /// are sent to active editor and are automatically accepted. + /// \param info Mouse event info + /// \param event Mouse event + void grabMouse(const MouseEventInfo& info, QMouseEvent* event); + + /// Release mouse input + /// \param info Mouse event info + /// \param event Mouse event + void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event); + PDFDrawWidgetProxy* m_proxy; PDFAnnotationManager* m_annotationManager; const PDFDocument* m_document; @@ -608,6 +666,7 @@ private: std::vector m_widgetEditors; PDFFormFieldWidgetEditor* m_focusedEditor; + MouseGrabInfo m_mouseGrabInfo; }; } // namespace pdf