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 }