File indexing completed on 2024-05-12 15:58:43
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_stroke.h" 0008 0009 #include "kis_stroke_strategy.h" 0010 0011 0012 KisStroke::KisStroke(KisStrokeStrategy *strokeStrategy, Type type, int levelOfDetail) 0013 : m_strokeStrategy(strokeStrategy), 0014 m_strokeInitialized(false), 0015 m_strokeEnded(false), 0016 m_strokeSuspended(false), 0017 m_isCancelled(false), 0018 m_worksOnLevelOfDetail(levelOfDetail), 0019 m_type(type) 0020 { 0021 m_initStrategy.reset(m_strokeStrategy->createInitStrategy()); 0022 m_dabStrategy.reset(m_strokeStrategy->createDabStrategy()); 0023 m_cancelStrategy.reset(m_strokeStrategy->createCancelStrategy()); 0024 m_finishStrategy.reset(m_strokeStrategy->createFinishStrategy()); 0025 m_suspendStrategy.reset(m_strokeStrategy->createSuspendStrategy()); 0026 m_resumeStrategy.reset(m_strokeStrategy->createResumeStrategy()); 0027 0028 m_strokeStrategy->notifyUserStartedStroke(); 0029 0030 if(!m_initStrategy) { 0031 m_strokeInitialized = true; 0032 } 0033 else { 0034 enqueue(m_initStrategy.data(), m_strokeStrategy->createInitData()); 0035 } 0036 } 0037 0038 KisStroke::~KisStroke() 0039 { 0040 Q_ASSERT(m_strokeEnded); 0041 Q_ASSERT(m_jobsQueue.isEmpty()); 0042 } 0043 0044 bool KisStroke::supportsSuspension() 0045 { 0046 return !m_strokeInitialized || (m_suspendStrategy && m_resumeStrategy); 0047 } 0048 0049 void KisStroke::suspendStroke(KisStrokeSP recipient) 0050 { 0051 if (!m_strokeInitialized || m_strokeSuspended || 0052 (m_strokeEnded && !hasJobs())) { 0053 0054 return; 0055 } 0056 0057 KIS_ASSERT_RECOVER_NOOP(m_suspendStrategy && m_resumeStrategy); 0058 0059 prepend(m_resumeStrategy.data(), 0060 m_strokeStrategy->createResumeData(), 0061 worksOnLevelOfDetail(), false); 0062 0063 recipient->prepend(m_suspendStrategy.data(), 0064 m_strokeStrategy->createSuspendData(), 0065 worksOnLevelOfDetail(), false); 0066 0067 m_strokeSuspended = true; 0068 } 0069 0070 void KisStroke::addJob(KisStrokeJobData *data) 0071 { 0072 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeEnded); 0073 enqueue(m_dabStrategy.data(), data); 0074 } 0075 0076 void KisStroke::addMutatedJobs(const QVector<KisStrokeJobData *> list) 0077 { 0078 // factory methods can return null, if no action is needed 0079 if (!m_dabStrategy) { 0080 qDeleteAll(list); 0081 return; 0082 } 0083 0084 // Find first non-alien (non-suspend/non-resume) job 0085 // 0086 // Please note that this algorithm will stop working at the day we start 0087 // adding alien jobs not to the beginning of the stroke, but to other places. 0088 // Right now both suspend and resume jobs are added to the beginning of 0089 // the stroke. 0090 0091 auto it = std::find_if(m_jobsQueue.begin(), m_jobsQueue.end(), 0092 [] (KisStrokeJob *job) { 0093 return job->isOwnJob(); 0094 }); 0095 0096 0097 Q_FOREACH (KisStrokeJobData *data, list) { 0098 it = m_jobsQueue.insert(it, new KisStrokeJob(m_dabStrategy.data(), data, worksOnLevelOfDetail(), true)); 0099 ++it; 0100 } 0101 } 0102 0103 KisStrokeJob* KisStroke::popOneJob() 0104 { 0105 KisStrokeJob *job = dequeue(); 0106 0107 if(job) { 0108 m_strokeInitialized = true; 0109 m_strokeSuspended = false; 0110 } 0111 0112 return job; 0113 } 0114 0115 KUndo2MagicString KisStroke::name() const 0116 { 0117 return m_strokeStrategy->name(); 0118 } 0119 0120 QString KisStroke::id() const 0121 { 0122 return m_strokeStrategy->id(); 0123 } 0124 0125 bool KisStroke::hasJobs() const 0126 { 0127 return !m_jobsQueue.isEmpty(); 0128 } 0129 0130 qint32 KisStroke::numJobs() const 0131 { 0132 return m_jobsQueue.size(); 0133 } 0134 0135 void KisStroke::endStroke() 0136 { 0137 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_strokeEnded); 0138 m_strokeEnded = true; 0139 0140 enqueue(m_finishStrategy.data(), m_strokeStrategy->createFinishData()); 0141 m_strokeStrategy->notifyUserEndedStroke(); 0142 } 0143 0144 /** 0145 * About cancelling the stroke 0146 * There may be four different states of the stroke, when cancel 0147 * is requested: 0148 * 1) Not initialized, has jobs -- just clear the queue 0149 * 2) Initialized, has jobs, not finished -- clear the queue, 0150 * enqueue the cancel job 0151 * 5) Initialized, no jobs, not finished -- enqueue the cancel job 0152 * 3) Initialized, has jobs, finished -- clear the queue, enqueue 0153 * the cancel job 0154 * 4) Initialized, no jobs, finished -- it's too late to cancel 0155 * anything 0156 * 6) Initialized, has jobs, cancelled -- cancelling twice is a permitted 0157 * operation, though it does nothing 0158 */ 0159 0160 void KisStroke::cancelStroke() 0161 { 0162 // case 6 0163 if (m_isCancelled) return; 0164 0165 const bool effectivelyInitialized = 0166 m_strokeInitialized || m_strokeStrategy->needsExplicitCancel(); 0167 0168 if(!effectivelyInitialized) { 0169 /** 0170 * Lod0 stroke cannot be suspended and !initialized at the 0171 * same time, because the suspend job is created iff the 0172 * stroke has already done some meaningful work. 0173 * 0174 * At the same time, LodN stroke can be prepended with a 0175 * 'suspend' job even when it has not been started yet. That 0176 * is obvious: we should suspend the other stroke before doing 0177 * anything else. 0178 */ 0179 KIS_ASSERT_RECOVER_NOOP(type() == LODN || 0180 sanityCheckAllJobsAreCancellable()); 0181 clearQueueOnCancel(); 0182 } 0183 else if(effectivelyInitialized && 0184 (!m_jobsQueue.isEmpty() || !m_strokeEnded)) { 0185 0186 clearQueueOnCancel(); 0187 enqueue(m_cancelStrategy.data(), 0188 m_strokeStrategy->createCancelData()); 0189 } 0190 // else { 0191 // too late ... 0192 // } 0193 0194 m_isCancelled = true; 0195 m_strokeEnded = true; 0196 } 0197 0198 bool KisStroke::canCancel() const 0199 { 0200 return m_isCancelled || !m_strokeInitialized || 0201 !m_jobsQueue.isEmpty() || !m_strokeEnded; 0202 } 0203 0204 bool KisStroke::sanityCheckAllJobsAreCancellable() const 0205 { 0206 Q_FOREACH (KisStrokeJob *item, m_jobsQueue) { 0207 if (!item->isCancellable()) { 0208 return false; 0209 } 0210 } 0211 return true; 0212 } 0213 0214 void KisStroke::clearQueueOnCancel() 0215 { 0216 QQueue<KisStrokeJob*>::iterator it = m_jobsQueue.begin(); 0217 0218 while (it != m_jobsQueue.end()) { 0219 if ((*it)->isCancellable()) { 0220 delete (*it); 0221 it = m_jobsQueue.erase(it); 0222 } else { 0223 ++it; 0224 } 0225 } 0226 } 0227 0228 bool KisStroke::isInitialized() const 0229 { 0230 return m_strokeInitialized; 0231 } 0232 0233 bool KisStroke::isEnded() const 0234 { 0235 return m_strokeEnded; 0236 } 0237 0238 bool KisStroke::isCancelled() const 0239 { 0240 return m_isCancelled; 0241 } 0242 0243 bool KisStroke::isExclusive() const 0244 { 0245 return m_strokeStrategy->isExclusive(); 0246 } 0247 0248 bool KisStroke::supportsWrapAroundMode() const 0249 { 0250 return m_strokeStrategy->supportsWrapAroundMode(); 0251 } 0252 0253 int KisStroke::worksOnLevelOfDetail() const 0254 { 0255 return m_worksOnLevelOfDetail; 0256 } 0257 0258 bool KisStroke::canForgetAboutMe() const 0259 { 0260 return m_strokeStrategy->canForgetAboutMe(); 0261 } 0262 0263 bool KisStroke::isAsynchronouslyCancellable() const 0264 { 0265 return m_strokeStrategy->isAsynchronouslyCancellable(); 0266 } 0267 0268 bool KisStroke::clearsRedoOnStart() const 0269 { 0270 return m_strokeStrategy->clearsRedoOnStart(); 0271 } 0272 0273 qreal KisStroke::balancingRatioOverride() const 0274 { 0275 return m_strokeStrategy->balancingRatioOverride(); 0276 } 0277 0278 KisStrokeJobData::Sequentiality KisStroke::nextJobSequentiality() const 0279 { 0280 return !m_jobsQueue.isEmpty() ? 0281 m_jobsQueue.head()->sequentiality() : KisStrokeJobData::SEQUENTIAL; 0282 } 0283 0284 int KisStroke::nextJobLevelOfDetail() const 0285 { 0286 return !m_jobsQueue.isEmpty() ? 0287 m_jobsQueue.head()->levelOfDetail() : worksOnLevelOfDetail(); 0288 } 0289 0290 void KisStroke::enqueue(KisStrokeJobStrategy *strategy, 0291 KisStrokeJobData *data) 0292 { 0293 // factory methods can return null, if no action is needed 0294 if(!strategy) { 0295 delete data; 0296 return; 0297 } 0298 0299 m_jobsQueue.enqueue(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), true)); 0300 } 0301 0302 void KisStroke::prepend(KisStrokeJobStrategy *strategy, 0303 KisStrokeJobData *data, 0304 int levelOfDetail, 0305 bool isOwnJob) 0306 { 0307 // factory methods can return null, if no action is needed 0308 if(!strategy) { 0309 delete data; 0310 return; 0311 } 0312 0313 // LOG_MERGE_FIXME: 0314 Q_UNUSED(levelOfDetail); 0315 0316 m_jobsQueue.prepend(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), isOwnJob)); 0317 } 0318 0319 KisStrokeJob* KisStroke::dequeue() 0320 { 0321 return !m_jobsQueue.isEmpty() ? m_jobsQueue.dequeue() : 0; 0322 } 0323 0324 void KisStroke::setLodBuddy(KisStrokeSP buddy) 0325 { 0326 m_lodBuddy = buddy; 0327 } 0328 0329 KisStrokeSP KisStroke::lodBuddy() const 0330 { 0331 return m_lodBuddy; 0332 } 0333 0334 KisStroke::Type KisStroke::type() const 0335 { 0336 if (m_type == LOD0) { 0337 KIS_ASSERT_RECOVER_NOOP(m_lodBuddy && "LOD0 strokes must always have a buddy"); 0338 } else if (m_type == LODN) { 0339 KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail > 0 && "LODN strokes must work on LOD > 0!"); 0340 } else if (m_type == LEGACY) { 0341 KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail == 0 && "LEGACY strokes must work on LOD == 0!"); 0342 } 0343 0344 return m_type; 0345 }