File indexing completed on 2024-12-22 04:12:57

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "move_stroke_strategy.h"
0008 
0009 #include <klocalizedstring.h>
0010 #include "kis_image_interfaces.h"
0011 #include "kis_node.h"
0012 #include "commands_new/kis_update_command.h"
0013 #include "commands_new/kis_node_move_command2.h"
0014 #include "kis_layer_utils.h"
0015 #include "krita_utils.h"
0016 
0017 #include "KisRunnableStrokeJobData.h"
0018 #include "KisRunnableStrokeJobUtils.h"
0019 #include "KisRunnableStrokeJobsInterface.h"
0020 #include "kis_abstract_projection_plane.h"
0021 #include "kis_image.h"
0022 #include "kis_image_animation_interface.h"
0023 #include "kis_raster_keyframe_channel.h"
0024 #include "KisAnimAutoKey.h"
0025 
0026 #include "kis_transform_mask.h"
0027 #include "kis_transform_mask_params_interface.h"
0028 #include "kis_keyframe_channel.h"
0029 #include "kis_scalar_keyframe_channel.h"
0030 #include "kis_image_animation_interface.h"
0031 #include "commands_new/KisSimpleModifyTransformMaskCommand.h"
0032 #include "commands_new/KisLazyCreateTransformMaskKeyframesCommand.h"
0033 
0034 /* MoveNodeStrategyBase and descendants
0035  *
0036  * A set of strategies that define how to actually move
0037  * nodes of different types. Some nodes should be moved
0038  * with KisNodeMoveCommand2, others with KisTransaction,
0039  * transform masks with their own command.
0040  */
0041 
0042 struct MoveNodeStrategyBase
0043 {
0044     MoveNodeStrategyBase(KisNodeSP node)
0045         : m_node(node),
0046           m_initialOffset(node->x(), node->y())
0047     {
0048     }
0049 
0050     virtual ~MoveNodeStrategyBase() {}
0051 
0052     virtual QRect moveNode(const QPoint &offset) = 0;
0053     virtual void finishMove(KUndo2Command *parentCommand) = 0;
0054     virtual QRect cancelMove() = 0;
0055 
0056 protected:
0057     QRect moveNodeCommon(const QPoint &offset) {
0058         const QPoint newOffset = m_initialOffset + offset;
0059 
0060         QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
0061 
0062         /**
0063          * Some layers, e.g. clones need an update to change extent(), so
0064          * calculate the dirty rect manually
0065          */
0066         QPoint currentOffset(m_node->x(), m_node->y());
0067         dirtyRect |= dirtyRect.translated(newOffset - currentOffset);
0068 
0069         m_node->setX(newOffset.x());
0070         m_node->setY(newOffset.y());
0071 
0072         KisNodeMoveCommand2::tryNotifySelection(m_node);
0073         return dirtyRect;
0074     }
0075 
0076 protected:
0077     KisNodeSP m_node;
0078     QPoint m_initialOffset;
0079 };
0080 
0081 struct MoveNormalNodeStrategy : public MoveNodeStrategyBase
0082 {
0083     MoveNormalNodeStrategy(KisNodeSP node)
0084         : MoveNodeStrategyBase(node)
0085     {
0086     }
0087 
0088     QRect moveNode(const QPoint &offset) override {
0089         return moveNodeCommon(offset);
0090     }
0091 
0092     void finishMove(KUndo2Command *parentCommand) override {
0093         const QPoint nodeOffset(m_node->x(), m_node->y());
0094         new KisNodeMoveCommand2(m_node, m_initialOffset, nodeOffset, parentCommand);
0095     }
0096 
0097     QRect cancelMove() override {
0098         return moveNode(QPoint());
0099     }
0100 
0101 };
0102 
0103 struct MoveTransformMaskStrategy : public MoveNodeStrategyBase
0104 {
0105     MoveTransformMaskStrategy(KisNodeSP node)
0106         : MoveNodeStrategyBase(node)
0107     {
0108     }
0109 
0110     QRect moveNode(const QPoint &offset) override {
0111         QScopedPointer<KUndo2Command> cmd;
0112         QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
0113 
0114         KisTransformMask *mask = dynamic_cast<KisTransformMask*>(m_node.data());
0115         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(mask, QRect());
0116 
0117         KisTransformMaskParamsInterfaceSP oldParams = mask->transformParams();
0118         KisTransformMaskParamsInterfaceSP params = oldParams->clone();
0119         params->translateDstSpace(offset - m_currentOffset);
0120 
0121         cmd.reset(new KisSimpleModifyTransformMaskCommand(mask, params));
0122         cmd->redo();
0123 
0124         if (m_undoCommand) {
0125             const bool mergeResult = m_undoCommand->mergeWith(cmd.get());
0126             KIS_SAFE_ASSERT_RECOVER_NOOP(mergeResult);
0127             cmd.reset();
0128         } else {
0129             m_undoCommand.swap(cmd);
0130         }
0131 
0132         m_currentOffset = offset;
0133 
0134         dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
0135 
0136         return dirtyRect;
0137     }
0138 
0139     void finishMove(KUndo2Command *parentCommand) override {
0140         KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(parentCommand);
0141         cmd->addCommand(m_undoCommand.take());
0142     }
0143 
0144     QRect cancelMove() override {
0145         return moveNode(QPoint());
0146     }
0147 
0148 private:
0149     QPoint m_currentOffset;
0150     QScopedPointer<KUndo2Command> m_undoCommand;
0151 };
0152 
0153 struct MovePaintableNodeStrategy : public MoveNodeStrategyBase
0154 {
0155     MovePaintableNodeStrategy(KisNodeSP node)
0156         : MoveNodeStrategyBase(node),
0157           m_transaction(node->paintDevice(), 0, -1, 0, KisTransaction::SuppressUpdates)
0158     {
0159         // TODO: disable updates in the transaction
0160     }
0161 
0162     QRect moveNode(const QPoint &offset) override {
0163         return moveNodeCommon(offset);
0164     }
0165 
0166     void finishMove(KUndo2Command *parentCommand) override {
0167         KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(parentCommand);
0168 
0169         KUndo2Command *transactionCommand = m_transaction.endAndTake();
0170         transactionCommand->redo();
0171         cmd->addCommand(transactionCommand);
0172     }
0173 
0174     QRect cancelMove() override {
0175         QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
0176 
0177         m_transaction.revert();
0178 
0179         dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
0180 
0181         return dirtyRect;
0182     }
0183 
0184 private:
0185     KisTransaction m_transaction;
0186 };
0187 
0188 /*******************************************************/
0189 /*    MoveStrokeStrategy::Private                      */
0190 /*******************************************************/
0191 
0192 struct MoveStrokeStrategy::Private {
0193     std::unordered_map<KisNodeSP, std::unique_ptr<MoveNodeStrategyBase>> strategy;
0194 };
0195 
0196 template <typename Functor>
0197 void MoveStrokeStrategy::recursiveApplyNodes(KisNodeList nodes, Functor &&func) {
0198     Q_FOREACH(KisNodeSP subtree, nodes) {
0199         KisLayerUtils::recursiveApplyNodes(subtree,
0200             [&] (KisNodeSP node) {
0201                 if (!m_blacklistedNodes.contains(node)) {
0202                     func(node);
0203                 }
0204             });
0205     }
0206 }
0207 
0208 /*******************************************************/
0209 /*    MoveStrokeStrategy                               */
0210 /*******************************************************/
0211 
0212 MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSelectionRecipe nodeSelection,
0213                                        KisUpdatesFacade *updatesFacade,
0214                                        KisStrokeUndoFacade *undoFacade)
0215     : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade),
0216       m_d(new Private()),
0217       m_requestedNodeSelection(nodeSelection),
0218       m_updatesFacade(updatesFacade),
0219       m_updatesEnabled(true)
0220 {
0221     setSupportsWrapAroundMode(true);
0222 
0223     enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER);
0224 }
0225 
0226 MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade)
0227     : MoveStrokeStrategy(KisNodeSelectionRecipe(nodes), updatesFacade, undoFacade)
0228 {
0229 }
0230 
0231 MoveStrokeStrategy::~MoveStrokeStrategy()
0232 {
0233 }
0234 
0235 MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs, int lod)
0236     : QObject(),
0237       KisStrokeStrategyUndoCommandBased(rhs),
0238       m_d(new Private()),
0239       m_requestedNodeSelection(rhs.m_requestedNodeSelection, lod),
0240       m_nodes(rhs.m_nodes),
0241       m_blacklistedNodes(rhs.m_blacklistedNodes),
0242       m_updatesFacade(rhs.m_updatesFacade),
0243       m_finalOffset(rhs.m_finalOffset),
0244       m_dirtyRects(rhs.m_dirtyRects),
0245       m_updatesEnabled(rhs.m_updatesEnabled)
0246 {
0247     KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->strategy.empty());
0248 }
0249 
0250 void MoveStrokeStrategy::initStrokeCallback()
0251 {
0252     /**
0253      * Our LodN might have already prepared the list of nodes for us,
0254      * so we should reuse it to avoid different nodes to be moved in
0255      * LodN and Lod0 modes.
0256      */
0257     if (m_updatesEnabled) {
0258         m_nodes = m_requestedNodeSelection.selectNodesToProcess();
0259 
0260         if (!m_nodes.isEmpty()) {
0261             m_nodes = KisLayerUtils::sortAndFilterMergeableInternalNodes(m_nodes, true);
0262         }
0263 
0264         KritaUtils::filterContainer<KisNodeList>(m_nodes,
0265                                                  [this](KisNodeSP node) {
0266             // TODO: check isolation
0267             return
0268                     !KisLayerUtils::checkIsCloneOf(node, m_nodes) &&
0269                     node->isEditable(true);
0270         });
0271         Q_FOREACH(KisNodeSP subtree, m_nodes) {
0272             KisLayerUtils::recursiveApplyNodes(
0273                         subtree,
0274                         [this](KisNodeSP node) {
0275                 if (KisLayerUtils::checkIsCloneOf(node, m_nodes) ||
0276                         !node->isEditable(false) ||
0277                         (dynamic_cast<KisTransformMask*>(node.data()) &&
0278                          KisLayerUtils::checkIsChildOf(node, m_nodes))) {
0279 
0280                     m_blacklistedNodes.insert(node);
0281                 }
0282             });
0283         }
0284 
0285         if (m_sharedNodes) {
0286             *m_sharedNodes = std::make_pair(m_nodes, m_blacklistedNodes);
0287         }
0288     } else {
0289         KIS_SAFE_ASSERT_RECOVER_RETURN(m_sharedNodes);
0290         std::tie(m_nodes, m_blacklistedNodes) = *m_sharedNodes;
0291     }
0292 
0293     if (m_nodes.isEmpty()) {
0294         emit sigStrokeStartedEmpty();
0295         return;
0296     }
0297 
0298     QVector<KisRunnableStrokeJobData*> jobs;
0299 
0300     KritaUtils::addJobBarrier(jobs, [this]() {
0301         Q_FOREACH(KisNodeSP node, m_nodes) {
0302             KisLayerUtils::forceAllHiddenOriginalsUpdate(node);
0303         }
0304     });
0305 
0306     KritaUtils::addJobBarrier(jobs, [this]() {
0307         Q_FOREACH(KisNodeSP node, m_nodes) {
0308             KisLayerUtils::forceAllDelayedNodesUpdate(node);
0309         }
0310     });
0311 
0312     KritaUtils::addJobBarrier(jobs, [this]() {
0313         QRect handlesRect;
0314 
0315         /**
0316          * Collect handles rect
0317          */
0318         recursiveApplyNodes(m_nodes,
0319             [&handlesRect] (KisNodeSP node) {
0320                 handlesRect |= node->projectionPlane()->tightUserVisibleBounds();
0321             });
0322 
0323         KisStrokeStrategyUndoCommandBased::initStrokeCallback();
0324 
0325         Q_FOREACH(KisNodeSP node, m_nodes) {
0326             if (node->hasEditablePaintDevice()) {
0327                 KUndo2Command *autoKeyframeCommand =
0328                         KisAutoKey::tryAutoCreateDuplicatedFrame(node->paintDevice(),
0329                                                                  KisAutoKey::SupportsLod);
0330                 if (autoKeyframeCommand) {
0331                     runAndSaveCommand(toQShared(autoKeyframeCommand), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0332                 }
0333             } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) {
0334                 const bool maskAnimated = KisLazyCreateTransformMaskKeyframesCommand::maskHasAnimation(mask);
0335 
0336                 if (maskAnimated) {
0337                     runAndSaveCommand(toQShared(new KisLazyCreateTransformMaskKeyframesCommand(mask)), KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0338                 }
0339             }
0340         }
0341 
0342         /**
0343          * Create strategies and start the transactions when necessary
0344          */
0345         recursiveApplyNodes(m_nodes,
0346             [this] (KisNodeSP node) {
0347                 if (dynamic_cast<KisTransformMask*>(node.data())) {
0348                     m_d->strategy.emplace(node, new MoveTransformMaskStrategy(node));
0349                 } else if (node->paintDevice()) {
0350                     m_d->strategy.emplace(node, new MovePaintableNodeStrategy(node));
0351                 } else {
0352                     m_d->strategy.emplace(node, new MoveNormalNodeStrategy(node));
0353                 }
0354             });
0355 
0356         if (m_updatesEnabled) {
0357             KisLodTransform t(m_nodes.first()->image()->currentLevelOfDetail());
0358             handlesRect = t.mapInverted(handlesRect);
0359 
0360             emit this->sigHandlesRectCalculated(handlesRect);
0361         }
0362 
0363         m_updateTimer.start();
0364     });
0365 
0366     runnableJobsInterface()->addRunnableJobs(jobs);
0367 }
0368 
0369 void MoveStrokeStrategy::finishStrokeCallback()
0370 {
0371     Q_FOREACH (KisNodeSP node, m_nodes) {
0372         KUndo2Command *updateCommand =
0373             new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
0374 
0375         recursiveApplyNodes({node}, [this, updateCommand](KisNodeSP node) {
0376             KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strategy.find(node) != m_d->strategy.end());
0377 
0378             MoveNodeStrategyBase *strategy = m_d->strategy[node].get();
0379 
0380             strategy->finishMove(updateCommand);
0381         });
0382 
0383         notifyCommandDone(KUndo2CommandSP(updateCommand),
0384                           KisStrokeJobData::SEQUENTIAL,
0385                           KisStrokeJobData::EXCLUSIVE);
0386     }
0387 
0388     if (!m_updatesEnabled) {
0389         Q_FOREACH (KisNodeSP node, m_nodes) {
0390             m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]);
0391         }
0392     }
0393 
0394     KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
0395 }
0396 
0397 void MoveStrokeStrategy::cancelStrokeCallback()
0398 {
0399     if (!m_nodes.isEmpty()) {
0400         m_finalOffset = QPoint();
0401         m_hasPostponedJob = true;
0402 
0403         QVector<KisRunnableStrokeJobData*> jobs;
0404 
0405         KritaUtils::addJobBarrierExclusive(jobs, [this]() {
0406             Q_FOREACH (KisNodeSP node, m_nodes) {
0407                 QRect dirtyRect;
0408 
0409                 recursiveApplyNodes({node},
0410                     [this, &dirtyRect] (KisNodeSP node) {
0411                         MoveNodeStrategyBase *strategy =
0412                             m_d->strategy[node].get();
0413                         KIS_SAFE_ASSERT_RECOVER_RETURN(strategy);
0414 
0415                         dirtyRect |= strategy->cancelMove();
0416                     });
0417 
0418                 m_dirtyRects[node] |= dirtyRect;
0419 
0420                 /// emit updates not looking onto the
0421                 /// updatesEnabled switch, since that is
0422                 /// the end of the stroke
0423                 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
0424             }
0425         });
0426 
0427         runnableJobsInterface()->addRunnableJobs(jobs);
0428     }
0429 
0430     KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
0431 }
0432 
0433 void MoveStrokeStrategy::tryPostUpdateJob(bool forceUpdate)
0434 {
0435     if (!m_hasPostponedJob) return;
0436 
0437     if (forceUpdate ||
0438         (m_updateTimer.elapsed() > m_updateInterval &&
0439          !m_updatesFacade->hasUpdatesRunning())) {
0440 
0441         addMutatedJob(new BarrierUpdateData(forceUpdate));
0442     }
0443 }
0444 
0445 void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
0446 {
0447     if (PickLayerData *pickData = dynamic_cast<PickLayerData*>(data)) {
0448         KisNodeSelectionRecipe clone = m_requestedNodeSelection;
0449         clone.pickPoint = pickData->pos;
0450         emit sigLayersPicked(clone.selectNodesToProcess());
0451         return;
0452     }
0453 
0454     Data *d = dynamic_cast<Data*>(data);
0455 
0456     if (!m_nodes.isEmpty() && d) {
0457         /**
0458          * NOTE: we do not care about threading here, because
0459          * all our jobs are declared sequential
0460          */
0461         m_finalOffset = d->offset;
0462         m_hasPostponedJob = true;
0463         tryPostUpdateJob(false);
0464 
0465     } else if (BarrierUpdateData *barrierData =
0466                dynamic_cast<BarrierUpdateData*>(data)) {
0467 
0468         doCanvasUpdate(barrierData->forceUpdate);
0469 
0470     } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData =
0471                dynamic_cast<KisAsynchronousStrokeUpdateHelper::UpdateData*>(data)) {
0472 
0473         tryPostUpdateJob(updateData->forceUpdate);
0474 
0475     } else {
0476         KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
0477     }
0478 }
0479 
0480 #include "kis_selection_mask.h"
0481 #include "kis_selection.h"
0482 
0483 void MoveStrokeStrategy::doCanvasUpdate(bool forceUpdate)
0484 {
0485     if (!forceUpdate &&
0486             (m_updateTimer.elapsed() < m_updateInterval ||
0487              m_updatesFacade->hasUpdatesRunning())) {
0488 
0489         return;
0490     }
0491 
0492     if (!m_hasPostponedJob) return;
0493 
0494     Q_FOREACH (KisNodeSP node, m_nodes) {
0495         QRect dirtyRect;
0496 
0497         recursiveApplyNodes({node},
0498             [this, &dirtyRect] (KisNodeSP node) {
0499                 MoveNodeStrategyBase *strategy =
0500                     m_d->strategy[node].get();
0501                 KIS_SAFE_ASSERT_RECOVER_RETURN(strategy);
0502 
0503                 dirtyRect |= strategy->moveNode(m_finalOffset);
0504             });
0505 
0506         m_dirtyRects[node] |= dirtyRect;
0507 
0508         if (m_updatesEnabled) {
0509             m_updatesFacade->refreshGraphAsync(node, dirtyRect);
0510         }
0511     }
0512 
0513     m_hasPostponedJob = false;
0514     m_updateTimer.restart();
0515 }
0516 
0517 void MoveStrokeStrategy::setUpdatesEnabled(bool value)
0518 {
0519     m_updatesEnabled = value;
0520 }
0521 
0522 bool checkSupportsLodMoves(KisNodeSP subtree)
0523 {
0524     return
0525         !KisLayerUtils::recursiveFindNode(
0526             subtree,
0527             [](KisNodeSP node) -> bool {
0528                 return !node->supportsLodMoves();
0529             });
0530 }
0531 
0532 
0533 KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail)
0534 {
0535     KisNodeList nodesToCheck;
0536 
0537     if (m_requestedNodeSelection.mode == KisNodeSelectionRecipe::SelectedLayer) {
0538         nodesToCheck = m_requestedNodeSelection.selectedNodes;
0539     } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){
0540         /**
0541          * Since this function is executed in the GUI thread, we cannot properly
0542          * pick the layers. Therefore we should use pessimistic approach and
0543          * check if there are non-lodn-capable nodes in the entire image.
0544          */
0545         nodesToCheck.append(KisLayerUtils::findRoot(m_requestedNodeSelection.selectedNodes.first()));
0546     }
0547 
0548     Q_FOREACH (KisNodeSP node, nodesToCheck) {
0549         if (!checkSupportsLodMoves(node)) return 0;
0550     }
0551 
0552     MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail);
0553     connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect)));
0554     connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty()));
0555     connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&)));
0556     this->setUpdatesEnabled(false);
0557     m_sharedNodes.reset(new std::pair<KisNodeList, QSet<KisNodeSP>>());
0558     clone->m_sharedNodes = m_sharedNodes;
0559     return clone;
0560 }
0561 
0562 MoveStrokeStrategy::Data::Data(QPoint _offset)
0563     : KisStrokeJobData(SEQUENTIAL, NORMAL),
0564       offset(_offset)
0565 {
0566 }
0567 
0568 KisStrokeJobData *MoveStrokeStrategy::Data::createLodClone(int levelOfDetail)
0569 {
0570     return new Data(*this, levelOfDetail);
0571 }
0572 
0573 MoveStrokeStrategy::Data::Data(const MoveStrokeStrategy::Data &rhs, int levelOfDetail)
0574     : KisStrokeJobData(rhs)
0575 {
0576     KisLodTransform t(levelOfDetail);
0577     offset = t.map(rhs.offset);
0578 }
0579 
0580 MoveStrokeStrategy::PickLayerData::PickLayerData(QPoint _pos)
0581     : KisStrokeJobData(SEQUENTIAL, NORMAL),
0582       pos(_pos)
0583 {
0584 }
0585 
0586 KisStrokeJobData *MoveStrokeStrategy::PickLayerData::createLodClone(int levelOfDetail) {
0587     return new PickLayerData(*this, levelOfDetail);
0588 }
0589 
0590 MoveStrokeStrategy::PickLayerData::PickLayerData(const MoveStrokeStrategy::PickLayerData &rhs, int levelOfDetail)
0591     : KisStrokeJobData(rhs)
0592 {
0593     KisLodTransform t(levelOfDetail);
0594     pos = t.map(rhs.pos);
0595 }
0596 
0597 MoveStrokeStrategy::BarrierUpdateData::BarrierUpdateData(bool _forceUpdate)
0598     : KisAsynchronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, EXCLUSIVE)
0599 {}
0600 
0601 KisStrokeJobData *MoveStrokeStrategy::BarrierUpdateData::createLodClone(int levelOfDetail) {
0602     return new BarrierUpdateData(*this, levelOfDetail);
0603 }
0604 
0605 MoveStrokeStrategy::BarrierUpdateData::BarrierUpdateData(const MoveStrokeStrategy::BarrierUpdateData &rhs, int levelOfDetail)
0606     : KisAsynchronousStrokeUpdateHelper::UpdateData(rhs, levelOfDetail)
0607 {
0608 }