Krita Source Code Documentation
Loading...
Searching...
No Matches
move_stroke_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <klocalizedstring.h>
11#include "kis_node.h"
14#include "kis_layer_utils.h"
15#include "krita_utils.h"
16
21#include "kis_image.h"
24#include "KisAnimAutoKey.h"
25
26#include "kis_transform_mask.h"
33
34/* MoveNodeStrategyBase and descendants
35 *
36 * A set of strategies that define how to actually move
37 * nodes of different types. Some nodes should be moved
38 * with KisNodeMoveCommand2, others with KisTransaction,
39 * transform masks with their own command.
40 */
41
43{
45 : m_node(node),
46 m_initialOffset(node->x(), node->y())
47 {
48 }
49
51
52 virtual QRect moveNode(const QPoint &offset) = 0;
53 virtual void finishMove(KUndo2Command *parentCommand) = 0;
54 virtual QRect cancelMove() = 0;
55
56protected:
57 QRect moveNodeCommon(const QPoint &offset) {
58 const QPoint newOffset = m_initialOffset + offset;
59
60 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
61
66 QPoint currentOffset(m_node->x(), m_node->y());
67 dirtyRect |= dirtyRect.translated(newOffset - currentOffset);
68
69 m_node->setX(newOffset.x());
70 m_node->setY(newOffset.y());
71
73 return dirtyRect;
74 }
75
76protected:
79};
80
82{
87
88 QRect moveNode(const QPoint &offset) override {
89 return moveNodeCommon(offset);
90 }
91
92 void finishMove(KUndo2Command *parentCommand) override {
93 const QPoint nodeOffset(m_node->x(), m_node->y());
94 new KisNodeMoveCommand2(m_node, m_initialOffset, nodeOffset, parentCommand);
95 }
96
97 QRect cancelMove() override {
98 return moveNode(QPoint());
99 }
100
101};
102
104{
109
110 QRect moveNode(const QPoint &offset) override {
111 QScopedPointer<KUndo2Command> cmd;
112 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
113
114 KisTransformMask *mask = dynamic_cast<KisTransformMask*>(m_node.data());
116
118 KisTransformMaskParamsInterfaceSP params = oldParams->clone();
119 params->translateDstSpace(offset - m_currentOffset);
120
121 cmd.reset(new KisSimpleModifyTransformMaskCommand(mask, params));
122 cmd->redo();
123
124 if (m_undoCommand) {
125 const bool mergeResult = m_undoCommand->mergeWith(cmd.get());
126 KIS_SAFE_ASSERT_RECOVER_NOOP(mergeResult);
127 cmd.reset();
128 } else {
129 m_undoCommand.swap(cmd);
130 }
131
132 m_currentOffset = offset;
133
134 dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
135
136 return dirtyRect;
137 }
138
139 void finishMove(KUndo2Command *parentCommand) override {
141 cmd->addCommand(m_undoCommand.take());
142 }
143
144 QRect cancelMove() override {
145 return moveNode(QPoint());
146 }
147
148private:
150 QScopedPointer<KUndo2Command> m_undoCommand;
151};
152
154{
156 : MoveNodeStrategyBase(node),
157 m_transaction(node->paintDevice(), 0, -1, 0, KisTransaction::SuppressUpdates)
158 {
159 // TODO: disable updates in the transaction
160 }
161
162 QRect moveNode(const QPoint &offset) override {
163 return moveNodeCommon(offset);
164 }
165
166 void finishMove(KUndo2Command *parentCommand) override {
168
169 KUndo2Command *transactionCommand = m_transaction.endAndTake();
170 transactionCommand->redo();
171 cmd->addCommand(transactionCommand);
172 }
173
174 QRect cancelMove() override {
175 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
176
178
179 dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
180
181 return dirtyRect;
182 }
183
184private:
186};
187
188/*******************************************************/
189/* MoveStrokeStrategy::Private */
190/*******************************************************/
191
193 std::unordered_map<KisNodeSP, std::unique_ptr<MoveNodeStrategyBase>> strategy;
194};
195
196template <typename Functor>
198 Q_FOREACH(KisNodeSP subtree, nodes) {
200 [&] (KisNodeSP node) {
201 if (!m_blacklistedNodes.contains(node)) {
202 func(node);
203 }
204 });
205 }
206}
207
208/*******************************************************/
209/* MoveStrokeStrategy */
210/*******************************************************/
211
213 KisUpdatesFacade *updatesFacade,
214 KisStrokeUndoFacade *undoFacade)
215 : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade),
216 m_d(new Private()),
217 m_requestedNodeSelection(nodeSelection),
218 m_updatesFacade(updatesFacade),
219 m_updatesEnabled(true)
220{
222
224}
225
227 : MoveStrokeStrategy(KisNodeSelectionRecipe(nodes), updatesFacade, undoFacade)
228{
229}
230
234
236 : QObject(),
238 m_d(new Private()),
239 m_requestedNodeSelection(rhs.m_requestedNodeSelection, lod),
240 m_nodes(rhs.m_nodes),
241 m_blacklistedNodes(rhs.m_blacklistedNodes),
242 m_updatesFacade(rhs.m_updatesFacade),
243 m_finalOffset(rhs.m_finalOffset),
244 m_dirtyRects(rhs.m_dirtyRects),
245 m_updatesEnabled(rhs.m_updatesEnabled)
246{
247 KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->strategy.empty());
248}
249
251{
257 if (m_updatesEnabled) {
259
260 if (!m_nodes.isEmpty()) {
262 }
263
264 KritaUtils::filterContainer<KisNodeList>(m_nodes, [this](KisNodeSP node) {
269 const bool isEmptyFilterMask = node->inherits("KisFilterMask") && node->paintDevice()
270 && node->paintDevice()->nonDefaultPixelArea().isEmpty();
271
272 return !isEmptyFilterMask &&
274 node->isEditable(true);
275 });
276 Q_FOREACH(KisNodeSP subtree, m_nodes) {
278 subtree,
279 [this](KisNodeSP node) {
281 !node->isEditable(false) ||
282 (dynamic_cast<KisTransformMask*>(node.data()) &&
284
285 m_blacklistedNodes.insert(node);
286 }
287 });
288 }
289
290 if (m_sharedNodes) {
291 *m_sharedNodes = std::make_pair(m_nodes, m_blacklistedNodes);
292 }
293 } else {
296 }
297
298 if (m_nodes.isEmpty()) {
299 Q_EMIT sigStrokeStartedEmpty();
300 return;
301 }
302
304
305 KritaUtils::addJobBarrier(jobs, [this]() {
306 Q_FOREACH(KisNodeSP node, m_nodes) {
308 }
309 });
310
311 KritaUtils::addJobBarrier(jobs, [this]() {
312 Q_FOREACH(KisNodeSP node, m_nodes) {
314 }
315 });
316
317 KritaUtils::addJobBarrier(jobs, [this]() {
318 QRect handlesRect;
319
324 [&handlesRect, this] (KisNodeSP node) {
325 if (node->inherits("KisFilterMask") && m_nodes.contains(node)) {
332 if (node->paintDevice()) {
333 handlesRect |= node->paintDevice()->nonDefaultPixelArea();
334 }
335 } else {
336 handlesRect |= node->projectionPlane()->tightUserVisibleBounds();
337 }
338 });
339
341
342 Q_FOREACH(KisNodeSP node, m_nodes) {
343 if (node->hasEditablePaintDevice()) {
344 KUndo2Command *autoKeyframeCommand =
347 if (autoKeyframeCommand) {
349 }
350 } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) {
352
353 if (maskAnimated) {
355 }
356 }
357 }
358
363 [this] (KisNodeSP node) {
364 if (dynamic_cast<KisTransformMask*>(node.data())) {
365 m_d->strategy.emplace(node, new MoveTransformMaskStrategy(node));
366 } else if (node->paintDevice()) {
367 m_d->strategy.emplace(node, new MovePaintableNodeStrategy(node));
368 } else {
369 m_d->strategy.emplace(node, new MoveNormalNodeStrategy(node));
370 }
371 });
372
373 if (m_updatesEnabled) {
374 KisLodTransform t(m_nodes.first()->image()->currentLevelOfDetail());
375 handlesRect = t.mapInverted(handlesRect);
376
377 Q_EMIT this->sigHandlesRectCalculated(handlesRect);
378 }
379
380 m_updateTimer.start();
381 });
382
383 runnableJobsInterface()->addRunnableJobs(jobs);
384}
385
387{
388 Q_FOREACH (KisNodeSP node, m_nodes) {
389 KUndo2Command *updateCommand =
390 new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
391
392 recursiveApplyNodes({node}, [this, updateCommand](KisNodeSP node) {
393 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strategy.find(node) != m_d->strategy.end());
394
395 MoveNodeStrategyBase *strategy = m_d->strategy[node].get();
396
397 strategy->finishMove(updateCommand);
398 });
399
400 notifyCommandDone(KUndo2CommandSP(updateCommand),
403 }
404
405 if (!m_updatesEnabled) {
406 Q_FOREACH (KisNodeSP node, m_nodes) {
408 }
409 }
410
412}
413
415{
416 if (!m_nodes.isEmpty()) {
417 m_finalOffset = QPoint();
418 m_hasPostponedJob = true;
419
421
423 Q_FOREACH (KisNodeSP node, m_nodes) {
424 QRect dirtyRect;
425
426 recursiveApplyNodes({node},
427 [this, &dirtyRect] (KisNodeSP node) {
428 MoveNodeStrategyBase *strategy =
429 m_d->strategy[node].get();
431
432 dirtyRect |= strategy->cancelMove();
433 });
434
435 m_dirtyRects[node] |= dirtyRect;
436
440 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
441 }
442 });
443
445 }
446
448}
449
451{
452 if (!m_hasPostponedJob) return;
453
454 if (forceUpdate ||
455 (m_updateTimer.elapsed() > m_updateInterval &&
457
458 addMutatedJob(new BarrierUpdateData(forceUpdate));
459 }
460}
461
463{
464 if (PickLayerData *pickData = dynamic_cast<PickLayerData*>(data)) {
466 clone.pickPoint = pickData->pos;
468 return;
469 }
470
471 Data *d = dynamic_cast<Data*>(data);
472
473 if (!m_nodes.isEmpty() && d) {
478 m_finalOffset = d->offset;
479 m_hasPostponedJob = true;
480 tryPostUpdateJob(false);
481
482 } else if (BarrierUpdateData *barrierData =
483 dynamic_cast<BarrierUpdateData*>(data)) {
484
485 doCanvasUpdate(barrierData->forceUpdate);
486
487 } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData =
489
490 tryPostUpdateJob(updateData->forceUpdate);
491
492 } else {
494 }
495}
496
498{
499 if (!forceUpdate &&
500 (m_updateTimer.elapsed() < m_updateInterval ||
502
503 return;
504 }
505
506 if (!m_hasPostponedJob) return;
507
508 Q_FOREACH (KisNodeSP node, m_nodes) {
509 QRect dirtyRect;
510
511 recursiveApplyNodes({node},
512 [this, &dirtyRect] (KisNodeSP node) {
513 MoveNodeStrategyBase *strategy =
514 m_d->strategy[node].get();
516
517 dirtyRect |= strategy->moveNode(m_finalOffset);
518 });
519
520 m_dirtyRects[node] |= dirtyRect;
521
522 if (m_updatesEnabled) {
523 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
524 }
525 }
526
527 m_hasPostponedJob = false;
528 m_updateTimer.restart();
529}
530
535
537{
538 return
540 subtree,
541 [](KisNodeSP node) -> bool {
542 return !node->supportsLodMoves();
543 });
544}
545
546
548{
549 KisNodeList nodesToCheck;
550
553 } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){
560 }
561
562 Q_FOREACH (KisNodeSP node, nodesToCheck) {
563 if (!checkSupportsLodMoves(node)) return 0;
564 }
565
566 MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail);
567 connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect)));
568 connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty()));
569 connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&)));
570 this->setUpdatesEnabled(false);
571 m_sharedNodes.reset(new std::pair<KisNodeList, QSet<KisNodeSP>>());
573 return clone;
574}
575
577 : KisStrokeJobData(SEQUENTIAL, NORMAL),
578 offset(_offset)
579{
580}
581
583{
584 return new Data(*this, levelOfDetail);
585}
586
588 : KisStrokeJobData(rhs)
589{
590 KisLodTransform t(levelOfDetail);
591 offset = t.map(rhs.offset);
592}
593
595 : KisStrokeJobData(SEQUENTIAL, NORMAL),
596 pos(_pos)
597{
598}
599
601 return new PickLayerData(*this, levelOfDetail);
602}
603
605 : KisStrokeJobData(rhs)
606{
607 KisLodTransform t(levelOfDetail);
608 pos = t.map(rhs.pos);
609}
610
612 : KisAsynchronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, EXCLUSIVE)
613{}
614
616 return new BarrierUpdateData(*this, levelOfDetail);
617}
618
float value(const T *src, size_t ch)
virtual void redo()
QRectF mapInverted(const QRectF &rc) const
KisPaintInformation map(KisPaintInformation pi) const
static void tryNotifySelection(KisNodeSP node)
KisNodeList selectNodesToProcess() const
QRect nonDefaultPixelArea() const
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
void runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
void doStrokeCallback(KisStrokeJobData *data) override
void addMutatedJob(KisStrokeJobData *data)
KUndo2Command * endAndTake()
virtual bool hasUpdatesRunning() const =0
void refreshGraphAsync(KisNodeSP root=nullptr, KisProjectionUpdateFlags flags=KisProjectionUpdateFlag::None)
KisStrokeJobData * createLodClone(int levelOfDetail) override
KisStrokeJobData * createLodClone(int levelOfDetail) override
void doStrokeCallback(KisStrokeJobData *data) override
KisNodeSelectionRecipe m_requestedNodeSelection
MoveStrokeStrategy(KisNodeSelectionRecipe nodeSelection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade)
void sigStrokeStartedEmpty()
KisUpdatesFacade * m_updatesFacade
void initStrokeCallback() override
void recursiveApplyNodes(KisNodeList nodes, Functor &&func)
void cancelStrokeCallback() override
QHash< KisNodeSP, QRect > m_dirtyRects
void sigLayersPicked(const KisNodeList &nodes)
void setUpdatesEnabled(bool value)
QScopedPointer< Private > m_d
void doCanvasUpdate(bool forceUpdate=false)
void finishStrokeCallback() override
void sigHandlesRectCalculated(const QRect &handlesRect)
QSet< KisNodeSP > m_blacklistedNodes
QSharedPointer< std::pair< KisNodeList, QSet< KisNodeSP > > > m_sharedNodes
void tryPostUpdateJob(bool forceUpdate)
KisStrokeStrategy * createLodClone(int levelOfDetail) override
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
QSharedPointer< T > toQShared(T *ptr)
QSharedPointer< KUndo2Command > KUndo2CommandSP
Definition kis_types.h:262
KUndo2MagicString kundo2_i18n(const char *text)
bool checkSupportsLodMoves(KisNodeSP subtree)
KUndo2Command * tryAutoCreateDuplicatedFrame(KisPaintDeviceSP device, AutoCreateKeyframeFlags flags)
create a new duplicated keyframe if auto-keyframe mode is on
bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes)
KisNodeSP recursiveFindNode(KisNodeSP node, std::function< bool(KisNodeSP)> func)
KisNodeList sortAndFilterMergeableInternalNodes(KisNodeList nodes, bool allowMasks)
KisNodeSP findRoot(KisNodeSP node)
void recursiveApplyNodes(NodePointer node, Functor func)
void forceAllHiddenOriginalsUpdate(KisNodeSP root)
bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents)
void forceAllDelayedNodesUpdate(KisNodeSP root)
void addJobBarrier(QVector< Job * > &jobs, Func func)
void addJobBarrierExclusive(QVector< Job * > &jobs, Func func)
bool isEditable(bool checkVisibility=true) const
virtual qint32 y() const
virtual void setX(qint32)
virtual void setY(qint32)
virtual qint32 x() const
virtual KisPaintDeviceSP paintDevice() const =0
bool hasEditablePaintDevice() const
virtual KisAbstractProjectionPlaneSP projectionPlane() const
Definition kis_node.cpp:240
KisTransformMaskParamsInterfaceSP transformParams() const
virtual void finishMove(KUndo2Command *parentCommand)=0
virtual QRect moveNode(const QPoint &offset)=0
QRect moveNodeCommon(const QPoint &offset)
virtual QRect cancelMove()=0
MoveNodeStrategyBase(KisNodeSP node)
void finishMove(KUndo2Command *parentCommand) override
MoveNormalNodeStrategy(KisNodeSP node)
QRect moveNode(const QPoint &offset) override
void finishMove(KUndo2Command *parentCommand) override
QRect moveNode(const QPoint &offset) override
KisStrokeJobData * createLodClone(int levelOfDetail) override
std::unordered_map< KisNodeSP, std::unique_ptr< MoveNodeStrategyBase > > strategy
QScopedPointer< KUndo2Command > m_undoCommand
QRect moveNode(const QPoint &offset) override
void finishMove(KUndo2Command *parentCommand) override