Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit eb17ce74 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Make KisToolTransform fully asynchronous

Now all initialization of the transform tool has been moved into the
stroke's context. It means that the tool doesn't need to block event
loop until it image is finished anymore.

The fix uses he new features of the strokes framework, specifically
mutated stroke jobs.

CCBUG:409275
parent 21889437
......@@ -33,7 +33,8 @@ enum CommandId {
ChangeRectangleShapeId,
ChangePathShapePointId,
ChangePathShapeControlPointId,
ChangePaletteId
ChangePaletteId,
TransformToolId
};
}
......
......@@ -26,6 +26,7 @@ class KRITACOMMAND_EXPORT KUndo2CommandExtraData
{
public:
virtual ~KUndo2CommandExtraData();
virtual KUndo2CommandExtraData* clone() const = 0;
};
#endif /* __KUNDO2COMMANDEXTRADATA_H */
......@@ -111,6 +111,7 @@ set(kritaimage_LIB_SRCS
commands_new/kis_change_projection_color_command.cpp
commands_new/kis_activate_selection_mask_command.cpp
commands_new/kis_transaction_based_command.cpp
commands_new/KisHoldUIUpdatesCommand.cpp
processing/kis_do_nothing_processing_visitor.cpp
processing/kis_simple_processing_visitor.cpp
processing/kis_crop_processing_visitor.cpp
......
/*
* Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisHoldUIUpdatesCommand.h"
#include <algorithm>
#include "kis_image_interfaces.h"
#include "krita_utils.h"
#include "kis_paintop_utils.h"
#include "kis_image_signal_router.h"
#include "KisRunnableStrokeJobData.h"
#include "KisRunnableStrokeJobUtils.h"
#include "KisRunnableStrokeJobsInterface.h"
KisHoldUIUpdatesCommand::KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state)
: KisCommandUtils::FlipFlopCommand(state),
m_updatesFacade(updatesFacade),
m_batchUpdateStarted(new bool(false))
{
}
void KisHoldUIUpdatesCommand::partA()
{
if (*m_batchUpdateStarted) {
m_updatesFacade->notifyBatchUpdateEnded();
*m_batchUpdateStarted = false;
}
m_updatesFacade->disableUIUpdates();
}
void KisHoldUIUpdatesCommand::partB()
{
QVector<QRect> totalDirtyRects = m_updatesFacade->enableUIUpdates();
const QRect totalRect =
m_updatesFacade->bounds() &
std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or<QRect>());
totalDirtyRects =
KisPaintOpUtils::splitAndFilterDabRect(totalRect,
totalDirtyRects,
KritaUtils::optimalPatchSize().width());
*m_batchUpdateStarted = true;
m_updatesFacade->notifyBatchUpdateStarted();
KisUpdatesFacade *updatesFacade = m_updatesFacade;
QSharedPointer<bool> batchUpdateStarted = m_batchUpdateStarted;
QVector<KisRunnableStrokeJobDataBase*> jobsData;
Q_FOREACH (const QRect &rc, totalDirtyRects) {
KritaUtils::addJobConcurrent(jobsData, [updatesFacade, rc] () {
updatesFacade->notifyUIUpdateCompleted(rc);
});
}
KritaUtils::addJobBarrier(jobsData, [updatesFacade, batchUpdateStarted] () {
updatesFacade->notifyBatchUpdateEnded();
*batchUpdateStarted = false;
});
runnableJobsInterface()->addRunnableJobs(jobsData);
}
/*
* Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 KISHOLDUIUPDATESCOMMAND_H
#define KISHOLDUIUPDATESCOMMAND_H
#include "kis_stroke_strategy_undo_command_based.h"
#include "kis_node.h"
#include "kis_command_utils.h"
#include <QSharedPointer>
class KisUpdatesFacade;
class KRITAIMAGE_EXPORT KisHoldUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand, public KisStrokeStrategyUndoCommandBased::MutatedCommandInterface
{
public:
KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state);
void partA() override;
void partB() override;
private:
KisUpdatesFacade *m_updatesFacade;
QSharedPointer<bool> m_batchUpdateStarted;
};
#endif // KISHOLDUIUPDATESCOMMAND_H
......@@ -158,6 +158,9 @@ struct KisSavedMacroCommand::Private
QVector<SavedCommand> commands;
int macroId = -1;
const KisSavedMacroCommand *overriddenCommand = 0;
QVector<const KUndo2Command*> skipWhenOverride;
};
KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name,
......@@ -191,6 +194,19 @@ bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command)
QVector<Private::SavedCommand> &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);
}
}
setExtraData(other->extraData()->clone());
return true;
}
if (m_d->commands.size() != otherCommands.size()) return false;
auto it = m_d->commands.constBegin();
......@@ -246,30 +262,44 @@ void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo)
addCommands(id, !strokeUndo);
}
void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
void KisSavedMacroCommand::getCommandExecutionJobs(QVector<KisStrokeJobData *> *jobs, bool undo) const
{
QVector<Private::SavedCommand>::iterator it;
if(!undo) {
for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) {
strokesFacade()->
addJob(id, new KisStrokeStrategyUndoCommandBased::
*jobs << new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
it->exclusivity));
it->exclusivity);
}
}
else {
for(it = m_d->commands.end(); it != m_d->commands.begin();) {
--it;
strokesFacade()->
addJob(id, new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
it->exclusivity));
*jobs << new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
it->exclusivity);
}
}
}
void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command*> &skipWhileOverride)
{
m_d->overriddenCommand = overriddenCommand;
m_d->skipWhenOverride = skipWhileOverride;
}
void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
{
QVector<KisStrokeJobData *> jobs;
getCommandExecutionJobs(&jobs, undo);
Q_FOREACH (KisStrokeJobData *job, jobs) {
strokesFacade()->addJob(id, job);
}
}
......@@ -90,6 +90,10 @@ public:
void performCancel(KisStrokeId id, bool strokeUndo);
void getCommandExecutionJobs(QVector<KisStrokeJobData*> *jobs, bool undo) const;
void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command *> &skipWhileOverride);
protected:
void addCommands(KisStrokeId id, bool undo) override;
......
......@@ -52,6 +52,10 @@ public:
return m_cropNode;
}
KUndo2CommandExtraData* clone() const override {
return new KisCropSavedExtraData(*this);
}
private:
Type m_type;
QRect m_cropRect;
......
......@@ -1369,6 +1369,11 @@ KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
&m_d->postExecutionUndoAdapter;
}
const KUndo2Command* KisImage::lastExecutedCommand() const
{
return m_d->undoStore->presentCommand();
}
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
......@@ -1747,6 +1752,21 @@ void KisImage::disableUIUpdates()
m_d->disableUIUpdateSignals.ref();
}
void KisImage::notifyBatchUpdateStarted()
{
m_d->signalRouter.emitNotifyBatchUpdateStarted();
}
void KisImage::notifyBatchUpdateEnded()
{
m_d->signalRouter.emitNotifyBatchUpdateEnded();
}
void KisImage::notifyUIUpdateCompleted(const QRect &rc)
{
notifyProjectionUpdated(rc);
}
QVector<QRect> KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
......
......@@ -415,6 +415,12 @@ public:
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override;
/**
* Return the lastly executed LoD0 command. It is effectively the same
* as to call undoAdapter()->presentCommand();
*/
const KUndo2Command* lastExecutedCommand() const;
/**
* Replace current undo store with the new one. The old store
* will be deleted.
......@@ -974,6 +980,29 @@ public Q_SLOTS:
*/
void disableUIUpdates() override;
/**
* Notify GUI about a bunch of updates planned. GUI is expected to wait
* until all the updates are completed, and render them on screen only
* in the very and of the batch.
*/
void notifyBatchUpdateStarted() override;
/**
* Notify GUI that batch update has been completed. Now GUI can start
* showing all of them on screen.
*/
void notifyBatchUpdateEnded() override;
/**
* Notify GUI that rect \p rc is now prepared in the image and
* GUI can read data from it.
*
* WARNING: GUI will read the data right in the handler of this
* signal, so exclusive access to the area must be guaranteed
* by the caller.
*/
void notifyUIUpdateCompleted(const QRect &rc) override;
/**
* \see disableUIUpdates
*/
......
......@@ -49,6 +49,12 @@ public:
virtual void disableUIUpdates() = 0;
virtual QVector<QRect> enableUIUpdates() = 0;
virtual void notifyBatchUpdateStarted() = 0;
virtual void notifyBatchUpdateEnded() = 0;
virtual void notifyUIUpdateCompleted(const QRect &rc) = 0;
virtual QRect bounds() const = 0;
virtual void disableDirtyRequests() = 0;
virtual void enableDirtyRequests() = 0;
......@@ -72,6 +78,7 @@ class KRITAIMAGE_EXPORT KisStrokeUndoFacade
public:
virtual ~KisStrokeUndoFacade();
virtual KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const = 0;
virtual const KUndo2Command* lastExecutedCommand() const = 0;
};
#endif /* __KIS_IMAGE_INTERFACES_H */
......@@ -30,7 +30,7 @@ KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name,
KisStrokeUndoFacade *undoFacade,
KUndo2CommandSP initCommand,
KUndo2CommandSP finishCommand)
: KisSimpleStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name),
: KisRunnableBasedStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name),
m_undo(undo),
m_initCommand(initCommand),
m_finishCommand(finishCommand),
......@@ -46,7 +46,7 @@ KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name,
KisStrokeStrategyUndoCommandBased::
KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs)
: KisSimpleStrokeStrategy(rhs),
: KisRunnableBasedStrokeStrategy(rhs),
m_undo(false),
m_initCommand(rhs.m_initCommand),
m_finishCommand(rhs.m_finishCommand),
......@@ -67,6 +67,10 @@ void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command,
{
if(!command) return;
if (MutatedCommandInterface *mutatedCommand = dynamic_cast<MutatedCommandInterface*>(command.data())) {
mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface());
}
if(undo) {
command->undo();
} else {
......@@ -115,8 +119,14 @@ void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback()
void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast<Data*>(data);
executeCommand(d->command, d->undo);
notifyCommandDone(d->command, d->sequentiality(), d->exclusivity());
if (d) {
executeCommand(d->command, d->undo);
notifyCommandDone(d->command, d->sequentiality(), d->exclusivity());
} else {
KisRunnableBasedStrokeStrategy::doStrokeCallback(data);
}
}
void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command,
......@@ -125,7 +135,7 @@ void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP comman
{
if (!command) return;
command->redo();
executeCommand(command, false);
notifyCommandDone(command, sequentiality, exclusivity);
}
......@@ -168,3 +178,8 @@ void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command
savedCommand->setMacroId(m_macroId);
}
}
KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const
{
return m_undoFacade;
}
......@@ -25,15 +25,35 @@
#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 KisSimpleStrokeStrategy
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,
......@@ -120,6 +140,8 @@ protected:
virtual void postProcessToplevelCommand(KUndo2Command *command);
KisStrokeUndoFacade* undoFacade() const;
private:
void executeCommand(KUndo2CommandSP command, bool undo);
......
......@@ -149,7 +149,6 @@ public:
void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action);
QMenu* popupActionsMenu() override;
void activatePrimaryAction() override;
void deactivatePrimaryAction() override;
void beginPrimaryAction(KoPointerEvent *event) override;
......@@ -233,9 +232,6 @@ private:
QList<KisNodeSP> fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive);
QScopedPointer<QMenu> m_contextMenu;
bool clearDevices(const QList<KisNodeSP> &nodes);
void transformClearedDevices();
void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset);
void endStroke();
void cancelStroke();
......@@ -249,45 +245,20 @@ private:
void commitChanges();
bool tryInitArgsFromNode(KisNodeSP node);
bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode);
void resetArgsForMode(ToolTransformArgs::TransformMode mode);
void initTransformMode(ToolTransformArgs::TransformMode mode);
void initGuiAfterTransformMode();
void initThumbnailImage(KisPaintDeviceSP previewDevice);
void updateSelectionPath();
void updateSelectionPath(const QPainterPath &selectionOutline);
void updateApplyResetAvailability();
void forceRepaintDelayedLayers(KisNodeSP root);
private:
ToolTransformArgs m_currentArgs;
bool m_actuallyMoveWhileSelected; // true <=> selection has been moved while clicked
KisPaintDeviceSP m_selectedPortionCache;
struct StrokeData {
StrokeData() {}
StrokeData(KisStrokeId strokeId) : m_strokeId(strokeId) {}
void clear() {
m_strokeId.clear();
m_clearedNodes.clear();
}
const KisStrokeId strokeId() const { return m_strokeId; }
void addClearedNode(KisNodeSP node) { m_clearedNodes.append(node); }
const QVector<KisNodeWSP>& clearedNodes() const { return m_clearedNodes; }
private:
KisStrokeId m_strokeId;
QVector<KisNodeWSP> m_clearedNodes;
};
StrokeData m_strokeData;
KisStrokeId m_strokeId;
bool m_workRecursively;
......@@ -342,7 +313,8 @@ private Q_SLOTS:
void slotRestartTransform();
void slotEditingFinished();
void slotPreviewDeviceGenerated(KisPaintDeviceSP device);
void slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args);
void slotPreviewDeviceGenerated(KisPaintDeviceSP device, const QPainterPath &selectionOutline);
// context menu options for updating the transform type
// this is to help with discoverability since come people can't find the tool options
......
......@@ -1165,40 +1165,9 @@ void KisToolTransformConfigWidget::slotSetWarpDensity(int value)
void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine)
{
if (pointsPerLine < 0) {
pointsPerLine = DEFAULT_POINTS_PER_LINE;
}
int nbPoints = pointsPerLine * pointsPerLine;
QVector<QPointF> origPoints(nbPoints);
QVector<QPointF> transfPoints(nbPoints);
qreal gridSpaceX, gridSpaceY;
if (nbPoints == 1) {
//there is actually no grid
origPoints[0] = m_transaction->originalCenterGeometric();
transfPoints[0] = m_transaction->originalCenterGeometric();
}
else if (nbPoints > 1) {
gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1);
gridSpaceY = m_transaction->originalRect().height() / (pointsPerLine - 1);
double y = m_transaction->originalRect().top();
for (int i = 0; i < pointsPerLine; ++i) {
double x = m_transaction->originalRect().left();
for (int j = 0 ; j < pointsPerLine; ++j) {
origPoints[i * pointsPerLine + j] = QPointF(x, y);
transfPoints[i * pointsPerLine + j] = QPointF(x, y);
x += gridSpaceX;
}
y += gridSpaceY;
}
}
ToolTransformArgs *config = m_transaction->currentConfig();
config->setDefaultPoints(nbPoints > 0);
config->setPoints(origPoints, transfPoints);
KisTransformUtils::setDefaultWarpPoints(pointsPerLine, m_transaction, config);
notifyConfigChanged();
}
......
......@@ -24,6 +24,28 @@
#include "tool_transform_args.h"
#include "kis_paint_device.h"
#include "kis_algebra_2d.h"
#include "transform_transaction_properties.h"
struct TransformTransactionPropertiesRegistrar {
TransformTransactionPropertiesRegistrar() {
qRegisterMetaType<TransformTransactionProperties>("TransformTransactionProperties");
}
};
static TransformTransactionPropertiesRegistrar __registrar1;
struct ToolTransformArgsRegistrar {
ToolTransformArgsRegistrar() {
qRegisterMetaType<ToolTransformArgs>("ToolTransformArgs");
}
};
static ToolTransformArgsRegistrar __registrar2;
struct QPainterPathRegistrar {
QPainterPathRegistrar() {
qRegisterMetaType<QPainterPath>("QPainterPath");
}
};
static QPainterPathRegistrar __registrar3;
const int KisTransformUtils::rotationHandleVisualRadius = 12;
......@@ -395,3 +417,74 @@ KisTransformUtils::AnchorHolder::~AnchorHolder() {
m_config->setTransformedCenter(m_config->transformedCenter() + diff);
}
void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine,
const TransformTransactionProperties *transaction,
ToolTransformArgs *config)
{
static const int DEFAULT_POINTS_PER_LINE = 3;
if (pointsPerLine < 0) {
pointsPerLine = DEFAULT_POINTS_PER_LINE;
}
int nbPoints = pointsPerLine * pointsPerLine;
QVector<QPointF> origPoints(nbPoints);
QVector<QPointF> transfPoints(nbPoints);
qreal gridSpaceX, gridSpaceY;
if (nbPoints == 1) {
//there is actually no grid
origPoints[0] = transaction->originalCenterGeometric();
transfPoints[0] = transaction->originalCenterGeometric();
}
else if (nbPoints > 1) {
gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1);
gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1);
double y = transaction->originalRect().top();
for (int i = 0; i < pointsPerLine; ++i) {
double x = transaction->originalRect().left();
for (int j = 0 ; j < pointsPerLine; ++j) {
origPoints[i * pointsPerLine + j] = QPointF(x, y);
transfPoints[i * pointsPerLine + j] = QPointF(x, y);
x += gridSpaceX;
}
y += gridSpaceY;
}
}
config->setDefaultPoints(nbPoints > 0);
config->setPoints(origPoints, transfPoints);
}
ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode,
const QString &filterId,
const TransformTransactionProperties &transaction)
{
ToolTransformArgs args;
args.setOriginalCenter(transaction.originalCenterGeometric());