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"
32
33/* MoveNodeStrategyBase and descendants
34 *
35 * A set of strategies that define how to actually move
36 * nodes of different types. Some nodes should be moved
37 * with KisNodeMoveCommand2, others with KisTransaction,
38 * transform masks with their own command.
39 */
40
42{
44 : m_node(node),
45 m_initialOffset(node->x(), node->y())
46 {
47 }
48
50
51 virtual QRect moveNode(const QPoint &offset) = 0;
52 virtual void finishMove(KUndo2Command *parentCommand) = 0;
53 virtual QRect cancelMove() = 0;
54
55protected:
56 QRect moveNodeCommon(const QPoint &offset) {
57 const QPoint newOffset = m_initialOffset + offset;
58
59 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
60
65 QPoint currentOffset(m_node->x(), m_node->y());
66 dirtyRect |= dirtyRect.translated(newOffset - currentOffset);
67
68 m_node->setX(newOffset.x());
69 m_node->setY(newOffset.y());
70
72 return dirtyRect;
73 }
74
75protected:
78};
79
81{
86
87 QRect moveNode(const QPoint &offset) override {
88 return moveNodeCommon(offset);
89 }
90
91 void finishMove(KUndo2Command *parentCommand) override {
92 const QPoint nodeOffset(m_node->x(), m_node->y());
93 new KisNodeMoveCommand2(m_node, m_initialOffset, nodeOffset, parentCommand);
94 }
95
96 QRect cancelMove() override {
97 return moveNode(QPoint());
98 }
99
100};
101
103{
108
109 QRect moveNode(const QPoint &offset) override {
110 QScopedPointer<KUndo2Command> cmd;
111 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
112
113 KisTransformMask *mask = dynamic_cast<KisTransformMask*>(m_node.data());
115
117 KisTransformMaskParamsInterfaceSP params = oldParams->clone();
118 params->translateDstSpace(offset - m_currentOffset);
119
120 cmd.reset(new KisSimpleModifyTransformMaskCommand(mask, params));
121 cmd->redo();
122
123 if (m_undoCommand) {
124 const bool mergeResult = m_undoCommand->mergeWith(cmd.get());
125 KIS_SAFE_ASSERT_RECOVER_NOOP(mergeResult);
126 cmd.reset();
127 } else {
128 m_undoCommand.swap(cmd);
129 }
130
131 m_currentOffset = offset;
132
133 dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
134
135 return dirtyRect;
136 }
137
138 void finishMove(KUndo2Command *parentCommand) override {
140 cmd->addCommand(m_undoCommand.take());
141 }
142
143 QRect cancelMove() override {
144 return moveNode(QPoint());
145 }
146
147private:
149 QScopedPointer<KUndo2Command> m_undoCommand;
150};
151
153{
155 : MoveNodeStrategyBase(node),
156 m_transaction(node->paintDevice(), 0, -1, 0, KisTransaction::SuppressUpdates)
157 {
158 // TODO: disable updates in the transaction
159 }
160
161 QRect moveNode(const QPoint &offset) override {
162 return moveNodeCommon(offset);
163 }
164
165 void finishMove(KUndo2Command *parentCommand) override {
167
168 KUndo2Command *transactionCommand = m_transaction.endAndTake();
169 transactionCommand->redo();
170 cmd->addCommand(transactionCommand);
171 }
172
173 QRect cancelMove() override {
174 QRect dirtyRect = m_node->projectionPlane()->tightUserVisibleBounds();
175
177
178 dirtyRect |= m_node->projectionPlane()->tightUserVisibleBounds();
179
180 return dirtyRect;
181 }
182
183private:
185};
186
187/*******************************************************/
188/* MoveStrokeStrategy::Private */
189/*******************************************************/
190
192 std::unordered_map<KisNodeSP, std::unique_ptr<MoveNodeStrategyBase>> strategy;
193};
194
195template <typename Functor>
196void MoveStrokeStrategy::recursiveApplyNodes(const KisNodeList &nodes, Functor &&func) {
197 Q_FOREACH(KisNodeSP subtree, nodes) {
199 [&] (KisNodeSP node) {
200 if (!m_blacklistedNodes.contains(node)) {
201 func(node);
202 }
203 });
204 }
205}
206
207/*******************************************************/
208/* MoveStrokeStrategy */
209/*******************************************************/
210
212 KisUpdatesFacade *updatesFacade,
213 KisStrokeUndoFacade *undoFacade)
214 : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade),
215 m_d(new Private()),
216 m_requestedNodeSelection(nodeSelection),
217 m_updatesFacade(updatesFacade),
218 m_updatesEnabled(true)
219{
221
223}
224
226 : MoveStrokeStrategy(KisNodeSelectionRecipe(nodes), updatesFacade, undoFacade)
227{
228}
229
233
235 : QObject(),
237 m_d(new Private()),
238 m_requestedNodeSelection(rhs.m_requestedNodeSelection, lod),
239 m_nodes(rhs.m_nodes),
240 m_blacklistedNodes(rhs.m_blacklistedNodes),
241 m_updatesFacade(rhs.m_updatesFacade),
242 m_finalOffset(rhs.m_finalOffset),
243 m_dirtyRects(rhs.m_dirtyRects),
244 m_updatesEnabled(rhs.m_updatesEnabled)
245{
246 KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->strategy.empty());
247}
248
250{
256 if (m_updatesEnabled) {
258
259 if (!m_nodes.isEmpty()) {
261 }
262
263 KritaUtils::filterContainer<KisNodeList>(m_nodes, [this](KisNodeSP node) {
268 const bool isEmptyFilterMask = node->inherits("KisFilterMask") && node->paintDevice()
269 && node->paintDevice()->nonDefaultPixelArea().isEmpty();
270
271 return !isEmptyFilterMask &&
273 node->isEditable(true);
274 });
275 Q_FOREACH(KisNodeSP subtree, m_nodes) {
277 subtree,
278 [this](KisNodeSP node) {
280 !node->isEditable(false) ||
281 (dynamic_cast<KisTransformMask*>(node.data()) &&
283
284 m_blacklistedNodes.insert(node);
285 }
286 });
287 }
288
289 if (m_sharedNodes) {
290 *m_sharedNodes = std::make_pair(m_nodes, m_blacklistedNodes);
291 }
292 } else {
295 }
296
297 if (m_nodes.isEmpty()) {
298 Q_EMIT sigStrokeStartedEmpty();
299 return;
300 }
301
303
304 KritaUtils::addJobBarrier(jobs, [this]() {
305 Q_FOREACH(KisNodeSP node, m_nodes) {
307 }
308 });
309
310 KritaUtils::addJobBarrier(jobs, [this]() {
311 Q_FOREACH(KisNodeSP node, m_nodes) {
313 }
314 });
315
316 KritaUtils::addJobBarrier(jobs, [this]() {
317 QRect handlesRect;
318
323 [&handlesRect, this] (KisNodeSP node) {
324 if (node->inherits("KisFilterMask") && m_nodes.contains(node)) {
331 if (node->paintDevice()) {
332 handlesRect |= node->paintDevice()->nonDefaultPixelArea();
333 }
334 } else {
335 handlesRect |= node->projectionPlane()->tightUserVisibleBounds();
336 }
337 });
338
340
341 Q_FOREACH(KisNodeSP node, m_nodes) {
342 if (node->hasEditablePaintDevice()) {
343 KUndo2Command *autoKeyframeCommand =
346 if (autoKeyframeCommand) {
348 }
349 } else if (KisTransformMask *mask = dynamic_cast<KisTransformMask*>(node.data())) {
351
352 if (maskAnimated) {
354 }
355 }
356 }
357
362 [this] (KisNodeSP node) {
363 if (dynamic_cast<KisTransformMask*>(node.data())) {
364 m_d->strategy.emplace(node, new MoveTransformMaskStrategy(node));
365 } else if (node->paintDevice()) {
366 m_d->strategy.emplace(node, new MovePaintableNodeStrategy(node));
367 } else {
368 m_d->strategy.emplace(node, new MoveNormalNodeStrategy(node));
369 }
370 });
371
372 if (m_updatesEnabled) {
373 KisLodTransform t(m_nodes.first()->image()->currentLevelOfDetail());
374 handlesRect = t.mapInverted(handlesRect);
375
376 Q_EMIT this->sigHandlesRectCalculated(handlesRect);
377 }
378
379 m_updateTimer.start();
380 });
381
382 runnableJobsInterface()->addRunnableJobs(jobs);
383}
384
386{
387 Q_FOREACH (KisNodeSP node, m_nodes) {
388 KUndo2Command *updateCommand =
389 new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
390
391 recursiveApplyNodes({node}, [this, updateCommand](KisNodeSP node) {
392 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strategy.find(node) != m_d->strategy.end());
393
394 MoveNodeStrategyBase *strategy = m_d->strategy[node].get();
395
396 strategy->finishMove(updateCommand);
397 });
398
399 notifyCommandDone(KUndo2CommandSP(updateCommand),
402 }
403
404 if (!m_updatesEnabled) {
405 Q_FOREACH (KisNodeSP node, m_nodes) {
407 }
408 }
409
411}
412
414{
415 if (!m_nodes.isEmpty()) {
416 m_finalOffset = QPoint();
417 m_hasPostponedJob = true;
418
420
422 Q_FOREACH (KisNodeSP node, m_nodes) {
423 QRect dirtyRect;
424
425 recursiveApplyNodes({node},
426 [this, &dirtyRect] (KisNodeSP node) {
427 MoveNodeStrategyBase *strategy =
428 m_d->strategy[node].get();
430
431 dirtyRect |= strategy->cancelMove();
432 });
433
434 m_dirtyRects[node] |= dirtyRect;
435
439 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
440 }
441 });
442
444 }
445
447}
448
450{
451 if (!m_hasPostponedJob) return;
452
453 if (forceUpdate ||
454 (m_updateTimer.elapsed() > m_updateInterval &&
456
457 addMutatedJob(new BarrierUpdateData(forceUpdate));
458 }
459}
460
462{
463 if (PickLayerData *pickData = dynamic_cast<PickLayerData*>(data)) {
465 clone.pickPoint = pickData->pos;
467 return;
468 }
469
470 Data *d = dynamic_cast<Data*>(data);
471
472 if (!m_nodes.isEmpty() && d) {
477 m_finalOffset = d->offset;
478 m_hasPostponedJob = true;
479 tryPostUpdateJob(false);
480
481 } else if (BarrierUpdateData *barrierData =
482 dynamic_cast<BarrierUpdateData*>(data)) {
483
484 doCanvasUpdate(barrierData->forceUpdate);
485
486 } else if (KisAsynchronousStrokeUpdateHelper::UpdateData *updateData =
488
489 tryPostUpdateJob(updateData->forceUpdate);
490
491 } else {
493 }
494}
495
497{
498 if (!forceUpdate &&
499 (m_updateTimer.elapsed() < m_updateInterval ||
501
502 return;
503 }
504
505 if (!m_hasPostponedJob) return;
506
507 Q_FOREACH (KisNodeSP node, m_nodes) {
508 QRect dirtyRect;
509
510 recursiveApplyNodes({node},
511 [this, &dirtyRect] (KisNodeSP node) {
512 MoveNodeStrategyBase *strategy =
513 m_d->strategy[node].get();
515
516 dirtyRect |= strategy->moveNode(m_finalOffset);
517 });
518
519 m_dirtyRects[node] |= dirtyRect;
520
521 if (m_updatesEnabled) {
522 m_updatesFacade->refreshGraphAsync(node, dirtyRect);
523 }
524 }
525
526 m_hasPostponedJob = false;
527 m_updateTimer.restart();
528}
529
534
536{
537 return
539 subtree,
540 [](KisNodeSP node) -> bool {
541 return !node->supportsLodMoves();
542 });
543}
544
545
547{
548 KisNodeList nodesToCheck;
549
552 } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){
559 }
560
561 Q_FOREACH (KisNodeSP node, nodesToCheck) {
562 if (!checkSupportsLodMoves(node)) return 0;
563 }
564
565 MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail);
566 connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect)));
567 connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty()));
568 connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&)));
569 this->setUpdatesEnabled(false);
570 m_sharedNodes.reset(new std::pair<KisNodeList, QSet<KisNodeSP>>());
572 return clone;
573}
574
576 : KisStrokeJobData(SEQUENTIAL, NORMAL),
577 offset(_offset)
578{
579}
580
582{
583 return new Data(*this, levelOfDetail);
584}
585
587 : KisStrokeJobData(rhs)
588{
589 KisLodTransform t(levelOfDetail);
590 offset = t.map(rhs.offset);
591}
592
594 : KisStrokeJobData(SEQUENTIAL, NORMAL),
595 pos(_pos)
596{
597}
598
600 return new PickLayerData(*this, levelOfDetail);
601}
602
604 : KisStrokeJobData(rhs)
605{
606 KisLodTransform t(levelOfDetail);
607 pos = t.map(rhs.pos);
608}
609
611 : KisAsynchronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, EXCLUSIVE)
612{}
613
615 return new BarrierUpdateData(*this, levelOfDetail);
616}
617
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 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)
void recursiveApplyNodes(const KisNodeList &nodes, Functor &&func)
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