File indexing completed on 2024-04-28 15:51:39

0001 /*
0002     SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "annotationtools.h"
0008 
0009 // qt / kde includes
0010 #include <QColor>
0011 #include <QCursor>
0012 #include <QEvent>
0013 #include <QMouseEvent>
0014 
0015 // local includes
0016 #include "core/annotations.h"
0017 
0018 AnnotatorEngine::AnnotatorEngine(const QDomElement &engineElement)
0019     : m_engineElement(engineElement)
0020     , m_creationCompleted(false)
0021     , m_item(nullptr)
0022 {
0023     // parse common engine attributes
0024     if (engineElement.hasAttribute(QStringLiteral("color"))) {
0025         m_engineColor = QColor(engineElement.attribute(QStringLiteral("color")));
0026     }
0027 
0028     // get the annotation element
0029     QDomElement annElement = m_engineElement.firstChild().toElement();
0030     if (!annElement.isNull() && annElement.tagName() == QLatin1String("annotation")) {
0031         m_annotElement = annElement;
0032     }
0033 }
0034 
0035 void AnnotatorEngine::decodeEvent(const QMouseEvent *mouseEvent, EventType *eventType, Button *button)
0036 {
0037     *eventType = AnnotatorEngine::Press;
0038     if (mouseEvent->type() == QEvent::MouseMove) {
0039         *eventType = AnnotatorEngine::Move;
0040     } else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
0041         *eventType = AnnotatorEngine::Release;
0042     }
0043 
0044     *button = AnnotatorEngine::None;
0045     const Qt::MouseButtons buttonState = (*eventType == AnnotatorEngine::Move) ? mouseEvent->buttons() : mouseEvent->button();
0046     if (buttonState == Qt::LeftButton) {
0047         *button = AnnotatorEngine::Left;
0048     } else if (buttonState == Qt::RightButton) {
0049         *button = AnnotatorEngine::Right;
0050     }
0051 }
0052 
0053 void AnnotatorEngine::decodeEvent(const QTabletEvent *tabletEvent, EventType *eventType, Button *button)
0054 {
0055     switch (tabletEvent->type()) {
0056     case QEvent::TabletPress:
0057         // Tablet press event is equivalent to pressing the left mouse button
0058         *button = AnnotatorEngine::Left;
0059         *eventType = AnnotatorEngine::Press;
0060         break;
0061     case QEvent::TabletRelease:
0062         // Tablet release event is equivalent to releasing the left mouse button
0063         *button = AnnotatorEngine::Left;
0064         *eventType = AnnotatorEngine::Release;
0065         break;
0066     case QEvent::TabletMove:
0067         // Tablet events are only routed if the pen is down so
0068         // this is equivalent to the left mouse button being pressed
0069         *button = AnnotatorEngine::Left;
0070         *eventType = AnnotatorEngine::Move;
0071         break;
0072     default:
0073         Q_ASSERT(false);
0074         break;
0075     }
0076 }
0077 
0078 AnnotatorEngine::~AnnotatorEngine()
0079 {
0080 }
0081 
0082 QCursor AnnotatorEngine::cursor() const
0083 {
0084     return Qt::CrossCursor;
0085 }
0086 
0087 SmoothPath::SmoothPath(const QList<Okular::NormalizedPoint> &points, const QPen &pen, qreal opacity, QPainter::CompositionMode compositionMode)
0088     : points(points)
0089     , pen(pen)
0090     , opacity(opacity)
0091     , compositionMode(compositionMode)
0092 {
0093 }
0094 
0095 /** SmoothPathEngine */
0096 SmoothPathEngine::SmoothPathEngine(const QDomElement &engineElement)
0097     : AnnotatorEngine(engineElement)
0098     , compositionMode(QPainter::CompositionMode_SourceOver)
0099 {
0100     // parse engine specific attributes
0101     if (engineElement.attribute(QStringLiteral("compositionMode"), QStringLiteral("sourceOver")) == QLatin1String("clear")) {
0102         compositionMode = QPainter::CompositionMode_Clear;
0103     }
0104 }
0105 
0106 QRect SmoothPathEngine::event(EventType type, Button button, Modifiers /*modifiers*/, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/)
0107 {
0108     // only proceed if pressing left button
0109     if (button != Left) {
0110         return QRect();
0111     }
0112 
0113     // start operation
0114     if (type == Press && points.isEmpty()) {
0115         lastPoint.x = nX;
0116         lastPoint.y = nY;
0117         totalRect.left = totalRect.right = lastPoint.x;
0118         totalRect.top = totalRect.bottom = lastPoint.y;
0119         points.append(lastPoint);
0120     }
0121     // add a point to the path
0122     else if (type == Move && points.count() > 0) {
0123         // double dist = hypot( nX - points.last().x, nY - points.last().y );
0124         // if ( dist > 0.0001 )
0125         //{
0126         // append mouse position (as normalized point) to the list
0127         Okular::NormalizedPoint nextPoint = Okular::NormalizedPoint(nX, nY);
0128         points.append(nextPoint);
0129         // update total rect
0130         totalRect.left = qMin(totalRect.left, nX);
0131         totalRect.top = qMin(totalRect.top, nY);
0132         totalRect.right = qMax(nX, totalRect.right);
0133         totalRect.bottom = qMax(nY, totalRect.bottom);
0134         // paint the difference to previous full rect
0135         Okular::NormalizedRect incrementalRect;
0136         incrementalRect.left = qMin(nextPoint.x, lastPoint.x);
0137         incrementalRect.right = qMax(nextPoint.x, lastPoint.x);
0138         incrementalRect.top = qMin(nextPoint.y, lastPoint.y);
0139         incrementalRect.bottom = qMax(nextPoint.y, lastPoint.y);
0140         lastPoint = nextPoint;
0141         return incrementalRect.geometry((int)xScale, (int)yScale);
0142         //}
0143     }
0144     // terminate process
0145     else if (type == Release && points.count() > 0) {
0146         if (points.count() < 2) {
0147             points.clear();
0148         } else {
0149             m_creationCompleted = true;
0150         }
0151         return totalRect.geometry((int)xScale, (int)yScale);
0152     }
0153     return QRect();
0154 }
0155 
0156 void SmoothPathEngine::paint(QPainter *painter, double xScale, double yScale, const QRect & /*clipRect*/)
0157 {
0158     const double penWidth = m_annotElement.attribute(QStringLiteral("width"), QStringLiteral("1")).toInt();
0159     const qreal opacity = m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble();
0160 
0161     // use engine's color for painting
0162     const SmoothPath path(points, QPen(m_engineColor, penWidth), opacity, compositionMode);
0163 
0164     // draw the path
0165     path.paint(painter, xScale, yScale);
0166 }
0167 
0168 void SmoothPath::paint(QPainter *painter, double xScale, double yScale) const
0169 {
0170     // draw SmoothPaths with at least 2 points
0171     if (points.count() > 1) {
0172         painter->setCompositionMode(compositionMode);
0173         painter->setPen(pen);
0174         painter->setOpacity(opacity);
0175 
0176         QPainterPath path;
0177         QList<Okular::NormalizedPoint>::const_iterator pIt = points.begin(), pEnd = points.end();
0178         path.moveTo(QPointF(pIt->x * xScale, pIt->y * yScale));
0179         ++pIt;
0180         for (; pIt != pEnd; ++pIt) {
0181             path.lineTo(QPointF(pIt->x * xScale, pIt->y * yScale));
0182         }
0183         painter->drawPath(path);
0184     }
0185 }
0186 
0187 QList<Okular::Annotation *> SmoothPathEngine::end()
0188 {
0189     m_creationCompleted = false;
0190 
0191     // find out annotation's description node
0192     if (m_annotElement.isNull()) {
0193         return QList<Okular::Annotation *>();
0194     }
0195 
0196     // find out annotation's type
0197     Okular::Annotation *ann = nullptr;
0198     QString typeString = m_annotElement.attribute(QStringLiteral("type"));
0199 
0200     // create InkAnnotation from path
0201     if (typeString == QLatin1String("Ink")) {
0202         Okular::InkAnnotation *ia = new Okular::InkAnnotation();
0203         ann = ia;
0204         if (m_annotElement.hasAttribute(QStringLiteral("width"))) {
0205             ann->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
0206         }
0207         // fill points
0208         QList<QList<Okular::NormalizedPoint>> list = ia->inkPaths();
0209         list.append(points);
0210         ia->setInkPaths(list);
0211         // set boundaries
0212         ia->setBoundingRectangle(totalRect);
0213     }
0214 
0215     // safety check
0216     if (!ann) {
0217         return QList<Okular::Annotation *>();
0218     }
0219 
0220     // set common attributes
0221     ann->style().setColor(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
0222     if (m_annotElement.hasAttribute(QStringLiteral("opacity"))) {
0223         ann->style().setOpacity(m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble());
0224     }
0225 
0226     // return annotation
0227     return QList<Okular::Annotation *>() << ann;
0228 }
0229 
0230 SmoothPath SmoothPathEngine::endSmoothPath()
0231 {
0232     m_creationCompleted = false;
0233 
0234     QColor color(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
0235 
0236     const int width = m_annotElement.attribute(QStringLiteral("width"), QStringLiteral("2")).toInt();
0237     const qreal opacity = m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble();
0238 
0239     return SmoothPath(points, QPen(color, width), opacity, compositionMode);
0240 }
0241 
0242 /* kate: replace-tabs on; indent-width 4; */