Annotation tooltips

This commit is contained in:
Jakub Melka
2020-04-12 18:12:42 +02:00
parent dc7dd8fe74
commit 6c746ba901
4 changed files with 299 additions and 58 deletions

View File

@ -24,8 +24,10 @@
#include "pdfwidgetutils.h" #include "pdfwidgetutils.h"
#include "pdfpagecontentprocessor.h" #include "pdfpagecontentprocessor.h"
#include "pdfparser.h" #include "pdfparser.h"
#include "pdfdrawwidget.h"
#include <QApplication> #include <QApplication>
#include <QMouseEvent>
namespace pdf namespace pdf
{ {
@ -900,78 +902,12 @@ PDFAnnotationManager::~PDFAnnotationManager()
} }
void PDFAnnotationManager::drawPage(QPainter* painter, QMatrix PDFAnnotationManager::prepareTransformations(const QMatrix& pagePointToDevicePointMatrix,
PDFInteger pageIndex, QPaintDevice* device,
const PDFPrecompiledPage* compiledPage, const PDFAnnotation::Flags annotationFlags,
PDFTextLayoutGetter& layoutGetter, const PDFPage* page,
const QMatrix& pagePointToDevicePointMatrix) const QRectF& annotationRectangle) const
{ {
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
PageAnnotations& annotations = getPageAnnotations(pageIndex);
if (!annotations.isEmpty())
{
const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
Q_ASSERT(page);
PDFRenderer::Features features = m_features;
if (!features.testFlag(PDFRenderer::DisplayAnnotations))
{
// Annotation displaying is disabled
return;
}
Q_ASSERT(m_fontCache);
Q_ASSERT(m_cmsManager);
Q_ASSERT(m_optionalActivity);
const PDFCMSPointer cms = m_cmsManager->getCurrentCMS();
m_fontCache->setCacheShrinkEnabled(this, false);
for (PageAnnotation& annotation : annotations.annotations)
{
const PDFAnnotation::Flags annotationFlags = annotation.annotation->getFlags();
if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden
(m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed
(m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView))) // Target is view, and annotation is disabled for screen
{
continue;
}
PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation));
if (!appearanceStreamObject.isStream())
{
// Object is not valid appearance stream. We will try to draw default
// annotation appearance.
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
AnnotationDrawParameters parameters;
parameters.painter = painter;
parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState());
annotation.annotation->draw(parameters);
painter->restore();
continue;
}
PDFDocumentDataLoaderDecorator loader(m_document);
const PDFStream* formStream = appearanceStreamObject.getStream();
const PDFDictionary* formDictionary = formStream->getDictionary();
QRectF annotationRectangle = annotation.annotation->getRectangle();
QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF());
QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix());
QByteArray content = m_document->getDecodedStream(formStream);
PDFObject resources = m_document->getObject(formDictionary->get("Resources"));
PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group"));
if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty())
{
// Form bounding box is empty
continue;
}
// "Unrotate" user coordinate space, if NoRotate flag is set // "Unrotate" user coordinate space, if NoRotate flag is set
QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix; QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix;
if (annotationFlags.testFlag(PDFAnnotation::NoRotate)) if (annotationFlags.testFlag(PDFAnnotation::NoRotate))
@ -1019,11 +955,92 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
// be exactly zoom squared. Also, we will adjust to target device logical DPI, // be exactly zoom squared. Also, we will adjust to target device logical DPI,
// if we, for example are using 4K, or 8K monitors. // if we, for example are using 4K, or 8K monitors.
qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant())); qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant()));
zoom = PDFWidgetUtils::scaleDPI_x(painter->device(), zoom); zoom = PDFWidgetUtils::scaleDPI_x(device, zoom);
QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom); QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom);
unzoomedRect.translate(0, -unzoomedRect.height()); unzoomedRect.translate(0, -unzoomedRect.height());
annotationRectangle = unzoomedRect; annotationRectangle = unzoomedRect;
}
return userSpaceToDeviceSpace;
}
void PDFAnnotationManager::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
const PageAnnotations& annotations = getPageAnnotations(pageIndex);
if (!annotations.isEmpty())
{
const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
Q_ASSERT(page);
PDFRenderer::Features features = m_features;
if (!features.testFlag(PDFRenderer::DisplayAnnotations))
{
// Annotation displaying is disabled
return;
}
Q_ASSERT(m_fontCache);
Q_ASSERT(m_cmsManager);
Q_ASSERT(m_optionalActivity);
const PDFCMSPointer cms = m_cmsManager->getCurrentCMS();
m_fontCache->setCacheShrinkEnabled(this, false);
for (const PageAnnotation& annotation : annotations.annotations)
{
const PDFAnnotation::Flags annotationFlags = annotation.annotation->getFlags();
if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden
(m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed
(m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView))) // Target is view, and annotation is disabled for screen
{
continue;
}
PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation));
if (!appearanceStreamObject.isStream())
{
// Object is not valid appearance stream. We will try to draw default
// annotation appearance.
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
AnnotationDrawParameters parameters;
parameters.painter = painter;
parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState());
annotation.annotation->draw(parameters);
painter->restore();
continue;
}
PDFDocumentDataLoaderDecorator loader(m_document);
const PDFStream* formStream = appearanceStreamObject.getStream();
const PDFDictionary* formDictionary = formStream->getDictionary();
QRectF annotationRectangle = annotation.annotation->getRectangle();
QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF());
QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix());
QByteArray content = m_document->getDecodedStream(formStream);
PDFObject resources = m_document->getObject(formDictionary->get("Resources"));
PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group"));
if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty())
{
// Form bounding box is empty
continue;
}
QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle);
if (annotationFlags.testFlag(PDFAnnotation::NoZoom))
{
features.setFlag(PDFRenderer::ClipToCropBox, false); 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 auto getAppearanceStream = [&pageAnnotation] (void) -> PDFObject
{ {
return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState()); return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState());
}; };
QMutexLocker lock(&m_mutex);
return pageAnnotation.appearanceStream.get(getAppearanceStream); return pageAnnotation.appearanceStream.get(getAppearanceStream);
} }
PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const const PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const
{
return const_cast<PDFAnnotationManager*>(this)->getPageAnnotations(pageIndex);
}
PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex)
{ {
Q_ASSERT(m_document); Q_ASSERT(m_document);
@ -1112,6 +1136,16 @@ PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(
return it->second; return it->second;
} }
bool PDFAnnotationManager::hasAnnotation(PDFInteger pageIndex) const
{
return !getPageAnnotations(pageIndex).isEmpty();
}
bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector<PDFInteger>& pageIndices) const
{
return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1));
}
PDFRenderer::Features PDFAnnotationManager::getFeatures() const PDFRenderer::Features PDFAnnotationManager::getFeatures() const
{ {
return m_features; return m_features;
@ -1175,6 +1209,99 @@ PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager()
m_proxy->unregisterDrawInterface(this); 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<PDFInteger> 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<const PDFMarkupAnnotation*>(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("<p><b>%1 (%2)</b></p><p>%3</p>").arg(markupAnnotation->getWindowTitle(), markupAnnotation->getCreationDate().toLocalTime().toString(), markupAnnotation->getContents());
}
}
}
else
{
pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal;
}
}
}
}
void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const
{ {
Q_ASSERT(parameters.painter); Q_ASSERT(parameters.painter);

View File

@ -32,6 +32,10 @@
#include <array> #include <array>
class QKeyEvent;
class QMouseEvent;
class QWheelEvent;
namespace pdf namespace pdf
{ {
class PDFObjectStorage; class PDFObjectStorage;
@ -1284,12 +1288,13 @@ public:
PDFRenderer::Features getFeatures() const; PDFRenderer::Features getFeatures() const;
void setFeatures(PDFRenderer::Features features); void setFeatures(PDFRenderer::Features features);
private: protected:
struct PageAnnotation struct PageAnnotation
{ {
PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal; PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal;
PDFCachedItem<PDFObject> appearanceStream;
PDFAnnotationPtr annotation; PDFAnnotationPtr annotation;
mutable PDFCachedItem<PDFObject> appearanceStream;
}; };
struct PageAnnotations struct PageAnnotations
@ -1299,14 +1304,37 @@ private:
std::vector<PageAnnotation> annotations; std::vector<PageAnnotation> 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 /// Returns current appearance stream for given page annotation
/// \param pageAnnotation 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. /// Returns reference to page annotation for given page index.
/// This function requires, that pointer to m_document is valid. /// This function requires, that pointer to m_document is valid.
/// \param pageIndex Page index (must point to valid page) /// \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<PDFInteger>& pageIndices) const;
const PDFDocument* m_document; const PDFDocument* m_document;
@ -1334,8 +1362,39 @@ public:
explicit PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent); explicit PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent);
virtual ~PDFWidgetAnnotationManager() override; 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: private:
void updateFromMouseEvent(QMouseEvent* event);
PDFDrawWidgetProxy* m_proxy; PDFDrawWidgetProxy* m_proxy;
QString m_tooltip;
}; };
} // namespace pdf } // namespace pdf

