[krita] /: Implemented moving multiple selected layers at once
Dmitry Kazakov
dimula73 at gmail.com
Tue Apr 5 11:51:32 UTC 2016
Git commit 28d985bcb91b5ca2e4cd410e7f01db14d30ec748 by Dmitry Kazakov.
Committed on 05/04/2016 at 11:48.
Pushed by dkazakov into branch 'master'.
Implemented moving multiple selected layers at once
Please test if it breaks anything in the Move tool!
(it shouldn't though :) )
Ref T2082
BUG:359559
CC:kimageshop at kde.org
M +25 -2 libs/image/kis_layer_utils.cpp
M +4 -2 libs/image/kis_layer_utils.h
M +11 -0 libs/image/krita_utils.h
M +8 -0 libs/ui/tool/kis_tool.cc
M +1 -0 libs/ui/tool/kis_tool.h
M +65 -85 plugins/tools/basictools/kis_tool_move.cc
M +3 -1 plugins/tools/basictools/kis_tool_move.h
M +31 -28 plugins/tools/basictools/strokes/move_stroke_strategy.cpp
M +3 -12 plugins/tools/basictools/strokes/move_stroke_strategy.h
M +1 -1 plugins/tools/basictools/tests/move_stroke_test.cpp
http://commits.kde.org/krita/28d985bcb91b5ca2e4cd410e7f01db14d30ec748
diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp
index 46f11f0..7fd6f02 100644
--- a/libs/image/kis_layer_utils.cpp
+++ b/libs/image/kis_layer_utils.cpp
@@ -377,7 +377,7 @@ namespace KisLayerUtils {
}
}
- bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const QList<KisNodeSP> &nodes) {
+ bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) {
foreach (KisNodeSP node, nodes) {
if (node == src) continue;
@@ -696,7 +696,7 @@ namespace KisLayerUtils {
return false;
}
- void filterMergableNodes(QList<KisNodeSP> &nodes, bool allowMasks)
+ void filterMergableNodes(KisNodeList &nodes, bool allowMasks)
{
QList<KisNodeSP>::iterator it = nodes.begin();
@@ -744,6 +744,29 @@ namespace KisLayerUtils {
return result;
}
+ KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks)
+ {
+ KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; }
+
+ KisNodeSP root;
+ Q_FOREACH(KisNodeSP node, nodes) {
+ KisNodeSP localRoot = node;
+ while (localRoot->parent()) {
+ localRoot = localRoot->parent();
+ }
+
+ if (!root) {
+ root = localRoot;
+ }
+ KIS_ASSERT_RECOVER(root == localRoot) { return nodes; }
+ }
+
+ KisNodeList result;
+ sortMergableNodes(root, nodes, result);
+ filterMergableNodes(result, allowMasks);
+ return result;
+ }
+
void addCopyOfNameTag(KisNodeSP node)
{
const QString prefix = i18n("Copy of");
diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h
index 47a1871..e733953 100644
--- a/libs/image/kis_layer_utils.h
+++ b/libs/image/kis_layer_utils.h
@@ -37,9 +37,11 @@ namespace KisLayerUtils
{
KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList<KisNodeSP> &inputNodes, QList<KisNodeSP> &outputNodes);
KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes);
- KRITAIMAGE_EXPORT void filterMergableNodes(QList<KisNodeSP> &nodes, bool allowMasks = false);
+ KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const QList<KisNodeSP> &parents);
+ KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
+
KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
KRITAIMAGE_EXPORT QSet<int> fetchLayerFrames(KisNodeSP node);
@@ -116,7 +118,7 @@ namespace KisLayerUtils
virtual void addCommandImpl(KUndo2Command *cmd) = 0;
void safeRemoveMultipleNodes(QList<KisNodeSP> nodes, KisImageSP image);
private:
- bool checkIsSourceForClone(KisNodeSP src, const QList<KisNodeSP> &nodes);
+ bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes);
static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove);
};
diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h
index 00bb08a..a1f19b3 100644
--- a/libs/image/krita_utils.h
+++ b/libs/image/krita_utils.h
@@ -31,6 +31,8 @@ class QPainter;
#include <QVector>
#include "kritaimage_export.h"
#include "kis_types.h"
+#include <functional>
+
namespace KritaUtils
{
@@ -93,6 +95,15 @@ namespace KritaUtils
}
}
+ template <class C>
+ void filterContainer(C &container, std::function<bool(typename C::reference)> keepIf) {
+
+ auto newEnd = std::remove_if(container.begin(), container.end(), std::unary_negate<decltype(keepIf)>(keepIf));
+ while (newEnd != container.end()) {
+ newEnd = container.erase(newEnd);
+ }
+ }
+
/**
* When drawing a rect Qt uses quite a weird algorithm. It
* draws 4 lines:
diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc
index 4719f22..c95ea01 100644
--- a/libs/ui/tool/kis_tool.cc
+++ b/libs/ui/tool/kis_tool.cc
@@ -41,6 +41,7 @@
#include <KoSnapGuide.h>
#include <KisViewManager.h>
+#include "kis_node_manager.h"
#include <kis_selection.h>
#include <kis_image.h>
#include <kis_group_layer.h>
@@ -412,6 +413,13 @@ KisNodeSP KisTool::currentNode()
return node;
}
+KisNodeList KisTool::selectedNodes() const
+{
+ KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
+ KisViewManager* viewManager = kiscanvas->viewManager();
+ return viewManager->nodeManager()->selectedNodes();
+}
+
KoColor KisTool::currentFgColor()
{
return d->currentFgColor;
diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h
index a65665b..2e66071 100644
--- a/libs/ui/tool/kis_tool.h
+++ b/libs/ui/tool/kis_tool.h
@@ -286,6 +286,7 @@ protected:
KoPattern* currentPattern();
KoAbstractGradient *currentGradient();
KisNodeSP currentNode();
+ KisNodeList selectedNodes() const;
KoColor currentFgColor();
KoColor currentBgColor();
KisPaintOpPresetSP currentPaintOpPreset();
diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc
index 638a9df..c1c2a8b 100644
--- a/plugins/tools/basictools/kis_tool_move.cc
+++ b/plugins/tools/basictools/kis_tool_move.cc
@@ -37,6 +37,8 @@
#include "strokes/move_selection_stroke_strategy.h"
#include "kis_resources_snapshot.h"
#include "kis_action_registry.h"
+#include "krita_utils.h"
+
KisToolMove::KisToolMove(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::moveCursor())
@@ -93,57 +95,83 @@ void KisToolMove::resetCursorStyle()
overrideCursorIfNotEditable();
}
-void KisToolMove::moveDiscrete(MoveDirection direction, bool big)
+bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos)
{
- if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
-
- setMode(KisTool::PAINT_MODE);
-
KisNodeSP node;
+ KisNodeList nodes;
KisImageSP image = this->image();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image, currentNode(), 0, this->canvas()->resourceManager());
KisSelectionSP selection = resources->activeSelection();
- // Move current layer by default.
- if ((!node && !(node = resources->currentNode())) || !node->isEditable()) {
- return;
+ if (mode != MoveSelectedLayer && pos) {
+ bool wholeGroup = !selection && mode == MoveGroup;
+ node = KisToolUtils::findNode(image->root(), *pos, wholeGroup);
+ nodes = {node};
}
+ if (nodes.isEmpty()) {
+ nodes = this->selectedNodes();
- // If node has changed, end current stroke.
- if (m_strokeId && (node != m_currentlyProcessingNode)) {
- endStroke();
+ KritaUtils::filterContainer(nodes,
+ [](KisNodeSP node) {
+ return node->isEditable();
+ });
}
+ if (nodes.size() == 1) {
+ node = nodes.first();
+ }
+
+ if (nodes.isEmpty()) {
+ return false;
+ }
/**
- * Begin a new stroke if necessary.
+ * If the target node has changed, the stroke should be
+ * restarted. Otherwise just continue processing current node.
*/
- if (!m_strokeId) {
- KisStrokeStrategy *strategy;
+ if (m_strokeId) {
+ if (KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) {
+ return true;
+ } else {
+ endStroke();
+ }
+ }
- KisPaintLayerSP paintLayer =
- dynamic_cast<KisPaintLayer*>(node.data());
+ KisStrokeStrategy *strategy;
- if (paintLayer && selection &&
- !selection->isTotallyUnselected(image->bounds())) {
+ KisPaintLayerSP paintLayer = node ?
+ dynamic_cast<KisPaintLayer*>(node.data()) : 0;
- strategy =
- new MoveSelectionStrokeStrategy(paintLayer,
- selection,
- image.data(),
- image->postExecutionUndoAdapter());
- } else {
- strategy =
- new MoveStrokeStrategy(node, image.data(),
- image->postExecutionUndoAdapter());
- }
+ if (paintLayer && selection &&
+ !selection->isTotallyUnselected(image->bounds())) {
- m_strokeId = image->startStroke(strategy);
- m_currentlyProcessingNode = node;
- m_accumulatedOffset = QPoint();
+ strategy =
+ new MoveSelectionStrokeStrategy(paintLayer,
+ selection,
+ image.data(),
+ image->postExecutionUndoAdapter());
+ } else {
+ strategy =
+ new MoveStrokeStrategy(nodes, image.data(),
+ image->postExecutionUndoAdapter());
+ }
+
+ m_strokeId = image->startStroke(strategy);
+ m_currentlyProcessingNodes = nodes;
+ m_accumulatedOffset = QPoint();
+
+ return true;
+}
+
+void KisToolMove::moveDiscrete(MoveDirection direction, bool big)
+{
+ if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
+
+ if (startStrokeImpl(MoveSelectedLayer, 0)) {
+ setMode(KisTool::PAINT_MODE);
}
// Larger movement if "shift" key is pressed.
@@ -155,7 +183,7 @@ void KisToolMove::moveDiscrete(MoveDirection direction, bool big)
direction == Left ? QPoint(-moveStep, 0) :
QPoint( moveStep, 0) ;
- image->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset));
+ image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset));
m_accumulatedOffset += offset;
@@ -252,59 +280,11 @@ void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode)
m_moveInProgress = true;
emit moveInProgressChanged();
- KisNodeSP node;
- KisImageSP image = this->image();
-
- KisResourcesSnapshotSP resources =
- new KisResourcesSnapshot(image, currentNode(), 0, this->canvas()->resourceManager());
- KisSelectionSP selection = resources->activeSelection();
-
- if (mode != MoveSelectedLayer) {
- bool wholeGroup = !selection && mode == MoveGroup;
- node = KisToolUtils::findNode(image->root(), pos, wholeGroup);
- }
-
- if ((!node && !(node = resources->currentNode())) || !node->isEditable()) {
- event->ignore();
- return;
- }
-
- setMode(KisTool::PAINT_MODE);
-
- /**
- * If the target node has changed, the stroke should be
- * restarted. Otherwise just continue processing current node.
- */
- if (m_strokeId) {
- if (node == m_currentlyProcessingNode) {
- return;
- } else {
- endStroke();
- }
- }
-
- KisStrokeStrategy *strategy;
-
- KisPaintLayerSP paintLayer =
- dynamic_cast<KisPaintLayer*>(node.data());
-
- if (paintLayer && selection &&
- !selection->isTotallyUnselected(image->bounds())) {
-
- strategy =
- new MoveSelectionStrokeStrategy(paintLayer,
- selection,
- image.data(),
- image->postExecutionUndoAdapter());
+ if (startStrokeImpl(mode, &pos)) {
+ setMode(KisTool::PAINT_MODE);
} else {
- strategy =
- new MoveStrokeStrategy(node, image.data(),
- image->postExecutionUndoAdapter());
+ event->ignore();
}
-
- m_strokeId = image->startStroke(strategy);
- m_currentlyProcessingNode = node;
- m_accumulatedOffset = QPoint();
}
void KisToolMove::continueAction(KoPointerEvent *event)
@@ -348,7 +328,7 @@ void KisToolMove::endStroke()
KisImageWSP image = currentImage();
image->endStroke(m_strokeId);
m_strokeId.clear();
- m_currentlyProcessingNode.clear();
+ m_currentlyProcessingNodes.clear();
m_moveInProgress = false;
emit moveInProgressChanged();
}
@@ -360,7 +340,7 @@ void KisToolMove::cancelStroke()
KisImageWSP image = currentImage();
image->cancelStroke(m_strokeId);
m_strokeId.clear();
- m_currentlyProcessingNode.clear();
+ m_currentlyProcessingNodes.clear();
m_moveInProgress = false;
emit moveInProgressChanged();
}
diff --git a/plugins/tools/basictools/kis_tool_move.h b/plugins/tools/basictools/kis_tool_move.h
index 87973c0..35e1338 100644
--- a/plugins/tools/basictools/kis_tool_move.h
+++ b/plugins/tools/basictools/kis_tool_move.h
@@ -100,6 +100,8 @@ private:
void cancelStroke();
QPoint applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos);
+ bool startStrokeImpl(MoveToolMode mode, const QPoint *pos);
+
private Q_SLOTS:
void endStroke();
@@ -113,7 +115,7 @@ private:
KisStrokeId m_strokeId;
bool m_moveInProgress;
- KisNodeSP m_currentlyProcessingNode;
+ KisNodeList m_currentlyProcessingNodes;
int m_resolution;
};
diff --git a/plugins/tools/basictools/strokes/move_stroke_strategy.cpp b/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
index c61c73e..8eedf3e 100644
--- a/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
+++ b/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
@@ -23,13 +23,14 @@
#include "kis_node.h"
#include "commands_new/kis_update_command.h"
#include "commands_new/kis_node_move_command2.h"
+#include "kis_layer_utils.h"
-MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSP node,
+MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes,
KisUpdatesFacade *updatesFacade,
KisPostExecutionUndoAdapter *undoAdapter)
: KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoAdapter),
- m_node(node),
+ m_nodes(KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, true)),
m_updatesFacade(updatesFacade),
m_undoEnabled(true),
m_updatesEnabled(true)
@@ -39,21 +40,16 @@ MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSP node,
MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs, bool suppressUndo)
: KisStrokeStrategyUndoCommandBased(rhs, suppressUndo),
- m_node(rhs.m_node),
+ m_nodes(rhs.m_nodes),
m_updatesFacade(rhs.m_updatesFacade),
m_finalOffset(rhs.m_finalOffset),
m_dirtyRect(rhs.m_dirtyRect),
+ m_dirtyRects(rhs.m_dirtyRects),
m_undoEnabled(rhs.m_undoEnabled),
m_updatesEnabled(rhs.m_updatesEnabled)
{
}
-void MoveStrokeStrategy::setNode(KisNodeSP node)
-{
- Q_ASSERT(!m_node);
- m_node = node;
-}
-
void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node)
{
m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y()));
@@ -67,8 +63,8 @@ void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node)
void MoveStrokeStrategy::initStrokeCallback()
{
- if (m_node) {
- saveInitialNodeOffsets(m_node);
+ Q_FOREACH(KisNodeSP node, m_nodes) {
+ saveInitialNodeOffsets(node);
}
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
@@ -76,19 +72,23 @@ void MoveStrokeStrategy::initStrokeCallback()
void MoveStrokeStrategy::finishStrokeCallback()
{
- if(m_node && m_undoEnabled) {
- KUndo2Command *updateCommand =
- new KisUpdateCommand(m_node, m_dirtyRect, m_updatesFacade, true);
+ if (m_undoEnabled) {
+ Q_FOREACH (KisNodeSP node, m_nodes) {
+ KUndo2Command *updateCommand =
+ new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
- addMoveCommands(m_node, updateCommand);
+ addMoveCommands(node, updateCommand);
- notifyCommandDone(KUndo2CommandSP(updateCommand),
- KisStrokeJobData::SEQUENTIAL,
- KisStrokeJobData::EXCLUSIVE);
+ notifyCommandDone(KUndo2CommandSP(updateCommand),
+ KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::EXCLUSIVE);
+ }
}
- if (m_node && !m_updatesEnabled) {
- m_updatesFacade->refreshGraphAsync(m_node, m_dirtyRect);
+ if (!m_updatesEnabled) {
+ Q_FOREACH (KisNodeSP node, m_nodes) {
+ m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]);
+ }
}
KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
@@ -96,8 +96,7 @@ void MoveStrokeStrategy::finishStrokeCallback()
void MoveStrokeStrategy::cancelStrokeCallback()
{
- if(m_node) {
-
+ if (!m_nodes.isEmpty()) {
// FIXME: make cancel job exclusive instead
m_updatesFacade->blockUpdates();
moveAndUpdate(QPoint());
@@ -111,7 +110,7 @@ void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast<Data*>(data);
- if(m_node && d) {
+ if(!m_nodes.isEmpty() && d) {
moveAndUpdate(d->offset);
/**
@@ -127,11 +126,13 @@ void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
void MoveStrokeStrategy::moveAndUpdate(QPoint offset)
{
- QRect dirtyRect = moveNode(m_node, offset);
- m_dirtyRect |= dirtyRect;
+ Q_FOREACH (KisNodeSP node, m_nodes) {
+ QRect dirtyRect = moveNode(node, offset);
+ m_dirtyRects[node] |= dirtyRect;
- if (m_updatesEnabled) {
- m_updatesFacade->refreshGraphAsync(m_node, dirtyRect);
+ if (m_updatesEnabled) {
+ m_updatesFacade->refreshGraphAsync(node, dirtyRect);
+ }
}
}
@@ -203,7 +204,9 @@ bool checkSupportsLodMoves(KisNodeSP node)
KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail)
{
- if (!checkSupportsLodMoves(m_node)) return 0;
+ Q_FOREACH (KisNodeSP node, m_nodes) {
+ if (!checkSupportsLodMoves(node)) return 0;
+ }
MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail > 0);
clone->setUndoEnabled(false);
diff --git a/plugins/tools/basictools/strokes/move_stroke_strategy.h b/plugins/tools/basictools/strokes/move_stroke_strategy.h
index 9eec619..bc1fb4f 100644
--- a/plugins/tools/basictools/strokes/move_stroke_strategy.h
+++ b/plugins/tools/basictools/strokes/move_stroke_strategy.h
@@ -57,19 +57,9 @@ public:
};
public:
- MoveStrokeStrategy(KisNodeSP node, KisUpdatesFacade *updatesFacade,
+ MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade,
KisPostExecutionUndoAdapter *undoAdapter);
- /**
- * You can use deferred initialization of the node pointer
- * To use it you need to pass 0 to the constructor, and
- * set the node with setNode layer.
- * NOTE: once set, you cannot change the node anymore,
- * you'll get an assert
- */
-
- void setNode(KisNodeSP node);
-
void initStrokeCallback();
void finishStrokeCallback();
void cancelStrokeCallback();
@@ -88,10 +78,11 @@ private:
void saveInitialNodeOffsets(KisNodeSP node);
private:
- KisNodeSP m_node;
+ KisNodeList m_nodes;
KisUpdatesFacade *m_updatesFacade;
QPoint m_finalOffset;
QRect m_dirtyRect;
+ QHash<KisNodeSP, QRect> m_dirtyRects;
bool m_undoEnabled;
bool m_updatesEnabled;
QHash<KisNodeSP, QPoint> m_initialNodeOffsets;
diff --git a/plugins/tools/basictools/tests/move_stroke_test.cpp b/plugins/tools/basictools/tests/move_stroke_test.cpp
index 54cc19e..754ed05 100644
--- a/plugins/tools/basictools/tests/move_stroke_test.cpp
+++ b/plugins/tools/basictools/tests/move_stroke_test.cpp
@@ -51,7 +51,7 @@ protected:
Q_UNUSED(indirectPainting);
KisNodeSP node = resources->currentNode();
- return new MoveStrokeStrategy(node, image.data(), resources->postExecutionUndoAdapter());
+ return new MoveStrokeStrategy({node}, image.data(), resources->postExecutionUndoAdapter());
}
using utils::StrokeTester::addPaintingJobs;
More information about the kimageshop
mailing list