File indexing completed on 2025-02-02 04:26:09

0001 /*
0002  *  SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include "AnnotationTool.h"
0010 #include "History.h"
0011 
0012 #include <QColor>
0013 #include <QFont>
0014 #include <QImage>
0015 #include <QMatrix4x4>
0016 #include <QObject>
0017 #include <QVariant>
0018 
0019 class AnnotationTool;
0020 class SelectedItemWrapper;
0021 class QPainter;
0022 
0023 /**
0024  * This class is used to render an image with annotations. The annotations are vector graphics
0025  * and image effects created from a stack of history items that can be undone or redone.
0026  * `paint()` and `renderToImage()` will be used by clients (e.g., AnnotationViewport) to render
0027  * their own content. There can be any amount of clients sharing the same AnnotationDocument.
0028  */
0029 class AnnotationDocument : public QObject
0030 {
0031     Q_OBJECT
0032     Q_PROPERTY(AnnotationTool *tool READ tool CONSTANT)
0033     Q_PROPERTY(SelectedItemWrapper *selectedItem READ selectedItemWrapper NOTIFY selectedItemWrapperChanged)
0034 
0035     Q_PROPERTY(int redoStackDepth READ redoStackDepth NOTIFY redoStackDepthChanged)
0036     Q_PROPERTY(int undoStackDepth READ undoStackDepth NOTIFY undoStackDepthChanged)
0037     Q_PROPERTY(QSizeF canvasSize READ canvasSize NOTIFY canvasSizeChanged)
0038     Q_PROPERTY(QSizeF imageSize READ imageSize NOTIFY imageSizeChanged)
0039     Q_PROPERTY(qreal imageDpr READ imageDpr NOTIFY imageDprChanged)
0040 
0041 public:
0042     enum class ContinueOption {
0043         NoOptions    = 0b00,
0044         SnapAngle    = 0b01,
0045         CenterResize = 0b10
0046     };
0047     Q_DECLARE_FLAGS(ContinueOptions, ContinueOption)
0048     Q_FLAG(ContinueOption)
0049 
0050     explicit AnnotationDocument(QObject *parent = nullptr);
0051     ~AnnotationDocument();
0052 
0053     AnnotationTool *tool() const;
0054     SelectedItemWrapper *selectedItemWrapper() const;
0055 
0056     int undoStackDepth() const;
0057     int redoStackDepth() const;
0058 
0059     QSizeF canvasSize() const;
0060 
0061     /// Image size in raw pixels
0062     QSizeF imageSize() const;
0063 
0064     /// Image device pixel ratio
0065     qreal imageDpr() const;
0066 
0067     /// Set the base image. Cannot be undone.
0068     void setImage(const QImage &image);
0069 
0070     /// Remove annotations that do not intersect with the rectangle and crop the image.
0071     /// Cannot be undone.
0072     void cropCanvas(const QRectF &cropRect);
0073 
0074     /// Clear all annotations. Cannot be undone.
0075     void clearAnnotations();
0076 
0077     /// Clear all annotations and the image. Cannot be undone.
0078     void clear();
0079 
0080     struct Viewport {
0081         QRectF rect;
0082         qreal scale = 1;
0083     };
0084 
0085     // Paint the base image.
0086     void paintBaseImage(QPainter *painter, const Viewport &viewport) const;
0087     // Paint the annotations. If the span is not set, all annotations will be painted.
0088     void paintAnnotations(QPainter *painter, const Viewport &viewport, std::optional<History::ConstSpan> span = {}) const;
0089     // Paint the base image and annotations.
0090     void paint(QPainter *painter, const Viewport &viewport, std::optional<History::ConstSpan> span = {}) const;
0091     QImage renderToImage(const Viewport &viewport, std::optional<History::ConstSpan> span = {}) const;
0092     QImage renderToImage(std::optional<History::ConstSpan> span = {}) const;
0093 
0094     // True when there is an item at the end of the undo stack and it is invalid.
0095     bool isCurrentItemValid() const;
0096 
0097     HistoryItem::shared_ptr popCurrentItem();
0098 
0099     // The first item with a mouse path intersecting the specified rectangle.
0100     // The rectangle is meant to be used as a way to make selecting an item more forgiving
0101     // by adding margins around the center of where the actual target point is.
0102     HistoryItem::const_shared_ptr itemAt(const QRectF &rect) const;
0103 
0104     Q_INVOKABLE void undo();
0105     Q_INVOKABLE void redo();
0106 
0107     // For starting a new item
0108     void beginItem(const QPointF &point);
0109     void continueItem(const QPointF &point, AnnotationDocument::ContinueOptions options = ContinueOption::NoOptions);
0110     void finishItem();
0111 
0112     // For managing an existing item
0113     Q_INVOKABLE void selectItem(const QRectF &rect);
0114     Q_INVOKABLE void deselectItem();
0115     Q_INVOKABLE void deleteSelectedItem();
0116 
0117 Q_SIGNALS:
0118     void selectedItemWrapperChanged();
0119     void undoStackDepthChanged();
0120     void redoStackDepthChanged();
0121     void canvasSizeChanged();
0122     void imageSizeChanged();
0123     void imageDprChanged();
0124 
0125     void repaintNeeded(const QRectF &area = {});
0126 
0127 private:
0128     friend class SelectedItemWrapper;
0129 
0130     void addItem(const HistoryItem::shared_ptr &item);
0131     void emitRepaintNeededUnlessEmpty(const QRectF &area);
0132 
0133     AnnotationTool *m_tool;
0134     SelectedItemWrapper *m_selectedItemWrapper;
0135 
0136     QRectF m_canvasRect;
0137     QImage m_image;
0138     // A temporary version of the item we want to edit so we can modify at will. This will be used
0139     // instead of the original item when rendering, but the original item will remain in history
0140     // until the changes are committed.
0141     HistoryItem::shared_ptr m_tempItem;
0142     History m_history;
0143 };
0144 
0145 /**
0146  * When the user selects an existing shape with the mouse, this wraps all the parameters of the associated item, so that they can be modified from QML
0147  */
0148 class SelectedItemWrapper : public QObject
0149 {
0150     Q_OBJECT
0151     Q_PROPERTY(bool hasSelection READ hasSelection CONSTANT)
0152     Q_PROPERTY(AnnotationTool::Options options READ options CONSTANT)
0153     Q_PROPERTY(int strokeWidth READ strokeWidth WRITE setStrokeWidth NOTIFY strokeWidthChanged)
0154     Q_PROPERTY(QColor strokeColor READ strokeColor WRITE setStrokeColor NOTIFY strokeColorChanged)
0155     Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor NOTIFY fillColorChanged)
0156     Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
0157     Q_PROPERTY(QColor fontColor READ fontColor WRITE setFontColor NOTIFY fontColorChanged)
0158     Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged)
0159     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
0160     Q_PROPERTY(bool shadow READ hasShadow WRITE setShadow NOTIFY shadowChanged)
0161     Q_PROPERTY(QPainterPath mousePath READ mousePath NOTIFY mousePathChanged)
0162 
0163 public:
0164     SelectedItemWrapper(AnnotationDocument *document);
0165     ~SelectedItemWrapper();
0166 
0167     // The item we are selecting.
0168     HistoryItem::const_weak_ptr selectedItem() const;
0169     void setSelectedItem(const HistoryItem::const_shared_ptr &item);
0170 
0171     // Transform the item with the given x and y deltas and at the specified edges.
0172     // Specifying no edges or all edges only translates.
0173     // We don't set things like scale directly because that would require more complex logic to be
0174     // written in various places in QML files.
0175     Q_INVOKABLE void transform(qreal dx, qreal dy, Qt::Edges edges = {});
0176 
0177     // Pushes the temporary item to history and sets the selected item as the temporary item parent.
0178     // Returns whether the commit actually happened.
0179     Q_INVOKABLE bool commitChanges();
0180 
0181     // Resets the selected item, temp item and options.
0182     // Returns the render area of the reset items.
0183     QRectF reset();
0184 
0185     bool hasSelection() const;
0186 
0187     AnnotationTool::Options options() const;
0188 
0189     int strokeWidth() const;
0190     void setStrokeWidth(int width);
0191 
0192     QColor strokeColor() const;
0193     void setStrokeColor(const QColor &color);
0194 
0195     QColor fillColor() const;
0196     void setFillColor(const QColor &color);
0197 
0198     QFont font() const;
0199     void setFont(const QFont &font);
0200 
0201     QColor fontColor() const;
0202     void setFontColor(const QColor &color);
0203 
0204     int number() const;
0205     void setNumber(int number);
0206 
0207     QString text() const;
0208     void setText(const QString &text);
0209 
0210     bool hasShadow() const;
0211     void setShadow(bool shadow);
0212 
0213     QPainterPath mousePath() const;
0214 
0215 Q_SIGNALS:
0216     void strokeWidthChanged();
0217     void strokeColorChanged();
0218     void fillColorChanged();
0219     void fontChanged();
0220     void fontColorChanged();
0221     void numberChanged();
0222     void textChanged();
0223     void shadowChanged();
0224     void mousePathChanged();
0225 
0226 private:
0227     AnnotationTool::Options m_options;
0228     HistoryItem::const_weak_ptr m_selectedItem;
0229     AnnotationDocument *const m_document;
0230 };
0231 
0232 QDebug operator<<(QDebug debug, const SelectedItemWrapper *);
0233 
0234 Q_DECLARE_OPERATORS_FOR_FLAGS(AnnotationDocument::ContinueOptions)