diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp index b6e04b57a5..3308ed5765 100644 --- a/libs/image/commands_new/kis_saved_commands.cpp +++ b/libs/image/commands_new/kis_saved_commands.cpp @@ -1,317 +1,319 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_saved_commands.h" #include #include "kis_image_interfaces.h" #include "kis_stroke_strategy_undo_command_based.h" KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KUndo2Command(name), m_strokesFacade(strokesFacade), m_skipOneRedo(true) { } KisSavedCommandBase::~KisSavedCommandBase() { } KisStrokesFacade* KisSavedCommandBase::strokesFacade() { return m_strokesFacade; } void KisSavedCommandBase::runStroke(bool undo) { KisStrokeStrategyUndoCommandBased *strategy = new KisStrokeStrategyUndoCommandBased(text(), undo, 0); strategy->setUsedWhileUndoRedo(true); KisStrokeId id = m_strokesFacade->startStroke(strategy); addCommands(id, undo); m_strokesFacade->endStroke(id); } void KisSavedCommandBase::undo() { runStroke(true); } void KisSavedCommandBase::redo() { /** * All the commands are first executed in the stroke and then * added to the undo stack. It means that the first redo should be * skipped */ if(m_skipOneRedo) { m_skipOneRedo = false; return; } runStroke(false); } KisSavedCommand::KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(command->text(), strokesFacade), m_command(command) { } int KisSavedCommand::id() const { return m_command->id(); } bool KisSavedCommand::mergeWith(const KUndo2Command* command) { const KisSavedCommand *other = dynamic_cast(command); if (other) { command = other->m_command.data(); } return m_command->mergeWith(command); } void KisSavedCommand::addCommands(KisStrokeId id, bool undo) { strokesFacade()-> addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo)); } int KisSavedCommand::timedId() { return m_command->timedId(); } void KisSavedCommand::setTimedID(int timedID) { m_command->setTimedID(timedID); } bool KisSavedCommand::timedMergeWith(KUndo2Command *other) { return m_command->timedMergeWith(other); } QVector KisSavedCommand::mergeCommandsVector() { return m_command->mergeCommandsVector(); } void KisSavedCommand::setTime() { m_command->setTime(); } QTime KisSavedCommand::time() { return m_command->time(); } void KisSavedCommand::setEndTime() { m_command->setEndTime(); } QTime KisSavedCommand::endTime() { return m_command->endTime(); } bool KisSavedCommand::isMerged() { return m_command->isMerged(); } struct KisSavedMacroCommand::Private { struct SavedCommand { KUndo2CommandSP command; KisStrokeJobData::Sequentiality sequentiality; KisStrokeJobData::Exclusivity exclusivity; }; QVector commands; int macroId = -1; const KisSavedMacroCommand *overriddenCommand = 0; QVector skipWhenOverride; }; KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(name, strokesFacade), m_d(new Private()) { } KisSavedMacroCommand::~KisSavedMacroCommand() { delete m_d; } void KisSavedMacroCommand::setMacroId(int value) { m_d->macroId = value; } int KisSavedMacroCommand::id() const { return m_d->macroId; } bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command) { const KisSavedMacroCommand *other = dynamic_cast(command); if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false; QVector &otherCommands = other->m_d->commands; if (other->m_d->overriddenCommand == this) { m_d->commands.clear(); Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) { if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) { m_d->commands.append(cmd); } } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } return true; } if (m_d->commands.size() != otherCommands.size()) return false; auto it = m_d->commands.constBegin(); auto end = m_d->commands.constEnd(); auto otherIt = otherCommands.constBegin(); auto otherEnd = otherCommands.constEnd(); bool sameCommands = true; while (it != end && otherIt != otherEnd) { if (it->command->id() < 0 || otherIt->command->id() < 0 || it->command->id() != otherIt->command->id() || it->sequentiality != otherIt->sequentiality || it->exclusivity != otherIt->exclusivity) { sameCommands = false; break; } ++it; ++otherIt; } if (!sameCommands) return false; it = m_d->commands.constBegin(); otherIt = otherCommands.constBegin(); while (it != end && otherIt != otherEnd) { if (it->command->id() != -1) { bool result = it->command->mergeWith(otherIt->command.data()); KIS_ASSERT_RECOVER(result) { return false; } } ++it; ++otherIt; } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } return true; } void KisSavedMacroCommand::addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { Private::SavedCommand item; item.command = command; item.sequentiality = sequentiality; item.exclusivity = exclusivity; m_d->commands.append(item); } void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo) { addCommands(id, !strokeUndo); } -void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo) const +void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory) const { QVector::iterator it; if(!undo) { for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) { *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, - it->exclusivity); + it->exclusivity, + shouldGoToHistory); } } else { for(it = m_d->commands.end(); it != m_d->commands.begin();) { --it; *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, - it->exclusivity); + it->exclusivity, + shouldGoToHistory); } } } void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride) { m_d->overriddenCommand = overriddenCommand; m_d->skipWhenOverride = skipWhileOverride; } void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo) { QVector jobs; getCommandExecutionJobs(&jobs, undo); Q_FOREACH (KisStrokeJobData *job, jobs) { strokesFacade()->addJob(id, job); } } diff --git a/libs/image/commands_new/kis_saved_commands.h b/libs/image/commands_new/kis_saved_commands.h index d1b805c847..4f23de43fc 100644 --- a/libs/image/commands_new/kis_saved_commands.h +++ b/libs/image/commands_new/kis_saved_commands.h @@ -1,105 +1,105 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SAVED_COMMANDS_H #define __KIS_SAVED_COMMANDS_H #include #include "kis_types.h" #include "kis_stroke_job_strategy.h" class KisStrokesFacade; class KRITAIMAGE_EXPORT KisSavedCommandBase : public KUndo2Command { public: KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedCommandBase() override; void undo() override; void redo() override; protected: virtual void addCommands(KisStrokeId id, bool undo) = 0; KisStrokesFacade* strokesFacade(); private: void runStroke(bool undo); private: KisStrokesFacade *m_strokesFacade; bool m_skipOneRedo; }; class KRITAIMAGE_EXPORT KisSavedCommand : public KisSavedCommandBase { public: KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade); int timedId() override; void setTimedID(int timedID) override; int id() const override; bool mergeWith(const KUndo2Command* command) override; bool timedMergeWith(KUndo2Command *other) override; QVector mergeCommandsVector() override; void setTime() override; QTime time() override; void setEndTime() override; QTime endTime() override; bool isMerged() override; protected: void addCommands(KisStrokeId id, bool undo) override; private: KUndo2CommandSP m_command; }; class KRITAIMAGE_EXPORT KisSavedMacroCommand : public KisSavedCommandBase { public: KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedMacroCommand() override; int id() const override; bool mergeWith(const KUndo2Command* command) override; void setMacroId(int value); void addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); void performCancel(KisStrokeId id, bool strokeUndo); - void getCommandExecutionJobs(QVector *jobs, bool undo) const; + void getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory = true) const; void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride); protected: void addCommands(KisStrokeId id, bool undo) override; private: struct Private; Private * const m_d; }; #endif /* __KIS_SAVED_COMMANDS_H */ diff --git a/libs/image/kis_stroke_job.h b/libs/image/kis_stroke_job.h index 2a2b2adaa1..13cb7f7a2b 100644 --- a/libs/image/kis_stroke_job.h +++ b/libs/image/kis_stroke_job.h @@ -1,103 +1,104 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_STROKE_JOB_H #define __KIS_STROKE_JOB_H #include "kis_runnable.h" #include "kis_stroke_job_strategy.h" class KisStrokeJob : public KisRunnable { public: KisStrokeJob(KisStrokeJobStrategy *strategy, KisStrokeJobData *data, int levelOfDetail, bool isOwnJob) : m_dabStrategy(strategy), m_dabData(data), m_levelOfDetail(levelOfDetail), m_isOwnJob(isOwnJob) { } ~KisStrokeJob() override { delete m_dabData; } void run() override { m_dabStrategy->run(m_dabData); } KisStrokeJobData::Sequentiality sequentiality() const { return m_dabData ? m_dabData->sequentiality() : KisStrokeJobData::SEQUENTIAL; } bool isSequential() const { // Default value is 'SEQUENTIAL' return m_dabData ? m_dabData->isSequential() : true; } bool isBarrier() const { // Default value is simply 'SEQUENTIAL', *not* 'BARRIER' return m_dabData ? m_dabData->isBarrier() : false; } bool isExclusive() const { // Default value is 'NORMAL' return m_dabData ? m_dabData->isExclusive() : false; } int levelOfDetail() const { return m_levelOfDetail; } bool isCancellable() const { - return m_isOwnJob; + return m_isOwnJob && + (!m_dabData || m_dabData->isCancellable()); } bool isOwnJob() const { return m_isOwnJob; } private: // for testing use only, do not use in real code friend QString getJobName(KisStrokeJob *job); friend QString getCommandName(KisStrokeJob *job); friend int cancelSeqNo(KisStrokeJob *job); KisStrokeJobStrategy* testingGetDabStrategy() { return m_dabStrategy; } KisStrokeJobData* testingGetDabData() { return m_dabData; } private: // Shared between different jobs KisStrokeJobStrategy *m_dabStrategy; // Owned by the job KisStrokeJobData *m_dabData; int m_levelOfDetail; bool m_isOwnJob; }; #endif /* __KIS_STROKE_JOB_H */ diff --git a/libs/image/kis_stroke_job_strategy.cpp b/libs/image/kis_stroke_job_strategy.cpp index 93800bc71d..c70fdaf20e 100644 --- a/libs/image/kis_stroke_job_strategy.cpp +++ b/libs/image/kis_stroke_job_strategy.cpp @@ -1,70 +1,82 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_stroke_job_strategy.h" #include KisStrokeJobData::KisStrokeJobData(Sequentiality sequentiality, Exclusivity exclusivity) : m_sequentiality(sequentiality), - m_exclusivity(exclusivity) + m_exclusivity(exclusivity), + m_isCancellable(true) { } KisStrokeJobData::KisStrokeJobData(const KisStrokeJobData &rhs) : m_sequentiality(rhs.m_sequentiality), - m_exclusivity(rhs.m_exclusivity) + m_exclusivity(rhs.m_exclusivity), + m_isCancellable(rhs.m_isCancellable) { } KisStrokeJobData::~KisStrokeJobData() { } bool KisStrokeJobData::isBarrier() const { return m_sequentiality == BARRIER; } bool KisStrokeJobData::isSequential() const { return m_sequentiality == SEQUENTIAL; } bool KisStrokeJobData::isExclusive() const { return m_exclusivity == EXCLUSIVE; } KisStrokeJobData* KisStrokeJobData::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); return 0; } +bool KisStrokeJobData::isCancellable() const +{ + return m_isCancellable; +} + +void KisStrokeJobData::setCancellable(bool value) +{ + m_isCancellable = value; +} + KisStrokeJobStrategy::KisStrokeJobStrategy() { } KisStrokeJobStrategy::~KisStrokeJobStrategy() { } diff --git a/libs/image/kis_stroke_job_strategy.h b/libs/image/kis_stroke_job_strategy.h index 45bb6eecc5..9f344b0ff5 100644 --- a/libs/image/kis_stroke_job_strategy.h +++ b/libs/image/kis_stroke_job_strategy.h @@ -1,75 +1,79 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_STROKE_JOB_STRATEGY_H #define __KIS_STROKE_JOB_STRATEGY_H #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisStrokeJobData { public: enum Sequentiality { CONCURRENT, SEQUENTIAL, BARRIER, UNIQUELY_CONCURRENT }; enum Exclusivity { NORMAL, EXCLUSIVE }; public: KisStrokeJobData(Sequentiality sequentiality = SEQUENTIAL, Exclusivity exclusivity = NORMAL); virtual ~KisStrokeJobData(); bool isBarrier() const; bool isSequential() const; bool isExclusive() const; Sequentiality sequentiality() { return m_sequentiality; } Exclusivity exclusivity() { return m_exclusivity; } virtual KisStrokeJobData* createLodClone(int levelOfDetail); + bool isCancellable() const; + void setCancellable(bool value); + protected: KisStrokeJobData(const KisStrokeJobData &rhs); private: Sequentiality m_sequentiality; Exclusivity m_exclusivity; + bool m_isCancellable; }; class KRITAIMAGE_EXPORT KisStrokeJobStrategy { public: KisStrokeJobStrategy(); virtual ~KisStrokeJobStrategy(); virtual void run(KisStrokeJobData *data) = 0; private: }; #endif /* __KIS_STROKE_JOB_STRATEGY_H */ diff --git a/libs/image/kis_stroke_strategy_undo_command_based.cpp b/libs/image/kis_stroke_strategy_undo_command_based.cpp index 37560264ee..d50d79eb5d 100644 --- a/libs/image/kis_stroke_strategy_undo_command_based.cpp +++ b/libs/image/kis_stroke_strategy_undo_command_based.cpp @@ -1,185 +1,189 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_stroke_strategy_undo_command_based.h" #include #include "kis_image_interfaces.h" #include "kis_post_execution_undo_adapter.h" #include "commands_new/kis_saved_commands.h" KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand, KUndo2CommandSP finishCommand) : KisRunnableBasedStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name), m_undo(undo), m_initCommand(initCommand), m_finishCommand(finishCommand), m_undoFacade(undoFacade), m_macroId(-1), m_macroCommand(0) { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); } KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs) : KisRunnableBasedStrokeStrategy(rhs), m_undo(false), m_initCommand(rhs.m_initCommand), m_finishCommand(rhs.m_finishCommand), m_undoFacade(rhs.m_undoFacade), m_macroCommand(0) { KIS_ASSERT_RECOVER_NOOP(!rhs.m_macroCommand && !rhs.m_undo && "After the stroke has been started, no copying must happen"); } void KisStrokeStrategyUndoCommandBased::setUsedWhileUndoRedo(bool value) { setClearsRedoOnStart(!value); } void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command, bool undo) { if(!command) return; if (MutatedCommandInterface *mutatedCommand = dynamic_cast(command.data())) { mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface()); } if(undo) { command->undo(); } else { command->redo(); } } void KisStrokeStrategyUndoCommandBased::initStrokeCallback() { if(m_undoFacade) { m_macroCommand = m_undoFacade->postExecutionUndoAdapter()->createMacro(name()); } executeCommand(m_initCommand, m_undo); notifyCommandDone(m_initCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void KisStrokeStrategyUndoCommandBased::finishStrokeCallback() { executeCommand(m_finishCommand, m_undo); notifyCommandDone(m_finishCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); QMutexLocker locker(&m_mutex); if(m_macroCommand) { Q_ASSERT(m_undoFacade); postProcessToplevelCommand(m_macroCommand); m_undoFacade->postExecutionUndoAdapter()->addMacro(m_macroCommand); m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback() { QMutexLocker locker(&m_mutex); if(m_macroCommand) { m_macroCommand->performCancel(cancelStrokeId(), m_undo); delete m_macroCommand; m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); if (d) { executeCommand(d->command, d->undo); - notifyCommandDone(d->command, d->sequentiality(), d->exclusivity()); + if (d->shouldGoToHistory) { + notifyCommandDone(d->command, + d->sequentiality(), + d->exclusivity()); + } } else { KisRunnableBasedStrokeStrategy::doStrokeCallback(data); } } void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if (!command) return; executeCommand(command, false); notifyCommandDone(command, sequentiality, exclusivity); } void KisStrokeStrategyUndoCommandBased::notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if(!command) return; QMutexLocker locker(&m_mutex); if(m_macroCommand) { m_macroCommand->addCommand(command, sequentiality, exclusivity); } } void KisStrokeStrategyUndoCommandBased::setCommandExtraData(KUndo2CommandExtraData *data) { if (m_undoFacade && m_macroCommand) { warnKrita << "WARNING: KisStrokeStrategyUndoCommandBased::setCommandExtraData():" << "the extra data is set while the stroke has already been started!" << "The result is undefined, continued actions may not work!"; } m_commandExtraData.reset(data); } void KisStrokeStrategyUndoCommandBased::setMacroId(int value) { m_macroId = value; } void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command *command) { if (m_commandExtraData) { command->setExtraData(m_commandExtraData.take()); } KisSavedMacroCommand *savedCommand = dynamic_cast(command); if (savedCommand) { savedCommand->setMacroId(m_macroId); } } KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const { return m_undoFacade; } diff --git a/libs/image/kis_stroke_strategy_undo_command_based.h b/libs/image/kis_stroke_strategy_undo_command_based.h index 41ca43c3fd..e4e46e364e 100644 --- a/libs/image/kis_stroke_strategy_undo_command_based.h +++ b/libs/image/kis_stroke_strategy_undo_command_based.h @@ -1,162 +1,167 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H #define __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H #include #include #include #include "kis_types.h" #include "kis_simple_stroke_strategy.h" #include "KisRunnableBasedStrokeStrategy.h" class KisStrokeJob; class KisSavedMacroCommand; class KisStrokeUndoFacade; class KisStrokesQueueMutatedJobInterface; class KRITAIMAGE_EXPORT KisStrokeStrategyUndoCommandBased : public KisRunnableBasedStrokeStrategy { public: struct MutatedCommandInterface { virtual ~MutatedCommandInterface() {} void setRunnableJobsInterface(KisRunnableStrokeJobsInterface *interface) { m_mutatedJobsInterface = interface; } KisRunnableStrokeJobsInterface* runnableJobsInterface() const { return m_mutatedJobsInterface; } private: KisRunnableStrokeJobsInterface *m_mutatedJobsInterface; }; class Data : public KisStrokeJobData { public: Data(KUndo2CommandSP _command, bool _undo = false, Sequentiality _sequentiality = SEQUENTIAL, - Exclusivity _exclusivity = NORMAL) + Exclusivity _exclusivity = NORMAL, + bool _shouldGoToHistory = true) : KisStrokeJobData(_sequentiality, _exclusivity), command(_command), - undo(_undo) + undo(_undo), + shouldGoToHistory(_shouldGoToHistory) { } Data(KUndo2Command *_command, bool _undo = false, Sequentiality _sequentiality = SEQUENTIAL, - Exclusivity _exclusivity = NORMAL) + Exclusivity _exclusivity = NORMAL, + bool _shouldGoToHistory = true) : KisStrokeJobData(_sequentiality, _exclusivity), command(_command), - undo(_undo) + undo(_undo), + shouldGoToHistory(_shouldGoToHistory) { } KUndo2CommandSP command; bool undo; + bool shouldGoToHistory = true; }; public: KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand = KUndo2CommandSP(0), KUndo2CommandSP finishCommand = KUndo2CommandSP(0)); using KisSimpleStrokeStrategy::setExclusive; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; /** * Set extra data that will be assigned to the command * representing this action. Using extra data has the following * restrictions: * * 1) The \p data must be set *before* the stroke has been started. * Setting the \p data after the stroke has been started with * image->startStroke(strokeId) leads to an undefined behaviour. * * 2) \p data becomes owned by the strategy/command right after * setting it. Don't try to change it afterwards. */ void setCommandExtraData(KUndo2CommandExtraData *data); /** * Sets the id of this action. Will be used for merging the undo commands * * The \p value must be set *before* the stroke has been started. * Setting the \p value after the stroke has been started with * image->startStroke(strokeId) leads to an undefined behaviour. */ void setMacroId(int value); /** * The undo-command-based is a low-level strategy, so it allows * changing its wraparound mode status. * * WARNING: the switch must be called *before* the stroke has been * started! Otherwise the mode will not be activated. */ using KisStrokeStrategy::setSupportsWrapAroundMode; void setUsedWhileUndoRedo(bool value); protected: void runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity); void notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity); KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs); virtual void postProcessToplevelCommand(KUndo2Command *command); KisStrokeUndoFacade* undoFacade() const; private: void executeCommand(KUndo2CommandSP command, bool undo); private: bool m_undo; KUndo2CommandSP m_initCommand; KUndo2CommandSP m_finishCommand; KisStrokeUndoFacade *m_undoFacade; QScopedPointer m_commandExtraData; int m_macroId; // protects done commands only QMutex m_mutex; KisSavedMacroCommand *m_macroCommand; }; #endif /* __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H */ diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index d9ce90047c..67490a23c0 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,671 +1,688 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include #include "transform_transaction_properties.h" #include "krita_container_utils.h" #include "commands_new/kis_saved_commands.h" #include "kis_command_ids.h" #include "KisRunnableStrokeJobUtils.h" #include "commands_new/KisHoldUIUpdatesCommand.h" TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_updatesFacade(updatesFacade), m_mode(mode), m_workRecursively(workRecursively), m_filterId(filterId), m_forceReset(forceReset), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); m_rootNode = rootNode; setMacroId(KisCommandUtils::TransformToolId); } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); TransformAllData *runAllData = dynamic_cast(data); if (runAllData) { // here we only save the passed args, actual // transformation will be performed during // finish job m_savedTransformArgs = runAllData->config; } else if (ppd) { KisNodeSP rootNode = m_rootNode; KisNodeList processedNodes = m_processedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); // In case the group is pass-through, it needs to be disabled for the preview, // otherwise it will crash (no parent for a preview leaf). // Also it needs to be done before setting the root layer for clonedImage. // Result: preview for pass-through group is the same as for standard group // (i.e. filter layers in the group won't affect the layer stack for a moment). clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds()); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } QPainterPath selectionOutline; if (m_selection && m_selection->outlineCacheValid()) { selectionOutline = m_selection->outlineCache(); } else if (previewDevice) { selectionOutline.addRect(previewDevice->exactBounds()); } emit sigPreviewDeviceReady(previewDevice, selectionOutline); } else if(td) { if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; KUndo2CommandExtraData* clone() const override { return new TransformExtraData(*this); } }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs); TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = *m_savedTransformArgs; data->rootNode = m_rootNode; data->transformedNodes = m_processedNodes; command->setExtraData(data); KisSavedMacroCommand *macroCommand = dynamic_cast(command); KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand); if (m_overriddenCommand && macroCommand) { macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands); } KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { *args = adapter->transformArgs(); result = true; } } return result; } bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs) { bool result = false; const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; ToolTransformArgs args; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) && args.mode() == mode && oldRootNode == currentNode) { if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) { args.saveContinuedState(); *outArgs = args; const KisSavedMacroCommand *command = dynamic_cast(lastCommand); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false); - command->getCommandExecutionJobs(undoJobs, true); - + // the jobs are fetched as !shouldGoToHistory, + // so there is no need to put them into + // m_skippedWhileMergeCommands + command->getCommandExecutionJobs(undoJobs, true, false); m_overriddenCommand = command; - Q_FOREACH (KisStrokeJobData *commonData, *undoJobs) { - Data *data = dynamic_cast(commonData); - KIS_SAFE_ASSERT_RECOVER(data) { continue; } - - m_skippedWhileMergeCommands << data->command.data(); - } - result = true; } } return result; } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } ToolTransformArgs initialTransformArgs; m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively); bool argsAreInitialized = false; QVector lastCommandUndoJobs; if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs, m_mode, m_rootNode, m_processedNodes, &lastCommandUndoJobs)) { argsAreInitialized = true; } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) { argsAreInitialized = true; } QVector extraInitJobs; extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER); extraInitJobs << lastCommandUndoJobs; KritaUtils::addJobSequential(extraInitJobs, [this]() { /** * We must request shape layers to rerender areas outside image bounds */ KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode); }); KritaUtils::addJobBarrier(extraInitJobs, [this]() { /** * We must ensure that the currently selected subtree * has finished all its updates. */ KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode); }); KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable { QRect srcRect; if (m_selection) { srcRect = m_selection->selectedExactRect(); } else { srcRect = QRect(); Q_FOREACH (KisNodeSP node, m_processedNodes) { // group layers may have a projection of layers // that are locked and will not be transformed if (node->inherits("KisGroupLayer")) continue; if (const KisTransformMask *mask = dynamic_cast(node.data())) { srcRect |= mask->sourceDataBounds(); } else { srcRect |= node->exactBounds(); } } } TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes); if (!argsAreInitialized) { initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction); } this->m_initialTransformArgs = initialTransformArgs; emit this->sigTransactionGenerated(transaction, initialTransformArgs, this); }); extraInitJobs << new PreparePreviewData(); Q_FOREACH (KisNodeSP node, m_processedNodes) { extraInitJobs << new ClearSelectionData(node); } extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER); + if (!lastCommandUndoJobs.isEmpty()) { + KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand); + + for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) { + (*it)->setCancellable(false); + } + } + addMutatedJobs(extraInitJobs); } void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args) { + /** + * Since our finishStrokeCallback() initiates new jobs, + * cancellation request may come even after + * finishStrokeCallback() (cancellations may be called + * until there are no jobs left in the stroke's queue). + * + * Therefore we should check for double-entry here and + * make sure the finilizing jobs are no cancellable. + */ + + if (m_finalizingActionsStarted) return; + m_finalizingActionsStarted = true; + QVector mutatedJobs; if (applyTransform) { Q_FOREACH (KisNodeSP node, m_processedNodes) { mutatedJobs << new TransformData(TransformData::PAINT_DEVICE, args, node); } mutatedJobs << new TransformData(TransformData::SELECTION, args, m_rootNode); } KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() { - - if (!applyTransform) { - KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); - } - Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } if (applyTransform) { KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); + } else { + KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } }); + for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) { + (*it)->setCancellable(false); + } + addMutatedJobs(mutatedJobs); } void TransformStrokeStrategy::finishStrokeCallback() { if (!m_savedTransformArgs || m_savedTransformArgs->isIdentity()) { cancelStrokeCallback(); return; } finishStrokeImpl(true, *m_savedTransformArgs); } void TransformStrokeStrategy::cancelStrokeCallback() { const bool shouldRecoverSavedInitialState = !m_initialTransformArgs.isIdentity(); if (shouldRecoverSavedInitialState) { m_savedTransformArgs = m_initialTransformArgs; } finishStrokeImpl(shouldRecoverSavedInitialState, *m_savedTransformArgs); } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h index b897991478..fb196b13a2 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h @@ -1,175 +1,177 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TRANSFORM_STROKE_STRATEGY_H #define __TRANSFORM_STROKE_STRATEGY_H #include #include #include #include #include #include "tool_transform_args.h" #include #include #include class KisPostExecutionUndoAdapter; class TransformTransactionProperties; class KisUpdatesFacade; class TransformStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { Q_OBJECT public: struct TransformAllData : public KisStrokeJobData { TransformAllData(const ToolTransformArgs &_config) : KisStrokeJobData(SEQUENTIAL, NORMAL), config(_config) {} ToolTransformArgs config; }; class TransformData : public KisStrokeJobData { public: enum Destination { PAINT_DEVICE, SELECTION, }; public: TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node) : KisStrokeJobData(CONCURRENT, NORMAL), destination(_destination), config(_config), node(_node) { } Destination destination; ToolTransformArgs config; KisNodeSP node; }; class ClearSelectionData : public KisStrokeJobData { public: ClearSelectionData(KisNodeSP _node) : KisStrokeJobData(SEQUENTIAL, NORMAL), node(_node) { } KisNodeSP node; }; class PreparePreviewData : public KisStrokeJobData { public: PreparePreviewData() : KisStrokeJobData(BARRIER, NORMAL) { } }; public: TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade); ~TransformStrokeStrategy() override; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; static bool fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes); Q_SIGNALS: void sigTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *cookie); void sigPreviewDeviceReady(KisPaintDeviceSP device, const QPainterPath &selectionOutline); protected: void postProcessToplevelCommand(KUndo2Command *command) override; private: KoUpdaterPtr fetchUpdater(KisNodeSP node); void transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); void clearSelection(KisPaintDeviceSP device); //void transformDevice(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); bool checkBelongsToSelection(KisPaintDeviceSP device) const; KisPaintDeviceSP createDeviceCache(KisPaintDeviceSP src); bool haveDeviceInCache(KisPaintDeviceSP src); void putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache); KisPaintDeviceSP getDeviceCache(KisPaintDeviceSP src); QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction); bool tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args); bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs); void finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args); private: KisUpdatesFacade *m_updatesFacade; ToolTransformArgs::TransformMode m_mode; bool m_workRecursively; QString m_filterId; bool m_forceReset; KisSelectionSP m_selection; QMutex m_devicesCacheMutex; QHash m_devicesCacheHash; KisTransformMaskSP writeToTransformMask; ToolTransformArgs m_initialTransformArgs; boost::optional m_savedTransformArgs; KisNodeSP m_rootNode; KisNodeList m_processedNodes; QList m_deactivatedSelections; QList m_hiddenProjectionLeaves; const KisSavedMacroCommand *m_overriddenCommand = 0; QVector m_skippedWhileMergeCommands; + + bool m_finalizingActionsStarted = false; }; #endif /* __TRANSFORM_STROKE_STRATEGY_H */