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 "pdfpagecontentprocessor.h"
#include "pdfparser.h"
#include "pdfdrawwidget.h"
#include <QApplication>
#include <QMouseEvent>
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<PDFAnnotationManager*>(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<PDFInteger>& 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<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
{
Q_ASSERT(parameters.painter);

View File

@ -32,6 +32,10 @@
#include <array>
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<PDFObject> appearanceStream;
PDFAnnotationPtr annotation;
mutable PDFCachedItem<PDFObject> appearanceStream;
};
struct PageAnnotations
@ -1299,14 +1304,37 @@ private:
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
/// \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<PDFInteger>& 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

View File

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

View File

@ -19,6 +19,7 @@
#include "pdfdrawspacecontroller.h"
#include "pdfcompiler.h"
#include "pdfwidgettool.h"
#include "pdfannotation.h"
#include <QPainter>
#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
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
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)
{
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());
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());
updateCursor();
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();
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();