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
tests/MockShapes.cpp
KisLockableCanvas.cpp
KoShapeUtils.cpp
)
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 @@
#include <KisStrokesFacade.h>
#include <KisRunnableStrokeJobData.h>
#include <KoShapeManager.h>
#include <KoShapeUtils.h>
#include <QQueue>
#include <kis_shape_layer.h>
#include <KoSelection.h>
struct KisNodeReplaceBasedStrokeStrategy::Private
{
Private(KisResourcesSnapshotSP resources);
Private(KisCanvas2 *canvas);
Private(const Private &rhs) = default;
~Private() = default;
static QList<KoShape *> findMappedShapes(KisNodeSP oldNode, KisNodeSP newNode, QList<KoShape *> shapesToMap);
KisNodeSP affectedNode;
KisNodeSP originalState;
KisPostExecutionUndoAdapter *postExecUndoAdapter;
KoShapeManager *shapeManager;
QList<KoShape *> oldSelection;
bool nodeChanged = false;
class NodeReplaceCommand : public KUndo2Command
{
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;
void undo() override;
......@@ -50,41 +57,93 @@ struct KisNodeReplaceBasedStrokeStrategy::Private
KisNodeSP m_affectedNode;
KisNodeSP m_originalState;
KisNodeSP m_editedState;
KoShapeManager *m_shapeManager;
QList<KoShape *> m_oldSelection;
QList<KoShape *> m_newSelection;
};
};
KisNodeReplaceBasedStrokeStrategy::Private::Private(KisResourcesSnapshotSP resources)
: affectedNode(resources->currentNode())
, originalState(affectedNode->clone())
, postExecUndoAdapter(resources->postExecutionUndoAdapter())
KisNodeReplaceBasedStrokeStrategy::Private::Private(KisCanvas2 *canvas)
{
KisResourcesSnapshotSP resources = new KisResourcesSnapshot(canvas->image().toStrongRef(), canvas->imageView()->currentNode(), canvas->resourceManager());
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)
, m_affectedNode(affectedNode)
, m_originalState(originalState)
, m_editedState(affectedNode->clone())
, m_shapeManager(shapeManager)
, m_oldSelection(oldSelection)
{
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()
{
KoSelection *selection = m_shapeManager->selection();
selection->deselectAll();
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()
{
KoSelection *selection = m_shapeManager->selection();
selection->deselectAll();
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,
const KUndo2MagicString &name)
: KisRunnableBasedStrokeStrategy(id, name)
, m_d(new Private(new KisResourcesSnapshot(canvas->image().toStrongRef(),
canvas->imageView()->currentNode(),
canvas->resourceManager())))
, m_d(new Private(canvas))
{
enableJob(KisSimpleStrokeStrategy::JOB_INIT);
enableJob(KisSimpleStrokeStrategy::JOB_FINISH, /* enable = */ true,
......@@ -92,7 +151,10 @@ KisNodeReplaceBasedStrokeStrategy::KisNodeReplaceBasedStrokeStrategy(QString id,
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE, /* enable = */ true,
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)
......@@ -123,7 +185,7 @@ void KisNodeReplaceBasedStrokeStrategy::cancelStrokeCallback()
void KisNodeReplaceBasedStrokeStrategy::finishStrokeCallback()
{
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);
}
......
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