From 6c746ba9011a2359b81104514fba87f3ece4e42b Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 12 Apr 2020 18:12:42 +0200 Subject: [PATCH] Annotation tooltips --- PdfForQtLib/sources/pdfannotation.cpp | 233 ++++++++++++++----- PdfForQtLib/sources/pdfannotation.h | 67 +++++- PdfForQtLib/sources/pdfdrawspacecontroller.h | 1 - PdfForQtLib/sources/pdfdrawwidget.cpp | 56 +++++ 4 files changed, 299 insertions(+), 58 deletions(-) diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index 28bdc70..33fc692 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -24,8 +24,10 @@ #include "pdfwidgetutils.h" #include "pdfpagecontentprocessor.h" #include "pdfparser.h" +#include "pdfdrawwidget.h" #include +#include namespace pdf { @@ -900,6 +902,69 @@ PDFAnnotationManager::~PDFAnnotationManager() } +QMatrix PDFAnnotationManager::prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, + QPaintDevice* device, + const PDFAnnotation::Flags annotationFlags, + const PDFPage* page, + QRectF& annotationRectangle) const +{ + // "Unrotate" user coordinate space, if NoRotate flag is set + QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix; + if (annotationFlags.testFlag(PDFAnnotation::NoRotate)) + { + PDFReal rotationAngle = 0.0; + switch (page->getPageRotation()) + { + case PageRotation::None: + break; + + case PageRotation::Rotate90: + rotationAngle = -90.0; + break; + + case PageRotation::Rotate180: + rotationAngle = -180.0; + break; + + case PageRotation::Rotate270: + rotationAngle = -270.0; + break; + + default: + Q_ASSERT(false); + break; + } + + QMatrix rotationMatrix; + rotationMatrix.rotate(-rotationAngle); + QPointF topLeft = annotationRectangle.bottomLeft(); // Do not forget, that y is upward instead of Qt + QPointF difference = topLeft - rotationMatrix.map(topLeft); + + QMatrix finalMatrix; + finalMatrix.translate(difference.x(), difference.y()); + finalMatrix.rotate(-rotationAngle); + userSpaceToDeviceSpace = finalMatrix * userSpaceToDeviceSpace; + } + + if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) + { + // Jakub Melka: we must adjust annotation rectangle to disable zoom. We calculate + // inverse zoom as square root of absolute value of determinant of scale matrix. + // Determinant corresponds approximately to zoom squared, and if we will have + // unrotated matrix, and both axes are scaled by same value, then determinant will + // be exactly zoom squared. Also, we will adjust to target device logical DPI, + // if we, for example are using 4K, or 8K monitors. + qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant())); + zoom = PDFWidgetUtils::scaleDPI_x(device, zoom); + + QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom); + unzoomedRect.translate(0, -unzoomedRect.height()); + annotationRectangle = unzoomedRect; + } + + return userSpaceToDeviceSpace; +} + void PDFAnnotationManager::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, @@ -909,7 +974,7 @@ void PDFAnnotationManager::drawPage(QPainter* painter, Q_UNUSED(compiledPage); Q_UNUSED(layoutGetter); - PageAnnotations& annotations = getPageAnnotations(pageIndex); + const PageAnnotations& annotations = getPageAnnotations(pageIndex); if (!annotations.isEmpty()) { const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); @@ -929,7 +994,7 @@ void PDFAnnotationManager::drawPage(QPainter* painter, const PDFCMSPointer cms = m_cmsManager->getCurrentCMS(); m_fontCache->setCacheShrinkEnabled(this, false); - for (PageAnnotation& annotation : annotations.annotations) + for (const PageAnnotation& annotation : annotations.annotations) { const PDFAnnotation::Flags annotationFlags = annotation.annotation->getFlags(); if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden @@ -972,58 +1037,10 @@ void PDFAnnotationManager::drawPage(QPainter* painter, continue; } - // "Unrotate" user coordinate space, if NoRotate flag is set - QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix; - if (annotationFlags.testFlag(PDFAnnotation::NoRotate)) - { - PDFReal rotationAngle = 0.0; - switch (page->getPageRotation()) - { - case PageRotation::None: - break; - - case PageRotation::Rotate90: - rotationAngle = -90.0; - break; - - case PageRotation::Rotate180: - rotationAngle = -180.0; - break; - - case PageRotation::Rotate270: - rotationAngle = -270.0; - break; - - default: - Q_ASSERT(false); - break; - } - - QMatrix rotationMatrix; - rotationMatrix.rotate(-rotationAngle); - QPointF topLeft = annotationRectangle.bottomLeft(); // Do not forget, that y is upward instead of Qt - QPointF difference = topLeft - rotationMatrix.map(topLeft); - - QMatrix finalMatrix; - finalMatrix.translate(difference.x(), difference.y()); - finalMatrix.rotate(-rotationAngle); - userSpaceToDeviceSpace = finalMatrix * userSpaceToDeviceSpace; - } + QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle); if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) { - // Jakub Melka: we must adjust annotation rectangle to disable zoom. We calculate - // inverse zoom as square root of absolute value of determinant of scale matrix. - // Determinant corresponds approximately to zoom squared, and if we will have - // unrotated matrix, and both axes are scaled by same value, then determinant will - // be exactly zoom squared. Also, we will adjust to target device logical DPI, - // if we, for example are using 4K, or 8K monitors. - qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant())); - zoom = PDFWidgetUtils::scaleDPI_x(painter->device(), zoom); - - QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom); - unzoomedRect.translate(0, -unzoomedRect.height()); - annotationRectangle = unzoomedRect; features.setFlag(PDFRenderer::ClipToCropBox, false); } @@ -1070,16 +1087,23 @@ void PDFAnnotationManager::setDocument(const PDFDocument* document, const PDFOpt } } -PDFObject PDFAnnotationManager::getAppearanceStream(PageAnnotation& pageAnnotation) const +PDFObject PDFAnnotationManager::getAppearanceStream(const PageAnnotation& pageAnnotation) const { auto getAppearanceStream = [&pageAnnotation] (void) -> PDFObject { return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState()); }; + + QMutexLocker lock(&m_mutex); return pageAnnotation.appearanceStream.get(getAppearanceStream); } -PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const +const PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const +{ + return const_cast(this)->getPageAnnotations(pageIndex); +} + +PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) { Q_ASSERT(m_document); @@ -1112,6 +1136,16 @@ PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations( return it->second; } +bool PDFAnnotationManager::hasAnnotation(PDFInteger pageIndex) const +{ + return !getPageAnnotations(pageIndex).isEmpty(); +} + +bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector& pageIndices) const +{ + return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1)); +} + PDFRenderer::Features PDFAnnotationManager::getFeatures() const { return m_features; @@ -1175,6 +1209,99 @@ PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager() m_proxy->unregisterDrawInterface(this); } +void PDFWidgetAnnotationManager::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + + updateFromMouseEvent(event); +} + +void PDFWidgetAnnotationManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + + updateFromMouseEvent(event); +} + +void PDFWidgetAnnotationManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + + updateFromMouseEvent(event); +} + +void PDFWidgetAnnotationManager::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) +{ + PDFWidget* widget = m_proxy->getWidget(); + std::vector currentPages = widget->getDrawWidget()->getCurrentPages(); + + if (!hasAnyPageAnnotation(currentPages)) + { + // All pages doesn't have annotation + return; + } + + m_tooltip = QString(); + + // We must update appearance states, and update tooltip + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + const bool isDown = event->buttons().testFlag(Qt::LeftButton); + const PDFAppeareanceStreams::Appearance hoverAppearance = isDown ? PDFAppeareanceStreams::Appearance::Down : PDFAppeareanceStreams::Appearance::Rollover; + + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex); + for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + QRectF annotationRect = pageAnnotation.annotation->getRectangle(); + QMatrix matrix = prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect); + QPainterPath path; + path.addRect(annotationRect); + path = matrix.map(path); + + if (path.contains(event->pos())) + { + pageAnnotation.appearance = hoverAppearance; + + // Generate tooltip + if (m_tooltip.isEmpty()) + { + const PDFMarkupAnnotation* markupAnnotation = dynamic_cast(pageAnnotation.annotation.data()); + if (markupAnnotation) + { + QColor backgroundColor = markupAnnotation->getDrawColorFromAnnotationColor(markupAnnotation->getColor()); + if (!backgroundColor.isValid()) + { + backgroundColor = Qt::lightGray; + } + backgroundColor.setHslF(backgroundColor.hslHueF(), backgroundColor.hslSaturationF(), 1.0); + m_tooltip = QString("

