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 }