File indexing completed on 2024-12-22 04:10:03

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_saved_commands.h"
0008 
0009 #include <QVector>
0010 
0011 #include "kis_image_interfaces.h"
0012 #include "kis_stroke_strategy_undo_command_based.h"
0013 #include <KisAsynchronouslyMergeableCommandInterface.h>
0014 
0015 
0016 KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name,
0017                                          KisStrokesFacade *strokesFacade)
0018     : KUndo2Command(name),
0019       m_strokesFacade(strokesFacade),
0020       m_skipOneRedo(true)
0021 {
0022 }
0023 
0024 KisSavedCommandBase::~KisSavedCommandBase()
0025 {
0026 }
0027 
0028 KisStrokesFacade* KisSavedCommandBase::strokesFacade()
0029 {
0030     return m_strokesFacade;
0031 }
0032 
0033 void KisSavedCommandBase::runStroke(bool undo)
0034 {
0035     KisStrokeStrategyUndoCommandBased *strategy =
0036         new KisStrokeStrategyUndoCommandBased(text(), undo, 0);
0037     strategy->setUsedWhileUndoRedo(true);
0038 
0039     KisStrokeId id = m_strokesFacade->startStroke(strategy);
0040     addCommands(id, undo);
0041     m_strokesFacade->endStroke(id);
0042 }
0043 
0044 void KisSavedCommandBase::undo()
0045 {
0046 
0047     runStroke(true);
0048 }
0049 
0050 
0051 
0052 void KisSavedCommandBase::redo()
0053 {
0054     /**
0055      * All the commands are first executed in the stroke and then
0056      * added to the undo stack. It means that the first redo should be
0057      * skipped
0058      */
0059 
0060     if(m_skipOneRedo) {
0061         m_skipOneRedo = false;
0062         return;
0063     }
0064 
0065     runStroke(false);
0066 }
0067 
0068 
0069 KisSavedCommand::KisSavedCommand(KUndo2CommandSP command,
0070                                  KisStrokesFacade *strokesFacade)
0071     : KisSavedCommandBase(command->text(), strokesFacade),
0072       m_command(command)
0073 {
0074 }
0075 
0076 int KisSavedCommand::id() const
0077 {
0078     return m_command->id();
0079 }
0080 
0081 bool KisSavedCommand::mergeWith(const KUndo2Command* command)
0082 {
0083     return unwrap(command, [this] (const KUndo2Command *cmd) {
0084         return m_command->mergeWith(cmd);
0085     });
0086 }
0087 
0088 bool KisSavedCommand::canAnnihilateWith(const KUndo2Command *command) const
0089 {
0090     return unwrap(command, [this] (const KUndo2Command *cmd) {
0091         return m_command->canAnnihilateWith(cmd);
0092     });
0093 }
0094 
0095 void KisSavedCommand::addCommands(KisStrokeId id, bool undo)
0096 {
0097     strokesFacade()->
0098         addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo));
0099 }
0100 int KisSavedCommand::timedId() const
0101 {
0102     return m_command->timedId();
0103 }
0104 void KisSavedCommand::setTimedID(int timedID)
0105 {
0106     m_command->setTimedID(timedID);
0107 }
0108 
0109 bool KisSavedCommand::timedMergeWith(KUndo2Command *other)
0110 {
0111     /// Since we are saving the actual command inside another
0112     /// command, so we cannot unwrap the command here. Otherwise
0113     /// the shared pointer in `other` will destroy the merged
0114     /// command some time later.
0115 
0116     return m_command->timedMergeWith(other);
0117 }
0118 QVector<KUndo2Command*> KisSavedCommand::mergeCommandsVector() const
0119 {
0120     return m_command->mergeCommandsVector();
0121 }
0122 void KisSavedCommand::setTime(const QTime &time)
0123 {
0124     m_command->setTime(time);
0125 }
0126 
0127 QTime KisSavedCommand::time() const
0128 {
0129     return m_command->time();
0130 }
0131 void KisSavedCommand::setEndTime(const QTime &time)
0132 {
0133     m_command->setEndTime(time);
0134 }
0135 
0136 QTime KisSavedCommand::endTime() const
0137 {
0138     return m_command->endTime();
0139 }
0140 bool KisSavedCommand::isMerged() const
0141 {
0142     return m_command->isMerged();
0143 }
0144 
0145 
0146 
0147 struct KisSavedMacroCommand::Private
0148 {
0149     struct SavedCommand {
0150         KUndo2CommandSP command;
0151         KisStrokeJobData::Sequentiality sequentiality;
0152         KisStrokeJobData::Exclusivity exclusivity;
0153     };
0154 
0155     QVector<SavedCommand> commands;
0156     int macroId = -1;
0157 
0158     const KisSavedMacroCommand *overriddenCommand = 0;
0159     QVector<const KUndo2Command*> skipWhenOverride;
0160 };
0161 
0162 KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name,
0163                                            KisStrokesFacade *strokesFacade)
0164     : KisSavedCommandBase(name, strokesFacade),
0165       m_d(new Private())
0166 {
0167 }
0168 
0169 KisSavedMacroCommand::~KisSavedMacroCommand()
0170 {
0171     delete m_d;
0172 }
0173 
0174 void KisSavedMacroCommand::setMacroId(int value)
0175 {
0176     m_d->macroId = value;
0177 }
0178 
0179 int KisSavedMacroCommand::id() const
0180 {
0181     return m_d->macroId;
0182 }
0183 
0184 bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command)
0185 {
0186     const KisSavedMacroCommand *other =
0187         dynamic_cast<const KisSavedMacroCommand*>(command);
0188 
0189     if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false;
0190 
0191     QVector<Private::SavedCommand> &otherCommands = other->m_d->commands;
0192 
0193     if (other->m_d->overriddenCommand == this) {
0194         m_d->commands.clear();
0195 
0196         Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) {
0197             if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) {
0198                 m_d->commands.append(cmd);
0199             }
0200         }
0201 
0202         if (other->extraData()) {
0203             setExtraData(other->extraData()->clone());
0204         } else {
0205             setExtraData(0);
0206         }
0207         return true;
0208     }
0209 
0210     if (m_d->commands.size() != otherCommands.size()) return false;
0211 
0212     auto it = m_d->commands.constBegin();
0213     auto end = m_d->commands.constEnd();
0214 
0215     auto otherIt = otherCommands.constBegin();
0216     auto otherEnd = otherCommands.constEnd();
0217 
0218     bool sameCommands = true;
0219     while (it != end && otherIt != otherEnd) {
0220         KisAsynchronouslyMergeableCommandInterface *iface1 =
0221             dynamic_cast<KisAsynchronouslyMergeableCommandInterface*>(it->command.data());
0222 
0223         if (!iface1 || !iface1->canMergeWith(otherIt->command.data()) ||
0224             it->command->id() < 0 || otherIt->command->id() < 0 ||
0225             it->command->id() != otherIt->command->id() ||
0226             it->sequentiality != otherIt->sequentiality ||
0227             it->exclusivity != otherIt->exclusivity) {
0228 
0229             sameCommands = false;
0230             break;
0231         }
0232         ++it;
0233         ++otherIt;
0234     }
0235 
0236     if (!sameCommands) return false;
0237 
0238     it = m_d->commands.constBegin();
0239     otherIt = otherCommands.constBegin();
0240 
0241     while (it != end && otherIt != otherEnd) {
0242         if (it->command->id() != -1) {
0243             bool result = it->command->mergeWith(otherIt->command.data());
0244             KIS_ASSERT_RECOVER(result) { return false; }
0245         }
0246         ++it;
0247         ++otherIt;
0248     }
0249 
0250     if (other->extraData()) {
0251         setExtraData(other->extraData()->clone());
0252     } else {
0253         setExtraData(0);
0254     }
0255 
0256     return true;
0257 }
0258 
0259 bool KisSavedMacroCommand::canAnnihilateWith(const KUndo2Command* command) const
0260 {
0261     const KisSavedMacroCommand *other =
0262         dynamic_cast<const KisSavedMacroCommand*>(command);
0263 
0264     if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false;
0265 
0266     QVector<Private::SavedCommand> &otherCommands = other->m_d->commands;
0267 
0268     if (other->m_d->overriddenCommand) return false;
0269     if (m_d->commands.size() != otherCommands.size()) return false;
0270 
0271     auto it = m_d->commands.constBegin();
0272     auto end = m_d->commands.constEnd();
0273 
0274     auto otherIt = otherCommands.constBegin();
0275     auto otherEnd = otherCommands.constEnd();
0276 
0277     bool sameCommands = true;
0278     while (it != end && otherIt != otherEnd) {
0279 
0280         if (!it->command->canAnnihilateWith(otherIt->command.data()) ||
0281             it->command->id() < 0 || otherIt->command->id() < 0 ||
0282             it->command->id() != otherIt->command->id() ||
0283             it->sequentiality != otherIt->sequentiality ||
0284             it->exclusivity != otherIt->exclusivity) {
0285 
0286             sameCommands = false;
0287             break;
0288         }
0289         ++it;
0290         ++otherIt;
0291     }
0292 
0293     if (!sameCommands) return false;
0294 
0295     return true;
0296 }
0297 
0298 void KisSavedMacroCommand::addCommand(KUndo2CommandSP command,
0299                                       KisStrokeJobData::Sequentiality sequentiality,
0300                                       KisStrokeJobData::Exclusivity exclusivity)
0301 {
0302     Private::SavedCommand item;
0303     item.command = command;
0304     item.sequentiality = sequentiality;
0305     item.exclusivity = exclusivity;
0306 
0307     m_d->commands.append(item);
0308 }
0309 
0310 void KisSavedMacroCommand::getCommandExecutionJobs(QVector<KisStrokeJobData *> *jobs, bool undo, bool shouldGoToHistory) const
0311 {
0312     QVector<Private::SavedCommand>::iterator it;
0313 
0314     if(!undo) {
0315         for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) {
0316             *jobs << new KisStrokeStrategyUndoCommandBased::
0317                        Data(it->command,
0318                             undo,
0319                             it->sequentiality,
0320                             it->exclusivity,
0321                             shouldGoToHistory);
0322         }
0323     }
0324     else {
0325         for(it = m_d->commands.end(); it != m_d->commands.begin();) {
0326             --it;
0327 
0328             *jobs << new KisStrokeStrategyUndoCommandBased::
0329                      Data(it->command,
0330                           undo,
0331                           it->sequentiality,
0332                           it->exclusivity,
0333                           shouldGoToHistory);
0334         }
0335     }
0336 }
0337 
0338 void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command*> &skipWhileOverride)
0339 {
0340     m_d->overriddenCommand = overriddenCommand;
0341     m_d->skipWhenOverride = skipWhileOverride;
0342 }
0343 
0344 void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
0345 {
0346     QVector<KisStrokeJobData *> jobs;
0347     getCommandExecutionJobs(&jobs, undo);
0348 
0349     Q_FOREACH (KisStrokeJobData *job, jobs) {
0350         strokesFacade()->addJob(id, job);
0351     }
0352 }