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 "kis_processing_applicator.h"
20
21 #include "kis_image.h"
22 #include "kis_paint_layer.h"
23 #include "kis_node.h"
24 #include "kis_clone_layer.h"
25 #include "kis_processing_visitor.h"
26 #include "commands_new/kis_processing_command.h"
27 #include "kis_stroke_strategy_undo_command_based.h"
28 #include "kis_layer_utils.h"
29 #include "kis_command_utils.h"
30 #include "kis_time_range.h"
31 #include "kis_node.h"
32 #include "kis_image_signal_router.h"
33 #include "KisAsynchronouslyMergeableCommandInterface.h"
34 #include "kis_command_ids.h"
35
36 class DisableUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand, public KisAsynchronouslyMergeableCommandInterface
37 {
38 public:
DisableUIUpdatesCommand(KisImageWSP image,bool finalUpdate)39 DisableUIUpdatesCommand(KisImageWSP image,
40 bool finalUpdate)
41 : FlipFlopCommand(finalUpdate),
42 m_image(image)
43 {
44 }
45
partA()46 void partA() override {
47 m_image->disableUIUpdates();
48 }
49
partB()50 void partB() override {
51 m_image->enableUIUpdates();
52 }
53
id() const54 int id() const override {
55 return KisCommandUtils::DisableUIUpdatesCommandId;
56 }
57
mergeWith(const KUndo2Command * command)58 bool mergeWith(const KUndo2Command *command) override {
59 return canMergeWith(command);
60 }
61
canMergeWith(const KUndo2Command * command) const62 bool canMergeWith(const KUndo2Command *command) const override {
63 const DisableUIUpdatesCommand *other =
64 dynamic_cast<const DisableUIUpdatesCommand*>(command);
65
66 return other && other->m_image == m_image;
67 }
68
69 private:
70 KisImageWSP m_image;
71 };
72
73
74 class UpdateCommand : public KisCommandUtils::FlipFlopCommand, public KisAsynchronouslyMergeableCommandInterface
75 {
76 public:
UpdateCommand(KisImageWSP image,KisNodeSP node,KisProcessingApplicator::ProcessingFlags flags,State initialState,QSharedPointer<bool> sharedAllFramesToken)77 UpdateCommand(KisImageWSP image, KisNodeSP node,
78 KisProcessingApplicator::ProcessingFlags flags,
79 State initialState, QSharedPointer<bool> sharedAllFramesToken)
80 : FlipFlopCommand(initialState),
81 m_image(image),
82 m_node(node),
83 m_flags(flags),
84 m_sharedAllFramesToken(sharedAllFramesToken)
85 {
86 }
87
88 private:
partA()89 void partA() override {
90 /**
91 * We disable all non-centralized updates here. Everything
92 * should be done by this command's explicit updates.
93 *
94 * If you still need third-party updates work, please add a
95 * flag to the applicator.
96 */
97 m_image->disableDirtyRequests();
98 }
99
partB()100 void partB() override {
101 m_image->enableDirtyRequests();
102
103 if (*m_sharedAllFramesToken) {
104 KisLayerUtils::recursiveApplyNodes(m_image->root(), [](KisNodeSP node){
105 KisPaintLayer* paintLayer = qobject_cast<KisPaintLayer*>(node.data());
106 if (paintLayer && paintLayer->onionSkinEnabled()) {
107 paintLayer->flushOnionSkinCache();
108 }
109 });
110 }
111
112 m_image->root()->graphListener()->invalidateFrames(KisTimeRange::infinite(0), m_node->exactBounds());
113
114 if (!m_flags.testFlag(KisProcessingApplicator::NO_IMAGE_UPDATES)) {
115 if(m_flags.testFlag(KisProcessingApplicator::RECURSIVE)) {
116 m_image->refreshGraphAsync(m_node);
117 }
118
119 m_node->setDirty(m_image->bounds());
120
121 updateClones(m_node);
122 }
123 }
124
updateClones(KisNodeSP node)125 void updateClones(KisNodeSP node) {
126 // simple tail-recursive iteration
127
128 KisNodeSP prevNode = node->lastChild();
129 while(prevNode) {
130 updateClones(prevNode);
131 prevNode = prevNode->prevSibling();
132 }
133
134 KisLayer *layer = qobject_cast<KisLayer*>(m_node.data());
135 if(layer && layer->hasClones()) {
136 Q_FOREACH (KisCloneLayerSP clone, layer->registeredClones()) {
137 if(!clone) continue;
138
139 QPoint offset(clone->x(), clone->y());
140 QRegion dirtyRegion(m_image->bounds());
141 dirtyRegion -= m_image->bounds().translated(offset);
142
143 clone->setDirty(KisRegion::fromQRegion(dirtyRegion));
144 }
145 }
146 }
147
id() const148 int id() const override {
149 return KisCommandUtils::UpdateCommandId;
150 }
151
mergeWith(const KUndo2Command * command)152 bool mergeWith(const KUndo2Command *command) override {
153 return canMergeWith(command);
154 }
155
canMergeWith(const KUndo2Command * command) const156 bool canMergeWith(const KUndo2Command *command) const override {
157 const UpdateCommand *other =
158 dynamic_cast<const UpdateCommand*>(command);
159
160 return other &&
161 other->m_image == m_image &&
162 other->m_node == m_node &&
163 other->m_flags == m_flags &&
164 bool(other->m_sharedAllFramesToken) == bool(m_sharedAllFramesToken) &&
165 (!m_sharedAllFramesToken || *m_sharedAllFramesToken == *other->m_sharedAllFramesToken);
166 }
167
168 private:
169 KisImageWSP m_image;
170 KisNodeSP m_node;
171 KisProcessingApplicator::ProcessingFlags m_flags;
172 QSharedPointer<bool> m_sharedAllFramesToken;
173 };
174
175 class EmitImageSignalsCommand : public KisCommandUtils::FlipFlopCommand, public KisAsynchronouslyMergeableCommandInterface
176 {
177 public:
EmitImageSignalsCommand(KisImageWSP image,KisImageSignalVector emitSignals,bool finalUpdate)178 EmitImageSignalsCommand(KisImageWSP image,
179 KisImageSignalVector emitSignals,
180 bool finalUpdate)
181 : FlipFlopCommand(finalUpdate),
182 m_image(image),
183 m_emitSignals(emitSignals)
184 {
185 }
186
partB()187 void partB() override {
188 if (getState() == State::FINALIZING) {
189 doUpdate(m_emitSignals);
190 } else {
191 KisImageSignalVector reverseSignals;
192
193 KisImageSignalVector::iterator i = m_emitSignals.end();
194 while (i != m_emitSignals.begin()) {
195 --i;
196 reverseSignals.append(i->inverted());
197 }
198
199 doUpdate(reverseSignals);
200 }
201 }
202
id() const203 int id() const override {
204 return KisCommandUtils::EmitImageSignalsCommandId;
205 }
206
mergeWith(const KUndo2Command * command)207 bool mergeWith(const KUndo2Command *command) override {
208 return canMergeWith(command);
209 }
210
canMergeWith(const KUndo2Command * command) const211 bool canMergeWith(const KUndo2Command *command) const override {
212 const EmitImageSignalsCommand *other =
213 dynamic_cast<const EmitImageSignalsCommand*>(command);
214
215 return other &&
216 other->m_image == m_image;
217
218 // TODO: implement proper comparison for emitted signals
219 // other->m_emitSignals == m_emitSignals;
220 }
221
222 private:
doUpdate(KisImageSignalVector emitSignals)223 void doUpdate(KisImageSignalVector emitSignals) {
224 Q_FOREACH (KisImageSignalType type, emitSignals) {
225 m_image->signalRouter()->emitNotification(type);
226 }
227 }
228
229 private:
230 KisImageWSP m_image;
231 KisImageSignalVector m_emitSignals;
232 };
233
234
KisProcessingApplicator(KisImageWSP image,KisNodeSP node,ProcessingFlags flags,KisImageSignalVector emitSignals,const KUndo2MagicString & name,KUndo2CommandExtraData * extraData,int macroId)235 KisProcessingApplicator::KisProcessingApplicator(KisImageWSP image,
236 KisNodeSP node,
237 ProcessingFlags flags,
238 KisImageSignalVector emitSignals,
239 const KUndo2MagicString &name,
240 KUndo2CommandExtraData *extraData,
241 int macroId)
242 : m_image(image),
243 m_node(node),
244 m_flags(flags),
245 m_emitSignals(emitSignals),
246 m_finalSignalsEmitted(false),
247 m_sharedAllFramesToken(new bool(false))
248 {
249 KisStrokeStrategyUndoCommandBased *strategy =
250 new KisStrokeStrategyUndoCommandBased(name, false, m_image.data());
251
252 if (m_flags.testFlag(SUPPORTS_WRAPAROUND_MODE)) {
253 strategy->setSupportsWrapAroundMode(true);
254 }
255
256 if (extraData) {
257 strategy->setCommandExtraData(extraData);
258 }
259
260 strategy->setMacroId(macroId);
261
262 m_strokeId = m_image->startStroke(strategy);
263 if(!m_emitSignals.isEmpty()) {
264 applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, false), KisStrokeJobData::BARRIER);
265 }
266
267 if(m_flags.testFlag(NO_UI_UPDATES)) {
268 applyCommand(new DisableUIUpdatesCommand(m_image, false), KisStrokeJobData::BARRIER);
269 }
270
271 if (m_node) {
272 applyCommand(new UpdateCommand(m_image, m_node, m_flags,
273 UpdateCommand::INITIALIZING,
274 m_sharedAllFramesToken));
275 }
276 }
277
~KisProcessingApplicator()278 KisProcessingApplicator::~KisProcessingApplicator()
279 {
280 }
281
getStroke() const282 const KisStrokeId KisProcessingApplicator::getStroke() const
283 {
284 return m_strokeId;
285 }
286
applyVisitor(KisProcessingVisitorSP visitor,KisStrokeJobData::Sequentiality sequentiality,KisStrokeJobData::Exclusivity exclusivity)287 void KisProcessingApplicator::applyVisitor(KisProcessingVisitorSP visitor,
288 KisStrokeJobData::Sequentiality sequentiality,
289 KisStrokeJobData::Exclusivity exclusivity)
290 {
291 KUndo2Command *initCommand = visitor->createInitCommand();
292 if (initCommand) {
293 applyCommand(initCommand,
294 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
295 }
296
297 if(!m_flags.testFlag(RECURSIVE)) {
298 applyCommand(new KisProcessingCommand(visitor, m_node),
299 sequentiality, exclusivity);
300 }
301 else {
302 visitRecursively(m_node, visitor, sequentiality, exclusivity);
303 }
304 }
305
applyVisitorAllFrames(KisProcessingVisitorSP visitor,KisStrokeJobData::Sequentiality sequentiality,KisStrokeJobData::Exclusivity exclusivity)306 void KisProcessingApplicator::applyVisitorAllFrames(KisProcessingVisitorSP visitor,
307 KisStrokeJobData::Sequentiality sequentiality,
308 KisStrokeJobData::Exclusivity exclusivity)
309 {
310 *m_sharedAllFramesToken = true;
311
312 KUndo2Command *initCommand = visitor->createInitCommand();
313 if (initCommand) {
314 applyCommand(initCommand,
315 KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
316 }
317
318 KisLayerUtils::FrameJobs jobs;
319
320 // TODO: implement a nonrecursive case when !m_flags.testFlag(RECURSIVE)
321 // (such case is not yet used anywhere)
322 KIS_SAFE_ASSERT_RECOVER_NOOP(m_flags.testFlag(RECURSIVE));
323
324 KisLayerUtils::updateFrameJobsRecursive(&jobs, m_node);
325
326 if (jobs.isEmpty()) {
327 applyVisitor(visitor, sequentiality, exclusivity);
328 return;
329 }
330
331 KisLayerUtils::FrameJobs::const_iterator it = jobs.constBegin();
332 KisLayerUtils::FrameJobs::const_iterator end = jobs.constEnd();
333
334 KisLayerUtils::SwitchFrameCommand::SharedStorageSP switchFrameStorage(
335 new KisLayerUtils::SwitchFrameCommand::SharedStorage());
336
337 for (; it != end; ++it) {
338 const int frame = it.key();
339 const QSet<KisNodeSP> &nodes = it.value();
340
341 applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, false, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
342
343 foreach (KisNodeSP node, nodes) {
344 applyCommand(new KisProcessingCommand(visitor, node),
345 sequentiality, exclusivity);
346 }
347
348 applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, true, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
349 }
350 }
351
visitRecursively(KisNodeSP node,KisProcessingVisitorSP visitor,KisStrokeJobData::Sequentiality sequentiality,KisStrokeJobData::Exclusivity exclusivity)352 void KisProcessingApplicator::visitRecursively(KisNodeSP node,
353 KisProcessingVisitorSP visitor,
354 KisStrokeJobData::Sequentiality sequentiality,
355 KisStrokeJobData::Exclusivity exclusivity)
356 {
357 // simple tail-recursive iteration
358
359 KisNodeSP prevNode = node->lastChild();
360 while(prevNode) {
361 visitRecursively(prevNode, visitor, sequentiality, exclusivity);
362 prevNode = prevNode->prevSibling();
363 }
364
365 applyCommand(new KisProcessingCommand(visitor, node),
366 sequentiality, exclusivity);
367 }
368
applyCommand(KUndo2Command * command,KisStrokeJobData::Sequentiality sequentiality,KisStrokeJobData::Exclusivity exclusivity)369 void KisProcessingApplicator::applyCommand(KUndo2Command *command,
370 KisStrokeJobData::Sequentiality sequentiality,
371 KisStrokeJobData::Exclusivity exclusivity)
372 {
373 /*
374 * One should not add commands after the final signals have been
375 * emitted, only end or cancel the stroke
376 */
377 KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted);
378
379 m_image->addJob(m_strokeId,
380 new KisStrokeStrategyUndoCommandBased::Data(KUndo2CommandSP(command),
381 false,
382 sequentiality,
383 exclusivity));
384 }
385
explicitlyEmitFinalSignals()386 void KisProcessingApplicator::explicitlyEmitFinalSignals()
387 {
388 KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted);
389
390 if (m_node) {
391 applyCommand(new UpdateCommand(m_image, m_node, m_flags,
392 UpdateCommand::FINALIZING,
393 m_sharedAllFramesToken));
394 }
395
396 if(m_flags.testFlag(NO_UI_UPDATES)) {
397 applyCommand(new DisableUIUpdatesCommand(m_image, true), KisStrokeJobData::BARRIER);
398 }
399
400 if(!m_emitSignals.isEmpty()) {
401 applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, true), KisStrokeJobData::BARRIER);
402 }
403
404 // simple consistency check
405 m_finalSignalsEmitted = true;
406 }
407
end()408 void KisProcessingApplicator::end()
409 {
410 if (!m_finalSignalsEmitted) {
411 explicitlyEmitFinalSignals();
412 }
413
414 m_image->endStroke(m_strokeId);
415 }
416
cancel()417 void KisProcessingApplicator::cancel()
418 {
419 m_image->cancelStroke(m_strokeId);
420 }
421
runSingleCommandStroke(KisImageSP image,KUndo2Command * cmd,KisStrokeJobData::Sequentiality sequentiality,KisStrokeJobData::Exclusivity exclusivity)422 void KisProcessingApplicator::runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
423 {
424 KisProcessingApplicator applicator(image, 0,
425 KisProcessingApplicator::NONE,
426 KisImageSignalVector() << ModifiedSignal,
427 cmd->text());
428 applicator.applyCommand(cmd, sequentiality, exclusivity);
429 applicator.end();
430 }
431