File indexing completed on 2024-05-12 15:58:45

0001 /*
0002  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_transaction_data.h"
0008 
0009 #include "kis_pixel_selection.h"
0010 #include "kis_paint_device.h"
0011 #include "kis_paint_device_frames_interface.h"
0012 #include "kis_datamanager.h"
0013 #include "kis_image.h"
0014 #include "KoColor.h"
0015 #include "KisTransactionWrapperFactory.h"
0016 #include "KisInterstrokeDataTransactionWrapperFactory.h"
0017 #include "kis_raster_keyframe_channel.h"
0018 #include "kis_image_config.h"
0019 #include <boost/optional.hpp>
0020 
0021 //#define DEBUG_TRANSACTIONS
0022 
0023 #ifdef DEBUG_TRANSACTIONS
0024 #    define DEBUG_ACTION(action) dbgKrita << action << "for" << m_d->device->dataManager()
0025 #else
0026 #    define DEBUG_ACTION(action)
0027 #endif
0028 
0029 
0030 struct OptionalInterstrokeInfo
0031 {
0032     QScopedPointer<KisTransactionWrapperFactory> factory;
0033     QScopedPointer<KUndo2Command> beginTransactionCommand;
0034     QScopedPointer<KUndo2Command> endTransactionCommand;
0035 };
0036 
0037 class Q_DECL_HIDDEN KisTransactionData::Private
0038 {
0039 public:
0040     KisPaintDeviceSP device;
0041     KisMementoSP memento;
0042     bool firstRedo;
0043     bool transactionFinished;
0044     QPoint oldOffset;
0045     QPoint newOffset;
0046 
0047     KoColor oldDefaultPixel;
0048     bool defaultPixelChanged = false;
0049 
0050     bool savedOutlineCacheValid;
0051     QPainterPath savedOutlineCache;
0052     QScopedPointer<KUndo2Command> flattenUndoCommand;
0053     bool resetSelectionOutlineCache;
0054 
0055     int transactionTime;
0056     int transactionFrameId;
0057     KisDataManagerSP savedDataManager;
0058 
0059     QScopedPointer<OptionalInterstrokeInfo> interstrokeInfo;
0060     bool suppressUpdates = false;
0061 
0062     void possiblySwitchCurrentTime();
0063     KisDataManagerSP dataManager();
0064     void moveDevice(const QPoint newOffset);
0065 };
0066 
0067 KisTransactionData::KisTransactionData(const KUndo2MagicString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KisTransactionWrapperFactory *interstrokeDataFactory, KUndo2Command* parent, bool suppressUpdates)
0068     : KUndo2Command(name, parent)
0069     , m_d(new Private())
0070 {
0071     m_d->resetSelectionOutlineCache = resetSelectionOutlineCache;
0072     m_d->suppressUpdates = suppressUpdates;
0073     setTimedID(-1);
0074 
0075     if (!interstrokeDataFactory && device->interstrokeData()) {
0076         interstrokeDataFactory = new KisInterstrokeDataTransactionWrapperFactory(0);
0077     }
0078 
0079     if (interstrokeDataFactory) {
0080         m_d->interstrokeInfo.reset(new OptionalInterstrokeInfo());
0081         m_d->interstrokeInfo->factory.reset(interstrokeDataFactory);
0082     }
0083 
0084     possiblyFlattenSelection(device);
0085     init(device);
0086     saveSelectionOutlineCache();
0087 }
0088 
0089 void KisTransactionData::init(KisPaintDeviceSP device)
0090 {
0091     m_d->device = device;
0092     DEBUG_ACTION("Transaction started");
0093 
0094     m_d->oldOffset = QPoint(device->x(), device->y());
0095     m_d->oldDefaultPixel = device->defaultPixel();
0096     m_d->firstRedo = true;
0097     m_d->transactionFinished = false;
0098 
0099     m_d->transactionTime = device->defaultBounds()->currentTime();
0100 
0101     if (m_d->interstrokeInfo) {
0102         m_d->interstrokeInfo->beginTransactionCommand.reset(m_d->interstrokeInfo->factory->createBeginTransactionCommand(m_d->device));
0103         if (m_d->interstrokeInfo->beginTransactionCommand) {
0104             m_d->interstrokeInfo->beginTransactionCommand->redo();
0105         }
0106     }
0107 
0108     m_d->transactionFrameId = device->framesInterface() ? device->framesInterface()->currentFrameId() : -1;
0109     m_d->savedDataManager = m_d->transactionFrameId >= 0 ?
0110         m_d->device->framesInterface()->frameDataManager(m_d->transactionFrameId) :
0111         m_d->device->dataManager();
0112     m_d->memento = m_d->savedDataManager->getMemento();
0113 }
0114 
0115 KisTransactionData::~KisTransactionData()
0116 {
0117     Q_ASSERT(m_d->memento);
0118     m_d->savedDataManager->purgeHistory(m_d->memento);
0119 
0120     delete m_d;
0121 }
0122 
0123 void KisTransactionData::Private::moveDevice(const QPoint newOffset)
0124 {
0125     if (transactionFrameId >= 0) {
0126         device->framesInterface()->setFrameOffset(transactionFrameId, newOffset);
0127     } else {
0128         device->moveTo(newOffset);
0129     }
0130 }
0131 
0132 void KisTransactionData::endTransaction()
0133 {
0134     if(!m_d->transactionFinished) {
0135         // make sure the time didn't change during the transaction
0136         KIS_ASSERT_RECOVER_RETURN(
0137             m_d->transactionTime == m_d->device->defaultBounds()->currentTime());
0138 
0139         DEBUG_ACTION("Transaction ended");
0140         m_d->transactionFinished = true;
0141         m_d->savedDataManager->commit();
0142         m_d->newOffset = QPoint(m_d->device->x(), m_d->device->y());
0143         m_d->defaultPixelChanged = m_d->oldDefaultPixel != m_d->device->defaultPixel();
0144 
0145         if (m_d->interstrokeInfo) {
0146             m_d->interstrokeInfo->endTransactionCommand.reset(m_d->interstrokeInfo->factory->createEndTransactionCommand());
0147             if (m_d->interstrokeInfo->endTransactionCommand) {
0148                 m_d->interstrokeInfo->endTransactionCommand->redo();
0149             }
0150             m_d->interstrokeInfo->factory.reset();
0151         }
0152     }
0153 }
0154 
0155 void KisTransactionData::startUpdates()
0156 {
0157     if (m_d->suppressUpdates) return;
0158 
0159     if (m_d->transactionFrameId == -1 ||
0160         m_d->transactionFrameId ==
0161         m_d->device->framesInterface()->currentFrameId()) {
0162 
0163         QRect rc;
0164         QRect mementoExtent = m_d->memento->extent();
0165 
0166         if (m_d->newOffset == m_d->oldOffset) {
0167             rc = mementoExtent.translated(m_d->device->x(), m_d->device->y());
0168         } else {
0169             QRect totalExtent =
0170                 m_d->savedDataManager->extent() | mementoExtent;
0171 
0172             rc = totalExtent.translated(m_d->oldOffset) |
0173                 totalExtent.translated(m_d->newOffset);
0174         }
0175 
0176         if (m_d->defaultPixelChanged) {
0177             rc |= m_d->device->defaultBounds()->bounds();
0178         }
0179 
0180         m_d->device->setDirty(rc);
0181     } else {
0182         m_d->device->framesInterface()->invalidateFrameCache(m_d->transactionFrameId);
0183     }
0184 }
0185 
0186 void KisTransactionData::possiblyNotifySelectionChanged()
0187 {
0188     KisPixelSelectionSP pixelSelection =
0189         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0190 
0191     KisSelectionSP selection;
0192     if (pixelSelection && (selection = pixelSelection->parentSelection())) {
0193         selection->notifySelectionChanged();
0194     }
0195 }
0196 
0197 void KisTransactionData::possiblyResetOutlineCache()
0198 {
0199     KisPixelSelectionSP pixelSelection;
0200 
0201     if (m_d->resetSelectionOutlineCache &&
0202         (pixelSelection =
0203          dynamic_cast<KisPixelSelection*>(m_d->device.data()))) {
0204 
0205         pixelSelection->invalidateOutlineCache();
0206     }
0207 }
0208 
0209 void KisTransactionData::possiblyFlattenSelection(KisPaintDeviceSP device)
0210 {
0211     KisPixelSelectionSP pixelSelection =
0212         dynamic_cast<KisPixelSelection*>(device.data());
0213 
0214     if (pixelSelection) {
0215         KisSelection *selection = pixelSelection->parentSelection().data();
0216         if (selection) {
0217             m_d->flattenUndoCommand.reset(selection->flatten());
0218 
0219             if (m_d->flattenUndoCommand) {
0220                 m_d->flattenUndoCommand->redo();
0221             }
0222         }
0223     }
0224 }
0225 
0226 void KisTransactionData::doFlattenUndoRedo(bool undo)
0227 {
0228     KisPixelSelectionSP pixelSelection =
0229         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0230 
0231     if (pixelSelection) {
0232         if (m_d->flattenUndoCommand) {
0233             if (undo) {
0234                 m_d->flattenUndoCommand->undo();
0235             } else {
0236                 m_d->flattenUndoCommand->redo();
0237             }
0238         }
0239     }
0240 }
0241 
0242 void KisTransactionData::Private::possiblySwitchCurrentTime()
0243 {
0244     if (device->defaultBounds()->currentTime() == transactionTime) return;
0245 
0246     qWarning() << "WARNING: undo command has been executed, when another frame has been active. That shouldn't have happened.";
0247     device->requestTimeSwitch(transactionTime);
0248 }
0249 
0250 void KisTransactionData::redo()
0251 {
0252     //KUndo2QStack calls redo(), so the first call needs to be blocked
0253     if (m_d->firstRedo) {
0254         m_d->firstRedo = false;
0255 
0256 
0257         possiblyResetOutlineCache();
0258         possiblyNotifySelectionChanged();
0259         return;
0260     }
0261 
0262 
0263 
0264     doFlattenUndoRedo(false);
0265     restoreSelectionOutlineCache(false);
0266 
0267     DEBUG_ACTION("Redo()");
0268 
0269     if (m_d->interstrokeInfo && m_d->interstrokeInfo->beginTransactionCommand) {
0270         m_d->interstrokeInfo->beginTransactionCommand->redo();
0271     }
0272 
0273     Q_ASSERT(m_d->memento);
0274     m_d->savedDataManager->rollforward(m_d->memento);
0275 
0276     if (m_d->newOffset != m_d->oldOffset) {
0277         m_d->moveDevice(m_d->newOffset);
0278     }
0279 
0280     if (m_d->interstrokeInfo && m_d->interstrokeInfo->endTransactionCommand) {
0281         m_d->interstrokeInfo->endTransactionCommand->redo();
0282     }
0283 
0284     m_d->possiblySwitchCurrentTime();
0285     startUpdates();
0286     possiblyNotifySelectionChanged();
0287 }
0288 
0289 void KisTransactionData::undo()
0290 {
0291     DEBUG_ACTION("Undo()");
0292 
0293     if (m_d->interstrokeInfo && m_d->interstrokeInfo->endTransactionCommand) {
0294         m_d->interstrokeInfo->endTransactionCommand->undo();
0295     }
0296 
0297     Q_ASSERT(m_d->memento);
0298     m_d->savedDataManager->rollback(m_d->memento);
0299 
0300     if (m_d->newOffset != m_d->oldOffset) {
0301         m_d->moveDevice(m_d->oldOffset);
0302     }
0303 
0304     if (m_d->interstrokeInfo && m_d->interstrokeInfo->beginTransactionCommand) {
0305         m_d->interstrokeInfo->beginTransactionCommand->undo();
0306     }
0307 
0308     restoreSelectionOutlineCache(true);
0309     doFlattenUndoRedo(true);
0310 
0311     m_d->possiblySwitchCurrentTime();
0312     startUpdates();
0313     possiblyNotifySelectionChanged();
0314 }
0315 
0316 void KisTransactionData::saveSelectionOutlineCache()
0317 {
0318     m_d->savedOutlineCacheValid = false;
0319 
0320     KisPixelSelectionSP pixelSelection =
0321         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0322 
0323     if (pixelSelection) {
0324         m_d->savedOutlineCacheValid = pixelSelection->outlineCacheValid();
0325         if (m_d->savedOutlineCacheValid) {
0326             m_d->savedOutlineCache = pixelSelection->outlineCache();
0327 
0328             possiblyResetOutlineCache();
0329         }
0330     }
0331 }
0332 
0333 void KisTransactionData::restoreSelectionOutlineCache(bool undo)
0334 {
0335     Q_UNUSED(undo);
0336     KisPixelSelectionSP pixelSelection =
0337         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0338 
0339     if (pixelSelection) {
0340         bool savedOutlineCacheValid;
0341         QPainterPath savedOutlineCache;
0342 
0343         savedOutlineCacheValid = pixelSelection->outlineCacheValid();
0344         if (savedOutlineCacheValid) {
0345             savedOutlineCache = pixelSelection->outlineCache();
0346         }
0347 
0348         if (m_d->savedOutlineCacheValid) {
0349             pixelSelection->setOutlineCache(m_d->savedOutlineCache);
0350         } else {
0351             pixelSelection->invalidateOutlineCache();
0352         }
0353 
0354         m_d->savedOutlineCacheValid = savedOutlineCacheValid;
0355         if (m_d->savedOutlineCacheValid) {
0356             m_d->savedOutlineCache = savedOutlineCache;
0357         }
0358     }
0359 }