%1 (%2)

%3

").arg(markupAnnotation->getWindowTitle(), markupAnnotation->getCreationDate().toLocalTime().toString(), markupAnnotation->getContents()); + } + } + } + else + { + pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal; + } + } + } +} + void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const { Q_ASSERT(parameters.painter); diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index 4af19da..369bf92 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -32,6 +32,10 @@ #include +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + namespace pdf { class PDFObjectStorage; @@ -1284,12 +1288,13 @@ public: PDFRenderer::Features getFeatures() const; void setFeatures(PDFRenderer::Features features); -private: +protected: struct PageAnnotation { PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal; - PDFCachedItem appearanceStream; PDFAnnotationPtr annotation; + + mutable PDFCachedItem appearanceStream; }; struct PageAnnotations @@ -1299,14 +1304,37 @@ private: std::vector annotations; }; + /// Prepares annotation transformations for rendering + /// \param pagePointToDevicePointMatrix Page point to device point matrix + /// \param device Paint device, onto which will be annotation rendered + /// \param annotationFlags Annotation flags + /// \param page Page + /// \param[in,out] annotationRectangle Input/output annotation rectangle + QMatrix prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, + QPaintDevice* device, + const PDFAnnotation::Flags annotationFlags, + const PDFPage* page, + QRectF& annotationRectangle) const; + /// Returns current appearance stream for given page annotation /// \param pageAnnotation Page annotation - PDFObject getAppearanceStream(PageAnnotation& pageAnnotation) const; + PDFObject getAppearanceStream(const PageAnnotation& pageAnnotation) const; + + /// Returns constant reference to page annotation for given page index. + /// This function requires, that pointer to m_document is valid. + /// \param pageIndex Page index (must point to valid page) + const PageAnnotations& getPageAnnotations(PDFInteger pageIndex) const; /// Returns reference to page annotation for given page index. /// This function requires, that pointer to m_document is valid. /// \param pageIndex Page index (must point to valid page) - PageAnnotations& getPageAnnotations(PDFInteger pageIndex) const; + PageAnnotations& getPageAnnotations(PDFInteger pageIndex); + + /// Returns true, if given page has any annotation + bool hasAnnotation(PDFInteger pageIndex) const; + + /// Returns true, if any page in the given indices has annotation + bool hasAnyPageAnnotation(const std::vector& pageIndices) const; const PDFDocument* m_document; @@ -1334,8 +1362,39 @@ public: explicit PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent); virtual ~PDFWidgetAnnotationManager() override; + /// Handles key press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + /// Handles mouse press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mousePressEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse release event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mouseReleaseEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse move event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mouseMoveEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse wheel event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void wheelEvent(QWidget* widget, QWheelEvent* event); + + /// Returns tooltip generated from annotation + const QString& getTooltip() const { return m_tooltip; } + private: + void updateFromMouseEvent(QMouseEvent* event); + PDFDrawWidgetProxy* m_proxy; + QString m_tooltip; }; } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index c7b042f..a775cfd 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -35,7 +35,6 @@ namespace pdf { class PDFProgress; class PDFWidget; -class IDrawWidget; class PDFCMSManager; class PDFTextLayoutGetter; class PDFWidgetAnnotationManager; diff --git a/PdfForQtLib/sources/pdfdrawwidget.cpp b/PdfForQtLib/sources/pdfdrawwidget.cpp index a710c3e..12edeb7 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.cpp +++ b/PdfForQtLib/sources/pdfdrawwidget.cpp @@ -19,6 +19,7 @@ #include "pdfdrawspacecontroller.h" #include "pdfcompiler.h" #include "pdfwidgettool.h" +#include "pdfannotation.h" #include #include @@ -232,6 +233,17 @@ void PDFDrawWidgetBase::keyPressEvent(QKeyEvent* event) } } + if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager()) + { + annotationManager->keyPressEvent(this, event); + setToolTip(annotationManager->getTooltip()); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + // Vertical navigation QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); if (verticalScrollbar->isVisible()) @@ -275,6 +287,17 @@ void PDFDrawWidgetBase::mousePressEvent(QMouseEvent* event) } } + if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager()) + { + annotationManager->mousePressEvent(this, event); + setToolTip(annotationManager->getTooltip()); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + if (event->button() == Qt::LeftButton) { m_mouseOperation = MouseOperation::Translate; @@ -301,6 +324,17 @@ void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) } } + if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager()) + { + annotationManager->mouseReleaseEvent(this, event); + setToolTip(annotationManager->getTooltip()); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + performMouseOperation(event->pos()); switch (m_mouseOperation) @@ -338,6 +372,17 @@ void PDFDrawWidgetBase::mouseMoveEvent(QMouseEvent* event) } } + if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager()) + { + annotationManager->mouseMoveEvent(this, event); + setToolTip(annotationManager->getTooltip()); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + performMouseOperation(event->pos()); updateCursor(); event->accept(); @@ -395,6 +440,17 @@ void PDFDrawWidgetBase::wheelEvent(QWheelEvent* event) } } + if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager()) + { + annotationManager->wheelEvent(this, event); + setToolTip(annotationManager->getTooltip()); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();