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 }