File indexing completed on 2024-05-12 16:01:35

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_node_juggler_compressed.h"
0008 
0009 #include <QHash>
0010 #include <QSharedPointer>
0011 #include <QPointer>
0012 
0013 #include "kis_global.h"
0014 #include "kis_image.h"
0015 #include "kis_processing_applicator.h"
0016 #include "commands/kis_image_layer_move_command.h"
0017 #include "commands/kis_image_layer_add_command.h"
0018 #include "kis_signal_compressor.h"
0019 #include "kis_command_utils.h"
0020 #include "kis_layer_utils.h"
0021 #include "kis_node_manager.h"
0022 #include "kis_layer.h"
0023 #include "kis_generator_layer.h"
0024 #include "kis_selection_mask.h"
0025 
0026 
0027 /**
0028  * A special structure that stores information about a node that was
0029  * moved. The purpose of the object is twofold:
0030  *
0031  *     1) When the reordering stroke is already started than the
0032  *        parent and sibling nodes may be not consistent anymore. So
0033  *        we store it separately.
0034  *
0035  *     2) This objects allows merging (compressing) multiple moves of
0036  *        a layer into a single action. This behavior is implemented
0037  *        in tryMerge() method.
0038  */
0039 struct MoveNodeStruct {
0040     MoveNodeStruct(KisImageSP _image, KisNodeSP _node, KisNodeSP _parent, KisNodeSP _above)
0041         : image(_image),
0042           node(_node),
0043           newParent(_parent),
0044           newAbove(_above),
0045           oldParent(_node->parent()),
0046           oldAbove(_node->prevSibling()),
0047           suppressNewParentRefresh(false),
0048           suppressOldParentRefresh(false)
0049     {
0050     }
0051 
0052     bool tryMerge(const MoveNodeStruct &rhs) {
0053         if (rhs.node != node) return false;
0054 
0055         bool result = true;
0056 
0057         if (newParent == rhs.oldParent) {
0058             // 'rhs' is newer
0059             newParent = rhs.newParent;
0060             newAbove = rhs.newAbove;
0061         } else if (oldParent == rhs.newParent) {
0062             // 'this' is newer
0063             oldParent = rhs.oldParent;
0064             oldAbove = rhs.oldAbove;
0065         } else {
0066             warnKrita << "MoveNodeStruct: Trying to merge unsequential moves!";
0067             result = false;
0068         }
0069 
0070         return result;
0071     }
0072 
0073     void doRedoUpdates() {
0074         if (oldParent && !suppressOldParentRefresh) {
0075             image->refreshGraphAsync(oldParent, KisImage::NoFilthyUpdate);
0076         }
0077 
0078         if (newParent && oldParent != newParent) {
0079             node->setDirty(image->bounds());
0080         }
0081     }
0082 
0083     void doUndoUpdates() {
0084         if (newParent && !suppressNewParentRefresh) {
0085             image->refreshGraphAsync(newParent, KisImage::NoFilthyUpdate);
0086         }
0087 
0088         if (oldParent && oldParent != newParent) {
0089             node->setDirty(image->bounds());
0090         }
0091     }
0092 
0093     KisImageSP image;
0094     KisNodeSP node;
0095     KisNodeSP newParent;
0096     KisNodeSP newAbove;
0097 
0098     KisNodeSP oldParent;
0099     KisNodeSP oldAbove;
0100     bool suppressNewParentRefresh;
0101     bool suppressOldParentRefresh;
0102 };
0103 
0104 typedef QSharedPointer<MoveNodeStruct> MoveNodeStructSP;
0105 typedef QHash<KisNodeSP, MoveNodeStructSP> MovedNodesHash;
0106 
0107 
0108 /**
0109  * All the commands executed bythe stroke system are running in the
0110  * background asynchronously. But, at the same time, they emit updates
0111  * in parallel to the ones emitted by the juggler. Therefore, the
0112  * juggler and all its commands should share some data: which updates
0113  * have been requested, but not yet dispatched (m_movedNodesInitial),
0114  * and what updates have already been processed and executed
0115  * (m_movedNodesUpdated). This object is shared via a shared pointer
0116  * and guarantees safe (including thread-safe) access to the shared
0117  * data.
0118  */
0119 class BatchMoveUpdateData {
0120     MovedNodesHash m_movedNodesInitial;
0121     MovedNodesHash m_movedNodesUpdated;
0122 
0123     QMutex m_mutex;
0124 
0125     QPointer<KisNodeJugglerCompressed> m_parentJuggler;
0126 
0127 public:
0128     BatchMoveUpdateData(KisNodeJugglerCompressed *parentJuggler)
0129         : m_parentJuggler(parentJuggler) {}
0130 
0131 private:
0132 
0133     struct NewParentCollistionPolicy {
0134         static void setSuppressRefresh(MoveNodeStructSP update, bool value) {
0135             update->suppressNewParentRefresh = value;
0136         }
0137         static bool compare(MoveNodeStructSP lhs, MoveNodeStructSP rhs) {
0138             return lhs->newParent < rhs->newParent;
0139         }
0140     };
0141 
0142     struct OldParentCollistionPolicy {
0143         static void setSuppressRefresh(MoveNodeStructSP update, bool value) {
0144             update->suppressOldParentRefresh = value;
0145         }
0146         static bool compare(MoveNodeStructSP lhs, MoveNodeStructSP rhs) {
0147             return lhs->oldParent < rhs->oldParent;
0148         }
0149     };
0150 
0151     template <typename CollisionPolicy>
0152     static void resolveParentCollisionsImpl(MovedNodesHash *hash) {
0153         QVector<MoveNodeStructSP> updates;
0154         std::copy(hash->begin(), hash->end(), std::back_inserter(updates));
0155         std::sort(updates.begin(), updates.end(), CollisionPolicy::compare);
0156 
0157         auto rangeBegin = updates.begin();
0158 
0159         while (rangeBegin != updates.end()) {
0160             auto rangeEnd = std::upper_bound(rangeBegin, updates.end(), *rangeBegin, CollisionPolicy::compare);
0161 
0162             for (auto it = rangeBegin; it != std::prev(rangeEnd); ++it) {
0163                 CollisionPolicy::setSuppressRefresh(*it, true);
0164             }
0165             CollisionPolicy::setSuppressRefresh(*std::prev(rangeEnd), false);
0166 
0167             rangeBegin = rangeEnd;
0168         }
0169     }
0170 
0171     static void resolveParentCollisions(MovedNodesHash *hash) {
0172         resolveParentCollisionsImpl<NewParentCollistionPolicy>(hash);
0173         resolveParentCollisionsImpl<OldParentCollistionPolicy>(hash);
0174     }
0175 
0176     static void addToHashLazy(MovedNodesHash *hash, MoveNodeStructSP moveStruct) {
0177         if (hash->contains(moveStruct->node)) {
0178             bool result = hash->value(moveStruct->node)->tryMerge(*moveStruct);
0179             KIS_ASSERT_RECOVER_NOOP(result);
0180         } else {
0181             hash->insert(moveStruct->node, moveStruct);
0182         }
0183     }
0184 
0185 public:
0186 
0187     void processUnhandledUpdates() {
0188         QMutexLocker l(&m_mutex);
0189 
0190         if (m_movedNodesInitial.isEmpty()) return;
0191 
0192         MovedNodesHash::const_iterator it = m_movedNodesInitial.constBegin();
0193         MovedNodesHash::const_iterator end = m_movedNodesInitial.constEnd();
0194 
0195         for (; it != end; ++it) {
0196             it.value()->doRedoUpdates();
0197             addToHashLazy(&m_movedNodesUpdated, it.value());
0198         }
0199 
0200         resolveParentCollisions(&m_movedNodesUpdated);
0201 
0202         m_movedNodesInitial.clear();
0203     }
0204 
0205     void addInitialUpdate(MoveNodeStructSP moveStruct) {
0206         {
0207             QMutexLocker l(&m_mutex);
0208             addToHashLazy(&m_movedNodesInitial, moveStruct);
0209             resolveParentCollisions(&m_movedNodesInitial);
0210 
0211             // the juggler might directly forward the signal to processUnhandledUpdates,
0212             // which would also like to get a lock, so we should release it beforehand
0213         }
0214         if (m_parentJuggler) {
0215             emit m_parentJuggler->requestUpdateAsyncFromCommand();
0216         }
0217     }
0218 
0219     void emitFinalUpdates(KisCommandUtils::FlipFlopCommand::State state) {
0220         QMutexLocker l(&m_mutex);
0221 
0222         if (m_movedNodesUpdated.isEmpty()) return;
0223 
0224         MovedNodesHash::const_iterator it = m_movedNodesUpdated.constBegin();
0225         MovedNodesHash::const_iterator end = m_movedNodesUpdated.constEnd();
0226 
0227         for (; it != end; ++it) {
0228             if (state == KisCommandUtils::FlipFlopCommand::State::FINALIZING) {
0229                 it.value()->doRedoUpdates();
0230             } else {
0231                 it.value()->doUndoUpdates();
0232             }
0233         }
0234     }
0235 };
0236 
0237 typedef QSharedPointer<BatchMoveUpdateData> BatchMoveUpdateDataSP;
0238 
0239 /**
0240  * A command that emits a update signals on the compressed move undo
0241  * or redo.
0242  */
0243 class UpdateMovedNodesCommand : public KisCommandUtils::FlipFlopCommand
0244 {
0245 public:
0246     UpdateMovedNodesCommand(BatchMoveUpdateDataSP updateData, bool finalize, KUndo2Command *parent = 0)
0247         : FlipFlopCommand(finalize, parent),
0248           m_updateData(updateData)
0249     {
0250     }
0251 
0252     void partB() override {
0253         State currentState = getState();
0254 
0255         if (currentState == FINALIZING && isFirstRedo()) {
0256             /**
0257              * When doing the first redo() some of the updates might
0258              * have already been executed by the juggler itself, so we
0259              * should process'unhandled' updates only
0260              */
0261             m_updateData->processUnhandledUpdates();
0262         } else {
0263             /**
0264              * When being executed by real undo/redo operations, we
0265              * should emit all the update signals. No one else will do
0266              * that for us (juggler, which did it in the previous
0267              * case, might have already died).
0268              */
0269             m_updateData->emitFinalUpdates(currentState);
0270         }
0271     }
0272 private:
0273     BatchMoveUpdateDataSP m_updateData;
0274 };
0275 
0276 /**
0277  * A command to activate newly created selection masks after any action
0278  */
0279 class ActivateSelectionMasksCommand : public KisCommandUtils::FlipFlopCommand
0280 {
0281 public:
0282     ActivateSelectionMasksCommand(const QList<KisSelectionMaskSP> &activeBefore,
0283                                   const QList<KisSelectionMaskSP> &activeAfter,
0284                                   bool finalize, KUndo2Command *parent = 0)
0285         : FlipFlopCommand(finalize, parent),
0286           m_activeBefore(activeBefore),
0287           m_activeAfter(activeAfter)
0288     {
0289     }
0290 
0291     void partA() override {
0292         QList<KisSelectionMaskSP> *newActiveMasks;
0293 
0294         if (getState() == FINALIZING) {
0295             newActiveMasks = &m_activeAfter;
0296         } else {
0297             newActiveMasks = &m_activeBefore;
0298         }
0299 
0300         Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
0301             mask->setActive(false);
0302         }
0303     }
0304 
0305     void partB() override {
0306         QList<KisSelectionMaskSP> *newActiveMasks;
0307 
0308         if (getState() == FINALIZING) {
0309             newActiveMasks = &m_activeAfter;
0310         } else {
0311             newActiveMasks = &m_activeBefore;
0312         }
0313 
0314         Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
0315             mask->setActive(true);
0316         }
0317     }
0318 
0319 private:
0320     QList<KisSelectionMaskSP> m_activeBefore;
0321     QList<KisSelectionMaskSP> m_activeAfter;
0322 };
0323 
0324 
0325 /**
0326  * A generalized command to muve up/down a set of layer
0327  */
0328 struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
0329     LowerRaiseLayer(BatchMoveUpdateDataSP updateData,
0330                     KisImageSP image,
0331                     const KisNodeList &nodes,
0332                     KisNodeSP activeNode,
0333                     bool lower)
0334         : m_updateData(updateData),
0335           m_image(image),
0336           m_nodes(nodes),
0337           m_activeNode(activeNode),
0338           m_lower (lower) {}
0339 
0340     enum NodesType {
0341         AllLayers,
0342         Mixed,
0343         AllMasks
0344     };
0345 
0346     NodesType getNodesType(KisNodeList nodes) {
0347         bool hasLayers = false;
0348         bool hasMasks = false;
0349 
0350         Q_FOREACH (KisNodeSP node, nodes) {
0351             hasLayers |= bool(qobject_cast<KisLayer*>(node.data()));
0352             hasMasks |= bool(qobject_cast<KisMask*>(node.data()));
0353         }
0354 
0355         return hasLayers && hasMasks ? Mixed :
0356             hasLayers ? AllLayers :
0357             AllMasks;
0358     }
0359 
0360     bool allowsAsChildren(KisNodeSP parent, KisNodeList nodes) {
0361         if (!parent->isEditable(false)) return false;
0362 
0363         Q_FOREACH (KisNodeSP node, nodes) {
0364             if (!parent->allowAsChild(node)) return false;
0365         }
0366 
0367         return true;
0368     }
0369 
0370     void populateChildCommands() override {
0371         KisNodeList sortedNodes = KisLayerUtils::sortAndFilterAnyMergableNodesSafe(m_nodes, m_image);
0372         KisNodeSP headNode = m_lower ? sortedNodes.first() : sortedNodes.last();
0373         const NodesType nodesType = getNodesType(sortedNodes);
0374 
0375         KisNodeSP parent = headNode->parent();
0376         KisNodeSP grandParent = parent ? parent->parent() : 0;
0377 
0378         if (!parent->isEditable(false)) return;
0379 
0380         KisNodeSP newAbove;
0381         KisNodeSP newParent;
0382 
0383         if (m_lower) {
0384             KisNodeSP prevNode = headNode->prevSibling();
0385 
0386             if (prevNode) {
0387                 if (allowsAsChildren(prevNode, sortedNodes) &&
0388                     !prevNode->collapsed()) {
0389 
0390                     newAbove = prevNode->lastChild();
0391                     newParent = prevNode;
0392                 } else {
0393                     newAbove = prevNode->prevSibling();
0394                     newParent = parent;
0395                 }
0396             } else if ((nodesType == AllLayers && grandParent) ||
0397                        (nodesType == AllMasks && grandParent && grandParent->parent())) {
0398                 newAbove = parent->prevSibling();
0399                 newParent = grandParent;
0400 
0401             } else if (nodesType == AllMasks &&
0402                        grandParent && !grandParent->parent() &&
0403                        (prevNode = parent->prevSibling()) &&
0404                        prevNode->inherits("KisLayer")) {
0405 
0406                 newAbove = prevNode->lastChild();
0407                 newParent = prevNode; // NOTE: this is an updated 'prevNode'!
0408             }
0409         } else {
0410             KisNodeSP nextNode = headNode->nextSibling();
0411 
0412             if (nextNode) {
0413                 if (allowsAsChildren(nextNode, sortedNodes) &&
0414                         !nextNode->collapsed()) {
0415                     newAbove = 0;
0416                     newParent = nextNode;
0417                 } else {
0418                     newAbove = nextNode;
0419                     newParent = parent;
0420                 }
0421             } else if ((nodesType == AllLayers && grandParent) ||
0422                        (nodesType == AllMasks && grandParent && grandParent->parent())) {
0423                 newAbove = parent;
0424                 newParent = grandParent;
0425 
0426             } else if (nodesType == AllMasks &&
0427                        grandParent && !grandParent->parent() &&
0428                        (nextNode = parent->nextSibling()) &&
0429                        nextNode->inherits("KisLayer")) {
0430 
0431                 newAbove = 0;
0432                 newParent = nextNode; // NOTE: this is an updated 'nextNode'!
0433             }
0434         }
0435 
0436         if (!newParent) return;
0437 
0438         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
0439                                                                m_activeNode, m_activeNode,
0440                                                                m_image, false));
0441 
0442         KisNodeSP currentAbove = newAbove;
0443         Q_FOREACH (KisNodeSP node, sortedNodes) {
0444             if (node->parent() != newParent && !newParent->allowAsChild(node)) {
0445                 continue;
0446             }
0447 
0448             MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, newParent, currentAbove));
0449             addCommand(new KisImageLayerMoveCommand(m_image, node, newParent, currentAbove, false));
0450             m_updateData->addInitialUpdate(moveStruct);
0451             currentAbove = node;
0452         }
0453 
0454         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
0455                                                                m_activeNode, m_activeNode,
0456                                                                m_image, true));
0457     }
0458 
0459 private:
0460     BatchMoveUpdateDataSP m_updateData;
0461     KisImageSP m_image;
0462     KisNodeList m_nodes;
0463     KisNodeSP m_activeNode;
0464     bool m_lower;
0465 };
0466 
0467 struct DuplicateLayers : public KisCommandUtils::AggregateCommand {
0468     enum Mode {
0469         MOVE,
0470         COPY,
0471         ADD
0472     };
0473 
0474     DuplicateLayers(BatchMoveUpdateDataSP updateData,
0475                     KisImageSP image,
0476                     const KisNodeList &nodes,
0477                     KisNodeSP dstParent,
0478                     KisNodeSP dstAbove,
0479                     KisNodeSP activeNode,
0480                     Mode mode)
0481         : m_updateData(updateData),
0482           m_image(image),
0483           m_nodes(nodes),
0484           m_dstParent(dstParent),
0485           m_dstAbove(dstAbove),
0486           m_activeNode(activeNode),
0487           m_mode(mode) {}
0488 
0489     void populateChildCommands() override {
0490         KisNodeList filteredNodes = KisLayerUtils::sortAndFilterAnyMergableNodesSafe(m_nodes, m_image);
0491 
0492         if (filteredNodes.isEmpty()) return;
0493 
0494         KisNodeSP newAbove = filteredNodes.last();
0495 
0496         // make sure we don't add the new layer into a locked group
0497         while (newAbove->parent() && !newAbove->parent()->isEditable(false)) {
0498             newAbove = newAbove->parent();
0499         }
0500 
0501         KisNodeSP newParent = newAbove->parent();
0502 
0503         // override parent if provided externally
0504         if (m_dstParent) {
0505             newAbove = m_dstAbove;
0506             newParent = m_dstParent;
0507         }
0508 
0509         const int indexOfActiveNode = filteredNodes.indexOf(m_activeNode);
0510         QList<KisSelectionMaskSP> activeMasks = findActiveSelectionMasks(filteredNodes);
0511 
0512         // we will deactivate the masks before processing, so we should
0513         // save their list in a convenient form
0514         QSet<KisNodeSP> activeMaskNodes;
0515         Q_FOREACH (KisSelectionMaskSP mask, activeMasks) {
0516             activeMaskNodes.insert(mask);
0517         }
0518 
0519         const bool haveActiveMasks = !activeMasks.isEmpty();
0520 
0521         if (!newParent) return;
0522 
0523         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
0524                                                                m_activeNode, KisNodeSP(),
0525                                                                m_image, false));
0526 
0527         if (haveActiveMasks) {
0528             /**
0529              * We should first disable the currently active masks, after the operation
0530              * completed their cloned counterparts will be activated instead.
0531              *
0532              * HINT: we should deactivate the masks before cloning, because otherwise
0533              *       KisGroupLayer::allowAsChild() will not let the second mask to be
0534              *       added to the list of child nodes. See bug 382315.
0535              */
0536 
0537             addCommand(new ActivateSelectionMasksCommand(activeMasks,
0538                                                          QList<KisSelectionMaskSP>(),
0539                                                          false));
0540         }
0541 
0542         KisNodeList newNodes;
0543         QList<KisSelectionMaskSP> newActiveMasks;
0544         KisNodeSP currentAbove = newAbove;
0545         Q_FOREACH (KisNodeSP node, filteredNodes) {
0546             if (m_mode == COPY || m_mode == ADD) {
0547                 KisNodeSP newNode;
0548 
0549                 if (m_mode == COPY) {
0550                     newNode = node->clone();
0551                     KisLayerUtils::addCopyOfNameTag(newNode);
0552                 } else {
0553                     newNode = node;
0554                 }
0555 
0556                 newNodes << newNode;
0557                 if (haveActiveMasks && activeMaskNodes.contains(node)) {
0558                     KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(newNode.data());
0559                     newActiveMasks << mask;
0560                 }
0561 
0562                 MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
0563                 m_updateData->addInitialUpdate(moveStruct);
0564 
0565                 addCommand(new KisImageLayerAddCommand(m_image, newNode,
0566                                                        newParent,
0567                                                        currentAbove,
0568                                                        false, false));
0569 
0570                 currentAbove = newNode;
0571             } else if (m_mode == MOVE) {
0572                 KisNodeSP newNode = node;
0573 
0574                 newNodes << newNode;
0575                 if (haveActiveMasks && activeMaskNodes.contains(node)) {
0576                     KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(newNode.data());
0577                     newActiveMasks << mask;
0578                 }
0579 
0580                 MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
0581                 m_updateData->addInitialUpdate(moveStruct);
0582 
0583                 addCommand(new KisImageLayerMoveCommand(m_image, newNode,
0584                                                         newParent,
0585                                                         currentAbove,
0586                                                         false));
0587                 currentAbove = newNode;
0588             }
0589         }
0590 
0591 
0592         if (haveActiveMasks) {
0593             /**
0594              * Activate the cloned counterparts of the masks after the operation
0595              * is complete.
0596              */
0597             addCommand(new ActivateSelectionMasksCommand(QList<KisSelectionMaskSP>(),
0598                                                          newActiveMasks,
0599                                                          true));
0600         }
0601 
0602         KisNodeSP newActiveNode = newNodes[qBound(0, indexOfActiveNode, newNodes.size() - 1)];
0603 
0604         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(KisNodeList(), newNodes,
0605                                                                KisNodeSP(), newActiveNode,
0606                                                                m_image, true));
0607     }
0608 private:
0609     KisSelectionMaskSP toActiveSelectionMask(KisNodeSP node) {
0610         KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(node.data());
0611         return mask && mask->active() ? mask : 0;
0612     }
0613 
0614 
0615     QList<KisSelectionMaskSP> findActiveSelectionMasks(KisNodeList nodes) {
0616         QList<KisSelectionMaskSP> masks;
0617         foreach (KisNodeSP node, nodes) {
0618             KisSelectionMaskSP mask = toActiveSelectionMask(node);
0619             if (mask) {
0620                 masks << mask;
0621             }
0622         }
0623         return masks;
0624     }
0625 private:
0626     BatchMoveUpdateDataSP m_updateData;
0627     KisImageSP m_image;
0628     KisNodeList m_nodes;
0629     KisNodeSP m_dstParent;
0630     KisNodeSP m_dstAbove;
0631     KisNodeSP m_activeNode;
0632     Mode m_mode;
0633 };
0634 
0635 struct RemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
0636     RemoveLayers(BatchMoveUpdateDataSP updateData,
0637                  KisImageSP image,
0638                  const KisNodeList &nodes,
0639                  KisNodeSP activeNode)
0640         : m_updateData(updateData),
0641           m_image(image),
0642           m_nodes(nodes),
0643           m_activeNode(activeNode){}
0644 
0645     void populateChildCommands() override {
0646         KisNodeList filteredNodes = m_nodes;
0647         KisLayerUtils::filterMergableNodes(filteredNodes, true);
0648         KisLayerUtils::filterUnlockedNodes(filteredNodes);
0649 
0650         if (filteredNodes.isEmpty()) return;
0651 
0652         Q_FOREACH (KisNodeSP node, filteredNodes) {
0653             MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, KisNodeSP(), KisNodeSP()));
0654             m_updateData->addInitialUpdate(moveStruct);
0655         }
0656 
0657         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
0658                                                                m_activeNode, KisNodeSP(),
0659                                                                m_image, false));
0660 
0661         safeRemoveMultipleNodes(filteredNodes, m_image);
0662 
0663         addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
0664                                                                m_activeNode, KisNodeSP(),
0665                                                                m_image, true));
0666     }
0667 protected:
0668     void addCommandImpl(KUndo2Command *cmd) override {
0669         addCommand(cmd);
0670     }
0671 
0672 private:
0673     BatchMoveUpdateDataSP m_updateData;
0674     KisImageSP m_image;
0675     KisNodeList m_nodes;
0676     KisNodeSP m_activeNode;
0677 };
0678 
0679 struct KisNodeJugglerCompressed::Private
0680 {
0681     Private(KisNodeJugglerCompressed *juggler, const KUndo2MagicString &_actionName, KisImageSP _image, KisNodeManager *_nodeManager, int _timeout)
0682         : actionName(_actionName),
0683           image(_image),
0684           nodeManager(_nodeManager),
0685           compressor(_timeout, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
0686           selfDestructionCompressor(3 * _timeout, KisSignalCompressor::POSTPONE),
0687           updateData(new BatchMoveUpdateData(juggler)),
0688           autoDelete(false),
0689           isStarted(false)
0690     {}
0691 
0692     KUndo2MagicString actionName;
0693     KisImageSP image;
0694     KisNodeManager *nodeManager;
0695     QScopedPointer<KisProcessingApplicator> applicator;
0696 
0697     KisSignalCompressor compressor;
0698     KisSignalCompressor selfDestructionCompressor;
0699 
0700     BatchMoveUpdateDataSP updateData;
0701 
0702     bool autoDelete;
0703     bool isStarted;
0704 };
0705 
0706 KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout)
0707     : m_d(new Private(this, actionName, image, nodeManager, timeout))
0708 {
0709 
0710     KisImageSignalVector emitSignals;
0711 
0712     m_d->applicator.reset(
0713         new KisProcessingApplicator(m_d->image, 0,
0714                                     KisProcessingApplicator::NONE,
0715                                     emitSignals,
0716                                     actionName));
0717     connect(this, SIGNAL(requestUpdateAsyncFromCommand()), SLOT(startTimers()));
0718     connect(&m_d->compressor, SIGNAL(timeout()), SLOT(slotUpdateTimeout()));
0719 
0720     connect(m_d->image, SIGNAL(sigStrokeCancellationRequested()), SLOT(slotEndStrokeRequested()));
0721     connect(m_d->image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(slotCancelStrokeRequested()));
0722     connect(m_d->image, SIGNAL(sigStrokeEndRequestedActiveNodeFiltered()), SLOT(slotEndStrokeRequested()));
0723     connect(m_d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(slotImageAboutToBeDeleted()));
0724 
0725     m_d->applicator->applyCommand(
0726         new UpdateMovedNodesCommand(m_d->updateData, false));
0727     m_d->isStarted = true;
0728 }
0729 
0730 KisNodeJugglerCompressed::~KisNodeJugglerCompressed()
0731 {
0732     KIS_ASSERT_RECOVER(!m_d->applicator) {
0733         m_d->applicator->end();
0734         m_d->applicator.reset();
0735     }
0736 }
0737 
0738 bool KisNodeJugglerCompressed::canMergeAction(const KUndo2MagicString &actionName)
0739 {
0740     return actionName == m_d->actionName;
0741 }
0742 
0743 void KisNodeJugglerCompressed::lowerNode(const KisNodeList &nodes)
0744 {
0745     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0746 
0747     m_d->applicator->applyCommand(
0748         new LowerRaiseLayer(m_d->updateData,
0749                             m_d->image,
0750                             nodes, activeNode, true),
0751                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0752 
0753 }
0754 
0755 void KisNodeJugglerCompressed::raiseNode(const KisNodeList &nodes)
0756 {
0757     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0758 
0759     m_d->applicator->applyCommand(
0760         new LowerRaiseLayer(m_d->updateData,
0761                             m_d->image,
0762                             nodes, activeNode, false),
0763                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0764 }
0765 
0766 void KisNodeJugglerCompressed::removeNode(const KisNodeList &nodes)
0767 {
0768     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0769 
0770     m_d->applicator->applyCommand(
0771         new RemoveLayers(m_d->updateData,
0772                          m_d->image,
0773                          nodes, activeNode),
0774                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0775 }
0776 
0777 void KisNodeJugglerCompressed::duplicateNode(const KisNodeList &nodes)
0778 {
0779     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0780 
0781     m_d->applicator->applyCommand(
0782         new DuplicateLayers(m_d->updateData,
0783                             m_d->image,
0784                             nodes,
0785                             KisNodeSP(), KisNodeSP(),
0786                             activeNode,
0787                             DuplicateLayers::COPY),
0788                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0789 }
0790 
0791 void KisNodeJugglerCompressed::copyNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
0792 {
0793     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0794 
0795     m_d->applicator->applyCommand(
0796         new DuplicateLayers(m_d->updateData,
0797                             m_d->image,
0798                             nodes,
0799                             dstParent, dstAbove,
0800                             activeNode,
0801                             DuplicateLayers::COPY),
0802                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0803 }
0804 
0805 void KisNodeJugglerCompressed::moveNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
0806 {
0807     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0808 
0809     m_d->applicator->applyCommand(
0810         new DuplicateLayers(m_d->updateData,
0811                             m_d->image,
0812                             nodes,
0813                             dstParent, dstAbove,
0814                             activeNode,
0815                             DuplicateLayers::MOVE),
0816                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0817 }
0818 
0819 void KisNodeJugglerCompressed::addNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
0820 {
0821     KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
0822 
0823     m_d->applicator->applyCommand(
0824         new DuplicateLayers(m_d->updateData,
0825                             m_d->image,
0826                             nodes,
0827                             dstParent, dstAbove,
0828                             activeNode,
0829                             DuplicateLayers::ADD),
0830                 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0831 }
0832 
0833 void KisNodeJugglerCompressed::moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above)
0834 {
0835     m_d->applicator->applyCommand(new KisImageLayerMoveCommand(m_d->image, node, parent, above, false),
0836                                   KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0837 
0838     MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_d->image, node, parent, above));
0839 
0840     m_d->updateData->addInitialUpdate(moveStruct);
0841 }
0842 
0843 void KisNodeJugglerCompressed::startTimers()
0844 {
0845     m_d->compressor.start();
0846 
0847     if (m_d->autoDelete) {
0848         m_d->selfDestructionCompressor.start();
0849     }
0850 }
0851 
0852 void KisNodeJugglerCompressed::slotUpdateTimeout()
0853 {
0854     // The juggler could have been already finished explicitly
0855     // by slotEndStrokeRequested(). In such a case the final updates
0856     // will be issued by the last command of the stroke.
0857 
0858     if (!m_d->updateData || !m_d->isStarted) return;
0859 
0860     BatchMoveUpdateDataSP updateData = m_d->updateData;
0861 
0862     /**
0863      * Since the update should be synchronized with the
0864      * flow of layer-add commands, we issue it from a fake
0865      * undo command that doesn't generate any history
0866      */
0867     m_d->applicator->applyCommand(
0868         new KisCommandUtils::LambdaCommand(
0869             [updateData] () {
0870                 updateData->processUnhandledUpdates();
0871                 return nullptr;
0872             }));
0873 }
0874 
0875 void KisNodeJugglerCompressed::end()
0876 {
0877     if (!m_d->isStarted) return;
0878 
0879     m_d->applicator->applyCommand(
0880         new UpdateMovedNodesCommand(m_d->updateData, true));
0881 
0882     m_d->applicator->end();
0883     cleanup();
0884 }
0885 
0886 void KisNodeJugglerCompressed::cleanup()
0887 {
0888     m_d->applicator.reset();
0889     m_d->compressor.stop();
0890     m_d->image.clear();
0891     m_d->updateData.clear();
0892     m_d->isStarted = false;
0893 
0894     if (m_d->autoDelete) {
0895         m_d->selfDestructionCompressor.stop();
0896         this->deleteLater();
0897     }
0898 }
0899 
0900 void KisNodeJugglerCompressed::setAutoDelete(bool value)
0901 {
0902     m_d->autoDelete = value;
0903     connect(&m_d->selfDestructionCompressor, SIGNAL(timeout()), SLOT(end()));
0904 }
0905 
0906 void KisNodeJugglerCompressed::slotEndStrokeRequested()
0907 {
0908     if (!m_d->isStarted) return;
0909     end();
0910 }
0911 
0912 void KisNodeJugglerCompressed::slotCancelStrokeRequested()
0913 {
0914     if (!m_d->isStarted) return;
0915 
0916     m_d->applicator->cancel();
0917     cleanup();
0918 }
0919 
0920 void KisNodeJugglerCompressed::slotImageAboutToBeDeleted()
0921 {
0922     if (!m_d->isStarted) return;
0923 
0924     m_d->applicator->end();
0925     cleanup();
0926 }
0927 
0928 bool KisNodeJugglerCompressed::isEnded() const
0929 {
0930     return !m_d->isStarted;
0931 }