mirror of
https://invent.kde.org/graphics/okular
synced 2024-08-27 11:40:07 +00:00
![Nikola Nikolic](/assets/img/avatar_default.png)
There are three reasons why scrolling happens (first two cases of scrolling are actually due to viewport transition that is triggered by annotation being traslated/created): 1. VisiblePageRect are NormalizedRect and should be represented by four values in [0, 1] interval. Due to rounding errors caused by using QRect, they only reach the [0, 0.999] interval. That means that right and bottom edge of the page are never visible so if annotation is translated along the right/bottom edge viewport transition occurs (fixed in PageView::slotRequestVisiblePixmaps) 2. For freehand line there are some constants (dX, dY) that make a line to be drawn outside the page borders causing viewport transition. Removing these constants solves the problem (fixed in SmoothPathEngine::event) 3. Browse mode is activated as soon as the cursor is moved outside the viewport. Instead, we should forward the move event to the annotation which than can ignore the event (fixed in PageView::mouseMoveEvent) BUG: 436742
243 lines
8.2 KiB
C++
243 lines
8.2 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "annotationtools.h"
|
|
|
|
// qt / kde includes
|
|
#include <QColor>
|
|
#include <QCursor>
|
|
#include <QEvent>
|
|
#include <QMouseEvent>
|
|
|
|
// local includes
|
|
#include "core/annotations.h"
|
|
|
|
AnnotatorEngine::AnnotatorEngine(const QDomElement &engineElement)
|
|
: m_engineElement(engineElement)
|
|
, m_creationCompleted(false)
|
|
, m_item(nullptr)
|
|
{
|
|
// parse common engine attributes
|
|
if (engineElement.hasAttribute(QStringLiteral("color"))) {
|
|
m_engineColor = QColor(engineElement.attribute(QStringLiteral("color")));
|
|
}
|
|
|
|
// get the annotation element
|
|
QDomElement annElement = m_engineElement.firstChild().toElement();
|
|
if (!annElement.isNull() && annElement.tagName() == QLatin1String("annotation")) {
|
|
m_annotElement = annElement;
|
|
}
|
|
}
|
|
|
|
void AnnotatorEngine::decodeEvent(const QMouseEvent *mouseEvent, EventType *eventType, Button *button)
|
|
{
|
|
*eventType = AnnotatorEngine::Press;
|
|
if (mouseEvent->type() == QEvent::MouseMove) {
|
|
*eventType = AnnotatorEngine::Move;
|
|
} else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
|
|
*eventType = AnnotatorEngine::Release;
|
|
}
|
|
|
|
*button = AnnotatorEngine::None;
|
|
const Qt::MouseButtons buttonState = (*eventType == AnnotatorEngine::Move) ? mouseEvent->buttons() : mouseEvent->button();
|
|
if (buttonState == Qt::LeftButton) {
|
|
*button = AnnotatorEngine::Left;
|
|
} else if (buttonState == Qt::RightButton) {
|
|
*button = AnnotatorEngine::Right;
|
|
}
|
|
}
|
|
|
|
void AnnotatorEngine::decodeEvent(const QTabletEvent *tabletEvent, EventType *eventType, Button *button)
|
|
{
|
|
switch (tabletEvent->type()) {
|
|
case QEvent::TabletPress:
|
|
// Tablet press event is equivalent to pressing the left mouse button
|
|
*button = AnnotatorEngine::Left;
|
|
*eventType = AnnotatorEngine::Press;
|
|
break;
|
|
case QEvent::TabletRelease:
|
|
// Tablet release event is equivalent to releasing the left mouse button
|
|
*button = AnnotatorEngine::Left;
|
|
*eventType = AnnotatorEngine::Release;
|
|
break;
|
|
case QEvent::TabletMove:
|
|
// Tablet events are only routed if the pen is down so
|
|
// this is equivalent to the left mouse button being pressed
|
|
*button = AnnotatorEngine::Left;
|
|
*eventType = AnnotatorEngine::Move;
|
|
break;
|
|
default:
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
AnnotatorEngine::~AnnotatorEngine()
|
|
{
|
|
}
|
|
|
|
QCursor AnnotatorEngine::cursor() const
|
|
{
|
|
return Qt::CrossCursor;
|
|
}
|
|
|
|
SmoothPath::SmoothPath(const QList<Okular::NormalizedPoint> &points, const QPen &pen, qreal opacity, QPainter::CompositionMode compositionMode)
|
|
: points(points)
|
|
, pen(pen)
|
|
, opacity(opacity)
|
|
, compositionMode(compositionMode)
|
|
{
|
|
}
|
|
|
|
/** SmoothPathEngine */
|
|
SmoothPathEngine::SmoothPathEngine(const QDomElement &engineElement)
|
|
: AnnotatorEngine(engineElement)
|
|
, compositionMode(QPainter::CompositionMode_SourceOver)
|
|
{
|
|
// parse engine specific attributes
|
|
if (engineElement.attribute(QStringLiteral("compositionMode"), QStringLiteral("sourceOver")) == QLatin1String("clear")) {
|
|
compositionMode = QPainter::CompositionMode_Clear;
|
|
}
|
|
}
|
|
|
|
QRect SmoothPathEngine::event(EventType type, Button button, Modifiers /*modifiers*/, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/)
|
|
{
|
|
// only proceed if pressing left button
|
|
if (button != Left) {
|
|
return QRect();
|
|
}
|
|
|
|
// start operation
|
|
if (type == Press && points.isEmpty()) {
|
|
lastPoint.x = nX;
|
|
lastPoint.y = nY;
|
|
totalRect.left = totalRect.right = lastPoint.x;
|
|
totalRect.top = totalRect.bottom = lastPoint.y;
|
|
points.append(lastPoint);
|
|
}
|
|
// add a point to the path
|
|
else if (type == Move && points.count() > 0) {
|
|
// double dist = hypot( nX - points.last().x, nY - points.last().y );
|
|
// if ( dist > 0.0001 )
|
|
//{
|
|
// append mouse position (as normalized point) to the list
|
|
Okular::NormalizedPoint nextPoint = Okular::NormalizedPoint(nX, nY);
|
|
points.append(nextPoint);
|
|
// update total rect
|
|
totalRect.left = qMin(totalRect.left, nX);
|
|
totalRect.top = qMin(totalRect.top, nY);
|
|
totalRect.right = qMax(nX, totalRect.right);
|
|
totalRect.bottom = qMax(nY, totalRect.bottom);
|
|
// paint the difference to previous full rect
|
|
Okular::NormalizedRect incrementalRect;
|
|
incrementalRect.left = qMin(nextPoint.x, lastPoint.x);
|
|
incrementalRect.right = qMax(nextPoint.x, lastPoint.x);
|
|
incrementalRect.top = qMin(nextPoint.y, lastPoint.y);
|
|
incrementalRect.bottom = qMax(nextPoint.y, lastPoint.y);
|
|
lastPoint = nextPoint;
|
|
return incrementalRect.geometry((int)xScale, (int)yScale);
|
|
//}
|
|
}
|
|
// terminate process
|
|
else if (type == Release && points.count() > 0) {
|
|
if (points.count() < 2) {
|
|
points.clear();
|
|
} else {
|
|
m_creationCompleted = true;
|
|
}
|
|
return totalRect.geometry((int)xScale, (int)yScale);
|
|
}
|
|
return QRect();
|
|
}
|
|
|
|
void SmoothPathEngine::paint(QPainter *painter, double xScale, double yScale, const QRect & /*clipRect*/)
|
|
{
|
|
const double penWidth = m_annotElement.attribute(QStringLiteral("width"), QStringLiteral("1")).toInt();
|
|
const qreal opacity = m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble();
|
|
|
|
// use engine's color for painting
|
|
const SmoothPath path(points, QPen(m_engineColor, penWidth), opacity, compositionMode);
|
|
|
|
// draw the path
|
|
path.paint(painter, xScale, yScale);
|
|
}
|
|
|
|
void SmoothPath::paint(QPainter *painter, double xScale, double yScale) const
|
|
{
|
|
// draw SmoothPaths with at least 2 points
|
|
if (points.count() > 1) {
|
|
painter->setCompositionMode(compositionMode);
|
|
painter->setPen(pen);
|
|
painter->setOpacity(opacity);
|
|
|
|
QPainterPath path;
|
|
QList<Okular::NormalizedPoint>::const_iterator pIt = points.begin(), pEnd = points.end();
|
|
path.moveTo(QPointF(pIt->x * xScale, pIt->y * yScale));
|
|
++pIt;
|
|
for (; pIt != pEnd; ++pIt) {
|
|
path.lineTo(QPointF(pIt->x * xScale, pIt->y * yScale));
|
|
}
|
|
painter->drawPath(path);
|
|
}
|
|
}
|
|
|
|
QList<Okular::Annotation *> SmoothPathEngine::end()
|
|
{
|
|
m_creationCompleted = false;
|
|
|
|
// find out annotation's description node
|
|
if (m_annotElement.isNull()) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// find out annotation's type
|
|
Okular::Annotation *ann = nullptr;
|
|
QString typeString = m_annotElement.attribute(QStringLiteral("type"));
|
|
|
|
// create InkAnnotation from path
|
|
if (typeString == QLatin1String("Ink")) {
|
|
Okular::InkAnnotation *ia = new Okular::InkAnnotation();
|
|
ann = ia;
|
|
if (m_annotElement.hasAttribute(QStringLiteral("width"))) {
|
|
ann->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
|
|
}
|
|
// fill points
|
|
QList<QList<Okular::NormalizedPoint>> list = ia->inkPaths();
|
|
list.append(points);
|
|
ia->setInkPaths(list);
|
|
// set boundaries
|
|
ia->setBoundingRectangle(totalRect);
|
|
}
|
|
|
|
// safety check
|
|
if (!ann) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// set common attributes
|
|
ann->style().setColor(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
|
|
if (m_annotElement.hasAttribute(QStringLiteral("opacity"))) {
|
|
ann->style().setOpacity(m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble());
|
|
}
|
|
|
|
// return annotation
|
|
return QList<Okular::Annotation *>() << ann;
|
|
}
|
|
|
|
SmoothPath SmoothPathEngine::endSmoothPath()
|
|
{
|
|
m_creationCompleted = false;
|
|
|
|
QColor color(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
|
|
|
|
const int width = m_annotElement.attribute(QStringLiteral("width"), QStringLiteral("2")).toInt();
|
|
const qreal opacity = m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble();
|
|
|
|
return SmoothPath(points, QPen(color, width), opacity, compositionMode);
|
|
}
|
|
|
|
/* kate: replace-tabs on; indent-width 4; */
|