File indexing completed on 2024-05-19 04:26:37

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     device->requestTimeSwitch(transactionTime);
0247 }
0248 
0249 void KisTransactionData::redo()
0250 {
0251     //KUndo2QStack calls redo(), so the first call needs to be blocked
0252     if (m_d->firstRedo) {
0253         m_d->firstRedo = false;
0254 
0255 
0256         possiblyResetOutlineCache();
0257         possiblyNotifySelectionChanged();
0258         return;
0259     }
0260 
0261 
0262 
0263     doFlattenUndoRedo(false);
0264     restoreSelectionOutlineCache(false);
0265 
0266     DEBUG_ACTION("Redo()");
0267 
0268     if (m_d->interstrokeInfo && m_d->interstrokeInfo->beginTransactionCommand) {
0269         m_d->interstrokeInfo->beginTransactionCommand->redo();
0270     }
0271 
0272     Q_ASSERT(m_d->memento);
0273     m_d->savedDataManager->rollforward(m_d->memento);
0274 
0275     if (m_d->newOffset != m_d->oldOffset) {
0276         m_d->moveDevice(m_d->newOffset);
0277     }
0278 
0279     if (m_d->interstrokeInfo && m_d->interstrokeInfo->endTransactionCommand) {
0280         m_d->interstrokeInfo->endTransactionCommand->redo();
0281     }
0282 
0283     m_d->possiblySwitchCurrentTime();
0284     startUpdates();
0285     possiblyNotifySelectionChanged();
0286 }
0287 
0288 void KisTransactionData::undo()
0289 {
0290     DEBUG_ACTION("Undo()");
0291 
0292     if (m_d->interstrokeInfo && m_d->interstrokeInfo->endTransactionCommand) {
0293         m_d->interstrokeInfo->endTransactionCommand->undo();
0294     }
0295 
0296     Q_ASSERT(m_d->memento);
0297     m_d->savedDataManager->rollback(m_d->memento);
0298 
0299     if (m_d->newOffset != m_d->oldOffset) {
0300         m_d->moveDevice(m_d->oldOffset);
0301     }
0302 
0303     if (m_d->interstrokeInfo && m_d->interstrokeInfo->beginTransactionCommand) {
0304         m_d->interstrokeInfo->beginTransactionCommand->undo();
0305     }
0306 
0307     restoreSelectionOutlineCache(true);
0308     doFlattenUndoRedo(true);
0309 
0310     m_d->possiblySwitchCurrentTime();
0311     startUpdates();
0312     possiblyNotifySelectionChanged();
0313 }
0314 
0315 void KisTransactionData::saveSelectionOutlineCache()
0316 {
0317     m_d->savedOutlineCacheValid = false;
0318 
0319     KisPixelSelectionSP pixelSelection =
0320         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0321 
0322     if (pixelSelection) {
0323         m_d->savedOutlineCacheValid = pixelSelection->outlineCacheValid();
0324         if (m_d->savedOutlineCacheValid) {
0325             m_d->savedOutlineCache = pixelSelection->outlineCache();
0326 
0327             possiblyResetOutlineCache();
0328         }
0329     }
0330 }
0331 
0332 void KisTransactionData::restoreSelectionOutlineCache(bool undo)
0333 {
0334     Q_UNUSED(undo);
0335     KisPixelSelectionSP pixelSelection =
0336         dynamic_cast<KisPixelSelection*>(m_d->device.data());
0337 
0338     if (pixelSelection) {
0339         bool savedOutlineCacheValid;
0340         QPainterPath savedOutlineCache;
0341 
0342         savedOutlineCacheValid = pixelSelection->outlineCacheValid();
0343         if (savedOutlineCacheValid) {
0344             savedOutlineCache = pixelSelection->outlineCache();
0345         }
0346 
0347         if (m_d->savedOutlineCacheValid) {
0348             pixelSelection->setOutlineCache(m_d->savedOutlineCache);
0349         } else {
0350             pixelSelection->invalidateOutlineCache();
0351         }
0352 
0353         m_d->savedOutlineCacheValid = savedOutlineCacheValid;
0354         if (m_d->savedOutlineCacheValid) {
0355             m_d->savedOutlineCache = savedOutlineCache;
0356         }
0357     }
0358 }