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 // TODO: check isolation
273 return !isEmptyFilterMask &&
275 node->isEditable(true);
276 });
277 Q_FOREACH(KisNodeSP subtree, m_nodes) {
279 subtree,
280 [this](KisNodeSP node) {
282 !node->isEditable(false) ||
283 (dynamic_cast<KisTransformMask*>(node.data()) &&
285
286 m_blacklistedNodes.insert(node);
287 }
288 });
289 }
290
291 if (m_sharedNodes) {
292 *m_sharedNodes = std::make_pair(m_nodes, m_blacklistedNodes);
293 }
294 } else {
297 }
298
299 if (m_nodes.isEmpty()) {
300 Q_EMIT sigStrokeStartedEmpty();
301 return;
302 }
303
305
306 KritaUtils::addJobBarrier(jobs, [this]() {
307 Q_FOREACH(KisNodeSP node, m_nodes) {
309 }
310 });
311
312 KritaUtils::addJobBarrier(jobs, [this]() {
313 Q_FOREACH(KisNodeSP node, m_nodes) {
315 }
316 });
317
318 KritaUtils::addJobBarrier(jobs, [this]() {
319 QRect handlesRect;
320
325 [&handlesRect, this] (KisNodeSP node) {
326 if (node->inherits("KisFilterMask") && m_nodes.contains(node)) {
333 if (node->paintDevice()) {
334 handlesRect |= node->paintDevice()->nonDefaultPixelArea();
335 }
336 } else {
337 handlesRect |= node->projectionPlane()->tightUserVisibleBounds();
338 }
339 });
340
342
343 Q_FOREACH(KisNodeSP node, m_nodes) {
344 if (node->hasEditablePaintDevice()) {
345 KUndo2Command *autoKeyframeCommand =
348 if (autoKeyframeCommand) {
350 }
351 } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) {
353
354 if (maskAnimated) {
356 }
357 }
358 }
359
364 [this] (KisNodeSP node) {
365 if (dynamic_cast<KisTransformMask*>(node.data())) {
366 m_d->strategy.emplace(node, new MoveTransformMaskStrategy(node));
367 } else if (node->paintDevice()) {
368 m_d->strategy.emplace(node, new MovePaintableNodeStrategy(node));
369 } else {
370 m_d->strategy.emplace(node, new MoveNormalNodeStrategy(node));
371 }
372 });
373
374 if (m_updatesEnabled) {
375 KisLodTransform t(m_nodes.first()->image()->currentLevelOfDetail());
376 handlesRect = t.mapInverted(handlesRect);
377
378 Q_EMIT this->sigHandlesRectCalculated(handlesRect);
379 }
380
381 m_updateTimer.start();
382 });
383
384 runnableJobsInterface()->addRunnableJobs(jobs);
385}
386
388{
389 Q_FOREACH (KisNodeSP node, m_nodes) {
390 KUndo2Command *updateCommand =
391 new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
392
393 recursiveApplyNodes({node}, [this, updateCommand](KisNodeSP node) {
394 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strategy.find(node) != m_d->strategy.end());
395
396 MoveNodeStrategyBase *strategy = m_d->strategy[node].get();
397
398 strategy->finishMove(updateCommand);
399 });
400
401 notifyCommandDone(KUndo2CommandSP(updateCommand),
404 }
405
406 if (!m_updatesEnabled) {
407 Q_FOREACH (KisNodeSP node, m_nodes) {
409 }
410 }
411
413}
414
416{
417 if (!m_nodes.isEmpty()) {
418 m_finalOffset = QPoint();
419 m_hasPostponedJob = true;
420
422
424 Q_FOREACH (KisNodeSP node, m_nodes) {
425 QRect dirtyRect;
426
427 recursiveApplyNodes({node},
428 [this, &dirtyRect] (KisNodeSP node) {
429 MoveNodeStrategyBase *strategy =
430 m_d->strategy[node].get();
432
433 dirtyRect |= strategy->cancelMove();
434 });
435
436 m_dirtyRects[node] |= dirtyRect;
437
441 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
442 }
443 });
444
446 }
447
449}
450
452{
453 if (!m_hasPostponedJob) return;
454
455 if (forceUpdate ||
456 (m_updateTimer.elapsed() > m_updateInterval &&
458
459 addMutatedJob(new BarrierUpdateData(forceUpdate));
460 }
461}
462
464{
465 if (PickLayerData *pickData = dynamic_cast<PickLayerData*>(data)) {
467 clone.pickPoint = pickData->pos;
469 return;
470 }
471
472 Data *d = dynamic_cast<Data*>(data);
473
474 if (!m_nodes.isEmpty() && d) {
479 m_finalOffset = d->offset;
480 m_hasPostponedJob = true;
481 tryPostUpdateJob(false);
482
483 } else if (BarrierUpdateData *barrierData =
484 dynamic_cast<BarrierUpdateData*>(data)) {
485
486 doCanvasUpdate(barrierData->forceUpdate);
487
488 } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData =
490
491 tryPostUpdateJob(updateData->forceUpdate);
492
493 } else {
495 }
496}
497
499{
500 if (!forceUpdate &&
501 (m_updateTimer.elapsed() < m_updateInterval ||
503
504 return;
505 }
506
507 if (!m_hasPostponedJob) return;
508
509 Q_FOREACH (KisNodeSP node, m_nodes) {
510 QRect dirtyRect;
511
512 recursiveApplyNodes({node},
513 [this, &dirtyRect] (KisNodeSP node) {
514 MoveNodeStrategyBase *strategy =
515 m_d->strategy[node].get();
517
518 dirtyRect |= strategy->moveNode(m_finalOffset);
519 });
520
521 m_dirtyRects[node] |= dirtyRect;
522
523 if (m_updatesEnabled) {
524 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
525 }
526 }
527
528 m_hasPostponedJob = false;
529 m_updateTimer.restart();
530}
531
536
538{
539 return
541 subtree,
542 [](KisNodeSP node) -> bool {
543 return !node->supportsLodMoves();
544 });
545}
546
547
549{
550 KisNodeList nodesToCheck;
551
554 } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){
561 }
562
563 Q_FOREACH (KisNodeSP node, nodesToCheck) {
564 if (!checkSupportsLodMoves(node)) return 0;
565 }
566
567 MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail);
568 connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect)));
569 connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty()));
570 connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&)));
571 this->setUpdatesEnabled(false);
572 m_sharedNodes.reset(new std::pair<KisNodeList, QSet<KisNodeSP>>());
574 return clone;
575}
576
578 : KisStrokeJobData(SEQUENTIAL, NORMAL),
579 offset(_offset)
580{
581}
582
584{
585 return new Data(*this, levelOfDetail);
586}
587
589 : KisStrokeJobData(rhs)
590{
591 KisLodTransform t(levelOfDetail);
592 offset = t.map(rhs.offset);
593}
594
596 : KisStrokeJobData(SEQUENTIAL, NORMAL),
597 pos(_pos)
598{
599}
600
602 return new PickLayerData(*this, levelOfDetail);
603}
604
606 : KisStrokeJobData(rhs)
607{
608 KisLodTransform t(levelOfDetail);
609 pos = t.map(rhs.pos);
610}
611
613 : KisAsynchronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, EXCLUSIVE)
614{}
615
617 return new BarrierUpdateData(*this, levelOfDetail);
618}
619
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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