View File

@ -35,7 +35,6 @@ namespace pdf
{ {
class PDFProgress; class PDFProgress;
class PDFWidget; class PDFWidget;
class IDrawWidget;
class PDFCMSManager; class PDFCMSManager;
class PDFTextLayoutGetter; class PDFTextLayoutGetter;
class PDFWidgetAnnotationManager; class PDFWidgetAnnotationManager;

View File

@ -19,6 +19,7 @@
#include "pdfdrawspacecontroller.h" #include "pdfdrawspacecontroller.h"
#include "pdfcompiler.h" #include "pdfcompiler.h"
#include "pdfwidgettool.h" #include "pdfwidgettool.h"
#include "pdfannotation.h"
#include <QPainter> #include <QPainter>
#include <QGridLayout> #include <QGridLayout>
@ -232,6 +233,17 @@ void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
} }
} }
if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager())
{
annotationManager->keyPressEvent(this, event);
setToolTip(annotationManager->getTooltip());
if (event->isAccepted())
{
updateCursor();
return;
}
}
// Vertical navigation // Vertical navigation
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
if (verticalScrollbar->isVisible()) if (verticalScrollbar->isVisible())
@ -275,6 +287,17 @@ void PDFDrawWidgetBase<BaseWidget>::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) if (event->button() == Qt::LeftButton)
{ {
m_mouseOperation = MouseOperation::Translate; m_mouseOperation = MouseOperation::Translate;
@ -301,6 +324,17 @@ void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
} }
} }
if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager())
{
annotationManager->mouseReleaseEvent(this, event);
setToolTip(annotationManager->getTooltip());
if (event->isAccepted())
{
updateCursor();
return;
}
}
performMouseOperation(event->pos()); performMouseOperation(event->pos());
switch (m_mouseOperation) switch (m_mouseOperation)
@ -338,6 +372,17 @@ void PDFDrawWidgetBase<BaseWidget>::mouseMoveEvent(QMouseEvent* event)
} }
} }
if (PDFWidgetAnnotationManager* annotationManager = getPDFWidget()->getDrawWidgetProxy()->getAnnotationManager())
{
annotationManager->mouseMoveEvent(this, event);
setToolTip(annotationManager->getTooltip());
if (event->isAccepted())
{
updateCursor();
return;
}
}
performMouseOperation(event->pos()); performMouseOperation(event->pos());
updateCursor(); updateCursor();
event->accept(); event->accept();
@ -395,6 +440,17 @@ void PDFDrawWidgetBase<BaseWidget>::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(); Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy(); PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();