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