Commit 6c60682a authored by Tusooa Zhu's avatar Tusooa Zhu 🅱

Keep selection before and after the action when undo/redo

parent 5afd7216
...@@ -215,6 +215,7 @@ set(kritaflake_SRCS ...@@ -215,6 +215,7 @@ set(kritaflake_SRCS
tests/MockShapes.cpp tests/MockShapes.cpp
KisLockableCanvas.cpp KisLockableCanvas.cpp
KoShapeUtils.cpp
) )
ki18n_wrap_ui(kritaflake_SRCS ki18n_wrap_ui(kritaflake_SRCS
......
/* This file is part of the KDE project
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoShapeUtils.h"
#include <KoShapeContainer.h>
void KoShapeUtils::recursiveApplyShapes(KoShape *topLevelShape, std::function<void (KoShape *)> func)
{
if (topLevelShape) {
func(topLevelShape);
if (KoShapeContainer *container = dynamic_cast<KoShapeContainer *>(topLevelShape)) {
Q_FOREACH (KoShape *shape, container->shapes()) {
recursiveApplyShapes(shape, func);
}
}
}
}
/* This file is part of the KDE project
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KO_SHAPE_UTILS_H_
#define KO_SHAPE_UTILS_H_
#include <kritaflake_export.h>
#include <functional>
class KoShape;
namespace KoShapeUtils
{
/**
* @brief recursiveApplyShapes applies the function to topLevelShape, and then
* to its children, if there are any.
* @param topLevelShape the shape to apply the function on
* @param func a function that accepts a pointer to KoShape
*/
void KRITAFLAKE_EXPORT recursiveApplyShapes(KoShape *topLevelShape, std::function<void (KoShape *)> func);
}
#endif
...@@ -25,23 +25,30 @@ ...@@ -25,23 +25,30 @@
#include <KisStrokesFacade.h> #include <KisStrokesFacade.h>
#include <KisRunnableStrokeJobData.h> #include <KisRunnableStrokeJobData.h>
#include <KoShapeManager.h> #include <KoShapeManager.h>
#include <KoShapeUtils.h>
#include <QQueue>
#include <kis_shape_layer.h>
#include <KoSelection.h>
struct KisNodeReplaceBasedStrokeStrategy::Private struct KisNodeReplaceBasedStrokeStrategy::Private
{ {
Private(KisResourcesSnapshotSP resources); Private(KisCanvas2 *canvas);
Private(const Private &rhs) = default; Private(const Private &rhs) = default;
~Private() = default; ~Private() = default;
static QList<KoShape *> findMappedShapes(KisNodeSP oldNode, KisNodeSP newNode, QList<KoShape *> shapesToMap);
KisNodeSP affectedNode; KisNodeSP affectedNode;
KisNodeSP originalState; KisNodeSP originalState;
KisPostExecutionUndoAdapter *postExecUndoAdapter; KisPostExecutionUndoAdapter *postExecUndoAdapter;
KoShapeManager *shapeManager; KoShapeManager *shapeManager;
QList<KoShape *> oldSelection;
bool nodeChanged = false; bool nodeChanged = false;
class NodeReplaceCommand : public KUndo2Command class NodeReplaceCommand : public KUndo2Command
{ {
public: public:
NodeReplaceCommand(KisNodeSP affectedNode, KisNodeSP originalState, const KUndo2MagicString &name); NodeReplaceCommand(KisNodeSP affectedNode, KisNodeSP originalState, QList<KoShape *> oldSelection, KoShapeManager *shapeManager, const KUndo2MagicString &name);
~NodeReplaceCommand() override = default; ~NodeReplaceCommand() override = default;
void undo() override; void undo() override;
...@@ -50,41 +57,93 @@ struct KisNodeReplaceBasedStrokeStrategy::Private ...@@ -50,41 +57,93 @@ struct KisNodeReplaceBasedStrokeStrategy::Private
KisNodeSP m_affectedNode; KisNodeSP m_affectedNode;
KisNodeSP m_originalState; KisNodeSP m_originalState;
KisNodeSP m_editedState; KisNodeSP m_editedState;
KoShapeManager *m_shapeManager;
QList<KoShape *> m_oldSelection;
QList<KoShape *> m_newSelection;
}; };
}; };
KisNodeReplaceBasedStrokeStrategy::Private::Private(KisResourcesSnapshotSP resources) KisNodeReplaceBasedStrokeStrategy::Private::Private(KisCanvas2 *canvas)
: affectedNode(resources->currentNode()) {
, originalState(affectedNode->clone()) KisResourcesSnapshotSP resources = new KisResourcesSnapshot(canvas->image().toStrongRef(), canvas->imageView()->currentNode(), canvas->resourceManager());
, postExecUndoAdapter(resources->postExecutionUndoAdapter()) affectedNode = resources->currentNode();
originalState = affectedNode->clone();
postExecUndoAdapter = resources->postExecutionUndoAdapter();
shapeManager = canvas->shapeManager();
}
QList<KoShape *> KisNodeReplaceBasedStrokeStrategy::Private::findMappedShapes(KisNodeSP oldNode, KisNodeSP newNode, QList<KoShape *> shapesToMap)
{ {
KisShapeLayerSP oldLayer(dynamic_cast<KisShapeLayer *>(oldNode.data()));
KisShapeLayerSP newLayer(dynamic_cast<KisShapeLayer *>(newNode.data()));
if (!oldLayer || !newLayer) {
return QList<KoShape *>();
}
QQueue<KoShape *> linearizedShapes;
QList<KoShape *> newShapes;
Q_FOREACH (const KoShape *oldShape, shapesToMap) {
Q_UNUSED(oldShape);
newShapes << 0;
}
KoShapeUtils::recursiveApplyShapes(oldLayer.data(),
[&linearizedShapes](KoShape *shape) {
linearizedShapes.enqueue(shape);
});
KoShapeUtils::recursiveApplyShapes(newLayer.data(),
[&linearizedShapes, shapesToMap, &newShapes](KoShape *shape) {
KoShape *oldShape = linearizedShapes.dequeue();
int index = shapesToMap.indexOf(oldShape);
if (index != -1) {
newShapes[index] = shape;
}
});
return newShapes;
} }
KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::NodeReplaceCommand(KisNodeSP affectedNode, KisNodeSP originalState, const KUndo2MagicString &name) KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::NodeReplaceCommand(KisNodeSP affectedNode, KisNodeSP originalState, QList<KoShape *> oldSelection, KoShapeManager *shapeManager, const KUndo2MagicString &name)
: KUndo2Command(name) : KUndo2Command(name)
, m_affectedNode(affectedNode) , m_affectedNode(affectedNode)
, m_originalState(originalState) , m_originalState(originalState)
, m_editedState(affectedNode->clone()) , m_editedState(affectedNode->clone())
, m_shapeManager(shapeManager)
, m_oldSelection(oldSelection)
{ {
KIS_SAFE_ASSERT_RECOVER_NOOP(m_editedState); KIS_SAFE_ASSERT_RECOVER_NOOP(m_editedState);
QScopedPointer<KisLockableCanvas::Locker> lock(shapeManager->obtainLock());
if (lock) { // then the current layer is a shape layer
m_newSelection = findMappedShapes(m_affectedNode, m_editedState, shapeManager->selection()->selectedShapes());
}
} }
void KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::undo() void KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::undo()
{ {
KoSelection *selection = m_shapeManager->selection();
selection->deselectAll();
m_affectedNode->copyFromNode(m_originalState.data()); m_affectedNode->copyFromNode(m_originalState.data());
QList<KoShape *> shapesToSelect = findMappedShapes(m_originalState, m_affectedNode, m_oldSelection);
Q_FOREACH (KoShape *shape, shapesToSelect) {
selection->select(shape);
}
} }
void KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::redo() void KisNodeReplaceBasedStrokeStrategy::Private::NodeReplaceCommand::redo()
{ {
KoSelection *selection = m_shapeManager->selection();
selection->deselectAll();
m_affectedNode->copyFromNode(m_editedState.data()); m_affectedNode->copyFromNode(m_editedState.data());
QList<KoShape *> shapesToSelect = findMappedShapes(m_editedState, m_affectedNode, m_newSelection);
Q_FOREACH (KoShape *shape, shapesToSelect) {
selection->select(shape);
}
} }
KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(QString id, KisCanvas2 *canvas, KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(QString id, KisCanvas2 *canvas,
const KUndo2MagicString &name) const KUndo2MagicString &name)
: KisRunnableBasedStrokeStrategy(id, name) : KisRunnableBasedStrokeStrategy(id, name)
, m_d(new Private(new KisResourcesSnapshot(canvas->image().toStrongRef(), , m_d(new Private(canvas))
canvas->imageView()->currentNode(),
canvas->resourceManager())))
{ {
enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_INIT);
enableJob(KisSimpleStrokeStrategy::JOB_FINISH, /* enable = */ true, enableJob(KisSimpleStrokeStrategy::JOB_FINISH, /* enable = */ true,
...@@ -92,7 +151,10 @@ KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(QString id, ...@@ -92,7 +151,10 @@ KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(QString id,
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE, /* enable = */ true, enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE, /* enable = */ true,
KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
m_d->shapeManager = canvas->shapeManager(); QScopedPointer<KisLockableCanvas::Locker> lock(m_d->shapeManager->obtainLock());
if (lock) { // then the current layer is a shape layer
m_d->oldSelection = m_d->findMappedShapes(m_d->affectedNode, m_d->originalState, m_d->shapeManager->selection()->selectedShapes());
}
} }
KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(const KisNodeReplaceBasedStrokeStrategy &rhs) KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(const KisNodeReplaceBasedStrokeStrategy &rhs)
...@@ -123,7 +185,7 @@ void KisNodeReplaceBasedStrokeStrategy::cancelStrokeCallback() ...@@ -123,7 +185,7 @@ void KisNodeReplaceBasedStrokeStrategy::cancelStrokeCallback()
void KisNodeReplaceBasedStrokeStrategy::finishStrokeCallback() void KisNodeReplaceBasedStrokeStrategy::finishStrokeCallback()
{ {
if (m_d->nodeChanged) { if (m_d->nodeChanged) {
KUndo2CommandSP cmd(new Private::NodeReplaceCommand(m_d->affectedNode, m_d->originalState, name())); KUndo2CommandSP cmd(new Private::NodeReplaceCommand(m_d->affectedNode, m_d->originalState, m_d->oldSelection, m_d->shapeManager, name()));
m_d->postExecUndoAdapter->addCommand(cmd); m_d->postExecUndoAdapter->addCommand(cmd);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment