1 /*
2  *  Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "move_stroke_strategy.h"
20 
21 #include <klocalizedstring.h>
22 #include "kis_image_interfaces.h"
23 #include "kis_node.h"
24 #include "commands_new/kis_update_command.h"
25 #include "commands_new/kis_node_move_command2.h"
26 #include "kis_layer_utils.h"
27 #include "krita_utils.h"
28 
29 #include "KisRunnableStrokeJobData.h"
30 #include "KisRunnableStrokeJobUtils.h"
31 #include "KisRunnableStrokeJobsInterface.h"
32 #include "kis_abstract_projection_plane.h"
33 #include "kis_image.h"
34 
35 
MoveStrokeStrategy(KisNodeSelectionRecipe nodeSelection,KisUpdatesFacade * updatesFacade,KisStrokeUndoFacade * undoFacade)36 MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSelectionRecipe nodeSelection,
37                                        KisUpdatesFacade *updatesFacade,
38                                        KisStrokeUndoFacade *undoFacade)
39     : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade),
40       m_requestedNodeSelection(nodeSelection),
41       m_updatesFacade(updatesFacade),
42       m_updatesEnabled(true)
43 {
44     setSupportsWrapAroundMode(true);
45 
46     enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER);
47 }
48 
MoveStrokeStrategy(KisNodeList nodes,KisUpdatesFacade * updatesFacade,KisStrokeUndoFacade * undoFacade)49 MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade)
50     : MoveStrokeStrategy(KisNodeSelectionRecipe(nodes), updatesFacade, undoFacade)
51 {
52 }
53 
MoveStrokeStrategy(const MoveStrokeStrategy & rhs,int lod)54 MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs, int lod)
55     : QObject(),
56       KisStrokeStrategyUndoCommandBased(rhs),
57       m_requestedNodeSelection(rhs.m_requestedNodeSelection, lod),
58       m_nodes(rhs.m_nodes),
59       m_blacklistedNodes(rhs.m_blacklistedNodes),
60       m_updatesFacade(rhs.m_updatesFacade),
61       m_finalOffset(rhs.m_finalOffset),
62       m_dirtyRect(rhs.m_dirtyRect),
63       m_dirtyRects(rhs.m_dirtyRects),
64       m_updatesEnabled(rhs.m_updatesEnabled)
65 {
66 }
saveInitialNodeOffsets(KisNodeSP node)67 void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node)
68 {
69     if (!m_blacklistedNodes.contains(node)) {
70         m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y()));
71     }
72 
73     KisNodeSP child = node->firstChild();
74     while(child) {
75         saveInitialNodeOffsets(child);
76         child = child->nextSibling();
77     }
78 }
79 
initStrokeCallback()80 void MoveStrokeStrategy::initStrokeCallback()
81 {
82     /**
83      * Our LodN moght have already prepared the list of nodes for us,
84      * so we should reuse it to avoid different nodes to be moved in
85      * LodN and Lod0 modes.
86      */
87     if (m_updatesEnabled) {
88         m_nodes = m_requestedNodeSelection.selectNodesToProcess();
89 
90         if (!m_nodes.isEmpty()) {
91             m_nodes = KisLayerUtils::sortAndFilterMergableInternalNodes(m_nodes, true);
92         }
93 
94         KritaUtils::filterContainer<KisNodeList>(m_nodes,
95                                                  [this](KisNodeSP node) {
96             // TODO: check isolation
97             return
98                     !KisLayerUtils::checkIsCloneOf(node, m_nodes) &&
99                     node->isEditable(true);
100         });
101         Q_FOREACH(KisNodeSP subtree, m_nodes) {
102             KisLayerUtils::recursiveApplyNodes(
103                         subtree,
104                         [this](KisNodeSP node) {
105                 if (KisLayerUtils::checkIsCloneOf(node, m_nodes) ||
106                         !node->isEditable(false)) {
107 
108                     m_blacklistedNodes.insert(node);
109                 }
110             });
111         }
112 
113         if (m_sharedNodes) {
114             *m_sharedNodes = m_nodes;
115         }
116     } else {
117         KIS_SAFE_ASSERT_RECOVER_RETURN(m_sharedNodes);
118         m_nodes = *m_sharedNodes;
119     }
120 
121     if (m_nodes.isEmpty()) {
122         emit sigStrokeStartedEmpty();
123         return;
124     }
125 
126     QVector<KisRunnableStrokeJobData*> jobs;
127 
128     KritaUtils::addJobBarrier(jobs, [this]() {
129         Q_FOREACH(KisNodeSP node, m_nodes) {
130             KisLayerUtils::forceAllHiddenOriginalsUpdate(node);
131         }
132     });
133 
134     KritaUtils::addJobBarrier(jobs, [this]() {
135         Q_FOREACH(KisNodeSP node, m_nodes) {
136             KisLayerUtils::forceAllDelayedNodesUpdate(node);
137         }
138     });
139 
140     KritaUtils::addJobBarrier(jobs, [this]() {
141         QRect handlesRect;
142 
143         Q_FOREACH(KisNodeSP node, m_nodes) {
144             saveInitialNodeOffsets(node);
145             handlesRect |= KisLayerUtils::recursiveTightNodeVisibleBounds(node);
146         }
147 
148         KisStrokeStrategyUndoCommandBased::initStrokeCallback();
149 
150         if (m_updatesEnabled) {
151             KisLodTransform t(m_nodes.first()->image()->currentLevelOfDetail());
152             handlesRect = t.mapInverted(handlesRect);
153 
154             emit this->sigHandlesRectCalculated(handlesRect);
155         }
156 
157         m_updateTimer.start();
158     });
159 
160     runnableJobsInterface()->addRunnableJobs(jobs);
161 }
162 
finishStrokeCallback()163 void MoveStrokeStrategy::finishStrokeCallback()
164 {
165     Q_FOREACH (KisNodeSP node, m_nodes) {
166         KUndo2Command *updateCommand =
167             new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
168 
169         addMoveCommands(node, updateCommand);
170 
171         notifyCommandDone(KUndo2CommandSP(updateCommand),
172                           KisStrokeJobData::SEQUENTIAL,
173                           KisStrokeJobData::EXCLUSIVE);
174     }
175 
176     if (!m_updatesEnabled) {
177         Q_FOREACH (KisNodeSP node, m_nodes) {
178             m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]);
179         }
180     }
181 
182     KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
183 }
184 
cancelStrokeCallback()185 void MoveStrokeStrategy::cancelStrokeCallback()
186 {
187     if (!m_nodes.isEmpty()) {
188         m_finalOffset = QPoint();
189         m_hasPostponedJob = true;
190         tryPostUpdateJob(true);
191     }
192 
193     KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
194 }
195 
tryPostUpdateJob(bool forceUpdate)196 void MoveStrokeStrategy::tryPostUpdateJob(bool forceUpdate)
197 {
198     if (!m_hasPostponedJob) return;
199 
200     if (forceUpdate ||
201         (m_updateTimer.elapsed() > m_updateInterval &&
202          !m_updatesFacade->hasUpdatesRunning())) {
203 
204         addMutatedJob(new BarrierUpdateData(forceUpdate));
205     }
206 }
207 
doStrokeCallback(KisStrokeJobData * data)208 void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
209 {
210     if (PickLayerData *pickData = dynamic_cast<PickLayerData*>(data)) {
211         KisNodeSelectionRecipe clone = m_requestedNodeSelection;
212         clone.pickPoint = pickData->pos;
213         emit sigLayersPicked(clone.selectNodesToProcess());
214         return;
215     }
216 
217     Data *d = dynamic_cast<Data*>(data);
218 
219     if (!m_nodes.isEmpty() && d) {
220         /**
221          * NOTE: we do not care about threading here, because
222          * all our jobs are declared sequential
223          */
224         m_finalOffset = d->offset;
225         m_hasPostponedJob = true;
226         tryPostUpdateJob(false);
227 
228     } else if (BarrierUpdateData *barrierData =
229                dynamic_cast<BarrierUpdateData*>(data)) {
230 
231         doCanvasUpdate(barrierData->forceUpdate);
232 
233     } else if (KisAsyncronousStrokeUpdateHelper::UpdateData *updateData =
234                dynamic_cast<KisAsyncronousStrokeUpdateHelper::UpdateData*>(data)) {
235 
236         tryPostUpdateJob(updateData->forceUpdate);
237 
238     } else {
239         KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
240     }
241 }
242 
243 #include "kis_selection_mask.h"
244 #include "kis_selection.h"
245 
doCanvasUpdate(bool forceUpdate)246 void MoveStrokeStrategy::doCanvasUpdate(bool forceUpdate)
247 {
248     if (!forceUpdate &&
249             (m_updateTimer.elapsed() < m_updateInterval ||
250              m_updatesFacade->hasUpdatesRunning())) {
251 
252         return;
253     }
254 
255     if (!m_hasPostponedJob) return;
256 
257     Q_FOREACH (KisNodeSP node, m_nodes) {
258         QRect dirtyRect = moveNode(node, m_finalOffset);
259         m_dirtyRects[node] |= dirtyRect;
260 
261         if (m_updatesEnabled) {
262             m_updatesFacade->refreshGraphAsync(node, dirtyRect);
263         }
264 
265         if (KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(node.data())) {
266             Q_UNUSED(mask);
267             //mask->selection()->notifySelectionChanged();
268         }
269     }
270 
271     m_hasPostponedJob = false;
272     m_updateTimer.restart();
273 }
274 
moveNode(KisNodeSP node,QPoint offset)275 QRect MoveStrokeStrategy::moveNode(KisNodeSP node, QPoint offset)
276 {
277     QRect dirtyRect;
278 
279     if (!m_blacklistedNodes.contains(node)) {
280         dirtyRect = node->projectionPlane()->tightUserVisibleBounds();
281         QPoint newOffset = m_initialNodeOffsets[node] + offset;
282 
283         /**
284          * Some layers, e.g. clones need an update to change extent(), so
285          * calculate the dirty rect manually
286          */
287         QPoint currentOffset(node->x(), node->y());
288         dirtyRect |= dirtyRect.translated(newOffset - currentOffset);
289 
290         node->setX(newOffset.x());
291         node->setY(newOffset.y());
292         KisNodeMoveCommand2::tryNotifySelection(node);
293     }
294 
295     KisNodeSP child = node->firstChild();
296     while(child) {
297         dirtyRect |= moveNode(child, offset);
298         child = child->nextSibling();
299     }
300 
301     return dirtyRect;
302 }
303 
addMoveCommands(KisNodeSP node,KUndo2Command * parent)304 void MoveStrokeStrategy::addMoveCommands(KisNodeSP node, KUndo2Command *parent)
305 {
306     if (!m_blacklistedNodes.contains(node)) {
307         QPoint nodeOffset(node->x(), node->y());
308         new KisNodeMoveCommand2(node, nodeOffset - m_finalOffset, nodeOffset, parent);
309     }
310 
311     KisNodeSP child = node->firstChild();
312     while(child) {
313         addMoveCommands(child, parent);
314         child = child->nextSibling();
315     }
316 }
317 
setUpdatesEnabled(bool value)318 void MoveStrokeStrategy::setUpdatesEnabled(bool value)
319 {
320     m_updatesEnabled = value;
321 }
322 
checkSupportsLodMoves(KisNodeSP subtree)323 bool checkSupportsLodMoves(KisNodeSP subtree)
324 {
325     return
326         !KisLayerUtils::recursiveFindNode(
327             subtree,
328             [](KisNodeSP node) -> bool {
329                 return !node->supportsLodMoves();
330             });
331 }
332 
333 
createLodClone(int levelOfDetail)334 KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail)
335 {
336     KisNodeList nodesToCheck;
337 
338     if (m_requestedNodeSelection.mode == KisNodeSelectionRecipe::SelectedLayer) {
339         nodesToCheck = m_requestedNodeSelection.selectedNodes;
340     } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){
341         /**
342          * Since this function is executed in the GUI thread, we cannot properly
343          * pick the layers. Therefore we should use pessimistic approach and
344          * check if there are non-lodn-capable nodes in the entire image.
345          */
346         nodesToCheck.append(KisLayerUtils::findRoot(m_requestedNodeSelection.selectedNodes.first()));
347     }
348 
349     Q_FOREACH (KisNodeSP node, nodesToCheck) {
350         if (!checkSupportsLodMoves(node)) return 0;
351     }
352 
353     MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail);
354     connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect)));
355     connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty()));
356     connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&)));
357     this->setUpdatesEnabled(false);
358     m_sharedNodes.reset(new KisNodeList());
359     clone->m_sharedNodes = m_sharedNodes;
360     return clone;
361 }
362 
Data(QPoint _offset)363 MoveStrokeStrategy::Data::Data(QPoint _offset)
364     : KisStrokeJobData(SEQUENTIAL, NORMAL),
365       offset(_offset)
366 {
367 }
368 
createLodClone(int levelOfDetail)369 KisStrokeJobData *MoveStrokeStrategy::Data::createLodClone(int levelOfDetail)
370 {
371     return new Data(*this, levelOfDetail);
372 }
373 
Data(const MoveStrokeStrategy::Data & rhs,int levelOfDetail)374 MoveStrokeStrategy::Data::Data(const MoveStrokeStrategy::Data &rhs, int levelOfDetail)
375     : KisStrokeJobData(rhs)
376 {
377     KisLodTransform t(levelOfDetail);
378     offset = t.map(rhs.offset);
379 }
380 
PickLayerData(QPoint _pos)381 MoveStrokeStrategy::PickLayerData::PickLayerData(QPoint _pos)
382     : KisStrokeJobData(SEQUENTIAL, NORMAL),
383       pos(_pos)
384 {
385 }
386 
createLodClone(int levelOfDetail)387 KisStrokeJobData *MoveStrokeStrategy::PickLayerData::createLodClone(int levelOfDetail) {
388     return new PickLayerData(*this, levelOfDetail);
389 }
390 
PickLayerData(const MoveStrokeStrategy::PickLayerData & rhs,int levelOfDetail)391 MoveStrokeStrategy::PickLayerData::PickLayerData(const MoveStrokeStrategy::PickLayerData &rhs, int levelOfDetail)
392     : KisStrokeJobData(rhs)
393 {
394     KisLodTransform t(levelOfDetail);
395     pos = t.map(rhs.pos);
396 }
397 
BarrierUpdateData(bool _forceUpdate)398 MoveStrokeStrategy::BarrierUpdateData::BarrierUpdateData(bool _forceUpdate)
399     : KisAsyncronousStrokeUpdateHelper::UpdateData(_forceUpdate, BARRIER, EXCLUSIVE)
400 {}
401 
createLodClone(int levelOfDetail)402 KisStrokeJobData *MoveStrokeStrategy::BarrierUpdateData::createLodClone(int levelOfDetail) {
403     return new BarrierUpdateData(*this, levelOfDetail);
404 }
405 
BarrierUpdateData(const MoveStrokeStrategy::BarrierUpdateData & rhs,int levelOfDetail)406 MoveStrokeStrategy::BarrierUpdateData::BarrierUpdateData(const MoveStrokeStrategy::BarrierUpdateData &rhs, int levelOfDetail)
407     : KisAsyncronousStrokeUpdateHelper::UpdateData(rhs, levelOfDetail)
408 {
409 }
410