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 }