Skip to content

Commit

Permalink
Merge pull request #5179 from opengisch/drawing_undo
Browse files Browse the repository at this point in the history
Implement undo mechanism for drawing canvas
  • Loading branch information
nirvn authored Apr 12, 2024
2 parents 20fd99e + bdea9aa commit c5672da
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 11 deletions.
37 changes: 34 additions & 3 deletions src/core/drawingcanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ DrawingCanvas::DrawingCanvas( QQuickItem *parent )

void DrawingCanvas::createBlankCanvas( int width, int height, QColor backgroundColor )
{
clear();

mBackgroundImage = QImage( QSize( width, height ), QImage::Format_ARGB32 );
mBackgroundImage.fill( backgroundColor );

Expand All @@ -44,6 +46,8 @@ void DrawingCanvas::createBlankCanvas( int width, int height, QColor backgroundC

void DrawingCanvas::createCanvasFromImage( const QString &path )
{
clear();

QImageReader imageReader( path.startsWith( QStringLiteral( "file://" ) ) ? path.mid( 7 ) : path );
imageReader.setAutoTransform( true );
mBackgroundImage = imageReader.read();
Expand Down Expand Up @@ -71,6 +75,8 @@ void DrawingCanvas::createCanvasFromImage( const QString &path )

void DrawingCanvas::clear()
{
mStrokes.clear();

mBackgroundImage = QImage();
mDrawingImage = QImage();

Expand Down Expand Up @@ -257,13 +263,15 @@ void DrawingCanvas::strokeEnd( const QPointF &point )
painter.setRenderHint( QPainter::Antialiasing, true );
drawStroke( &painter, mCurrentStroke );

mStrokes << mCurrentStroke;
mCurrentStroke.points.clear();

setIsDirty( true );

update();
}

void DrawingCanvas::drawStroke( QPainter *painter, Stroke &stroke, bool onCanvas )
void DrawingCanvas::drawStroke( QPainter *painter, const Stroke &stroke, bool onCanvas ) const
{
QPainterPath path( onCanvas ? stroke.points.at( 0 ) : canvasToItem( stroke.points.at( 0 ) ) );
for ( int i = 1; i < stroke.points.size(); i++ )
Expand All @@ -279,15 +287,38 @@ void DrawingCanvas::drawStroke( QPainter *painter, Stroke &stroke, bool onCanvas
painter->drawPath( path );
}

QPointF DrawingCanvas::itemToCanvas( const QPointF &point )
void DrawingCanvas::undo()
{
if ( !mStrokes.isEmpty() )
{
// Clear current stroke in the off chance an ongoing stroke had not yet ended
mCurrentStroke.points.clear();
mStrokes.removeLast();

// Reset image and redraw remaining strokes
mDrawingImage.fill( Qt::transparent );
for ( const Stroke &stroke : std::as_const( mStrokes ) )
{
QPainter painter( &mDrawingImage );
painter.setRenderHint( QPainter::Antialiasing, true );
drawStroke( &painter, stroke );
}

setIsDirty( !mStrokes.isEmpty() );

update();
}
}

QPointF DrawingCanvas::itemToCanvas( const QPointF &point ) const
{
const QPointF canvasTopLeft( size().width() / 2 - ( mBackgroundImage.size().width() * mZoomFactor / 2 ) + mOffset.x(),
size().height() / 2 - ( mBackgroundImage.size().height() * mZoomFactor / 2 ) + mOffset.y() );
return QPointF( ( point.x() - canvasTopLeft.x() ) / mZoomFactor,
( point.y() - canvasTopLeft.y() ) / mZoomFactor );
}

QPointF DrawingCanvas::canvasToItem( const QPointF &point )
QPointF DrawingCanvas::canvasToItem( const QPointF &point ) const
{
const QPointF canvasTopLeft( size().width() / 2 - ( mBackgroundImage.size().width() * mZoomFactor / 2 ) + mOffset.x(),
size().height() / 2 - ( mBackgroundImage.size().height() * mZoomFactor / 2 ) + mOffset.y() );
Expand Down
13 changes: 10 additions & 3 deletions src/core/drawingcanvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ class DrawingCanvas : public QQuickPaintedItem
*/
Q_INVOKABLE void clear();

/**
* Undos the last drawing operation.
* \see isDirty()
*/
Q_INVOKABLE void undo();

/**
* Saves the drawing canvas to a temporary location.
* \returns the temporary file path of the saved image.
Expand Down Expand Up @@ -168,10 +174,10 @@ class DrawingCanvas : public QQuickPaintedItem
QList<QPointF> points;
};

void drawStroke( QPainter *painter, Stroke &stroke, bool onCanvas = true );
void drawStroke( QPainter *painter, const Stroke &stroke, bool onCanvas = true ) const;

QPointF itemToCanvas( const QPointF &point );
QPointF canvasToItem( const QPointF &point );
QPointF itemToCanvas( const QPointF &point ) const;
QPointF canvasToItem( const QPointF &point ) const;

bool mIsEmpty = true;
bool mIsDirty = false;
Expand All @@ -183,6 +189,7 @@ class DrawingCanvas : public QQuickPaintedItem
QImage mBackgroundImage;
QImage mDrawingImage;

QList<Stroke> mStrokes;
Stroke mCurrentStroke;
};

Expand Down
29 changes: 24 additions & 5 deletions src/qml/QFieldSketcher.qml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Popup {

DragHandler {
id: stylusDragHandler
enabled: sketcher.visible
enabled: sketcher.visible && !drawingCanvas.isEmpty
target: null
acceptedButtons: Qt.NoButton | Qt.LeftButton | Qt.RightButton
acceptedDevices: PointerDevice.Stylus | PointerDevice.Mouse
Expand Down Expand Up @@ -82,7 +82,7 @@ Popup {

DragHandler {
id: dragHandler
enabled: sketcher.visible
enabled: sketcher.visible && !drawingCanvas.isEmpty
target: null
acceptedButtons: Qt.NoButton | Qt.LeftButton
acceptedDevices: PointerDevice.TouchScreen
Expand All @@ -107,7 +107,7 @@ Popup {

PinchHandler {
id: pinchHandler
enabled: sketcher.visible
enabled: sketcher.visible && !drawingCanvas.isEmpty
acceptedButtons: Qt.NoButton | Qt.LeftButton
acceptedDevices: PointerDevice.TouchScreen
dragThreshold: 2
Expand All @@ -129,7 +129,7 @@ Popup {

WheelHandler {
id: wheelHandler
enabled: sketcher.visible
enabled: sketcher.visible && !drawingCanvas.isEmpty
target: null
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByItems

Expand Down Expand Up @@ -281,6 +281,25 @@ Popup {
}
}

QfToolButton {
id: undoButton
visible: drawingCanvas.isDirty

anchors.right: saveButton.left
anchors.rightMargin: 5
anchors.top: parent.top
anchors.topMargin: mainWindow.sceneTopMargin + 5

iconSource: Theme.getThemeVectorIcon( "ic_undo_black_24dp" )
iconColor: "white"
bgcolor: Theme.darkGraySemiOpaque
round: true

onClicked: {
drawingCanvas.undo()
}
}

QfToolButton {
id: saveButton
visible: !drawingCanvas.isEmpty
Expand All @@ -290,7 +309,7 @@ Popup {
anchors.top: parent.top
anchors.topMargin: mainWindow.sceneTopMargin + 5

iconSource: Theme.getThemeIcon( 'ic_check_white_48dp' )
iconSource: Theme.getThemeIcon( "ic_check_white_48dp" )
iconColor: "white"
bgcolor: drawingCanvas.isDirty ? Theme.mainColor : Theme.darkGraySemiOpaque
round: true
Expand Down

1 comment on commit c5672da

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.