File indexing completed on 2024-05-19 04:26:35
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 std::mem_fn(&KisStrokeJob::isOwnJob)); 0093 0094 Q_FOREACH (KisStrokeJobData *data, list) { 0095 it = m_jobsQueue.insert(it, new KisStrokeJob(m_dabStrategy.data(), data, worksOnLevelOfDetail(), true)); 0096 ++it; 0097 } 0098 } 0099 0100 KisStrokeJob* KisStroke::popOneJob() 0101 { 0102 KisStrokeJob *job = dequeue(); 0103 0104 if(job) { 0105 m_strokeInitialized = true; 0106 m_strokeSuspended = false; 0107 } 0108 0109 return job; 0110 } 0111 0112 KUndo2MagicString KisStroke::name() const 0113 { 0114 return m_strokeStrategy->name(); 0115 } 0116 0117 QString KisStroke::id() const 0118 { 0119 return m_strokeStrategy->id(); 0120 } 0121 0122 bool KisStroke::hasJobs() const 0123 { 0124 return !m_jobsQueue.isEmpty(); 0125 } 0126 0127 qint32 KisStroke::numJobs() const 0128 { 0129 return m_jobsQueue.size(); 0130 } 0131 0132 void KisStroke::endStroke() 0133 { 0134 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_strokeEnded); 0135 m_strokeEnded = true; 0136 0137 enqueue(m_finishStrategy.data(), m_strokeStrategy->createFinishData()); 0138 m_strokeStrategy->notifyUserEndedStroke(); 0139 } 0140 0141 /** 0142 * About cancelling the stroke 0143 * There may be four different states of the stroke, when cancel 0144 * is requested: 0145 * 1) Not initialized, has jobs -- just clear the queue 0146 * 2) Initialized, has jobs, not finished -- clear the queue, 0147 * enqueue the cancel job 0148 * 5) Initialized, no jobs, not finished -- enqueue the cancel job 0149 * 3) Initialized, has jobs, finished -- clear the queue, enqueue 0150 * the cancel job 0151 * 4) Initialized, no jobs, finished -- it's too late to cancel 0152 * anything 0153 * 6) Initialized, has jobs, cancelled -- cancelling twice is a permitted 0154 * operation, though it does nothing 0155 */ 0156 0157 void KisStroke::cancelStroke() 0158 { 0159 // case 6 0160 if (m_isCancelled) return; 0161 0162 const bool effectivelyInitialized = 0163 m_strokeInitialized || m_strokeStrategy->needsExplicitCancel(); 0164 0165 if(!effectivelyInitialized) { 0166 /** 0167 * Lod0 stroke cannot be suspended and !initialized at the 0168 * same time, because the suspend job is created iff the 0169 * stroke has already done some meaningful work. 0170 * 0171 * At the same time, LodN stroke can be prepended with a 0172 * 'suspend' job even when it has not been started yet. That 0173 * is obvious: we should suspend the other stroke before doing 0174 * anything else. 0175 */ 0176 KIS_ASSERT_RECOVER_NOOP(type() == LODN || 0177 sanityCheckAllJobsAreCancellable()); 0178 clearQueueOnCancel(); 0179 } 0180 else if(effectivelyInitialized && 0181 (!m_jobsQueue.isEmpty() || !m_strokeEnded)) { 0182 0183 m_strokeStrategy->tryCancelCurrentStrokeJobAsync(); 0184 0185 clearQueueOnCancel(); 0186 enqueue(m_cancelStrategy.data(), 0187 m_strokeStrategy->createCancelData()); 0188 } 0189 // else { 0190 // too late ... 0191 // } 0192 0193 m_isCancelled = true; 0194 m_strokeEnded = true; 0195 } 0196 0197 bool KisStroke::canCancel() const 0198 { 0199 return m_isCancelled || !m_strokeInitialized || 0200 !m_jobsQueue.isEmpty() || !m_strokeEnded; 0201 } 0202 0203 bool KisStroke::sanityCheckAllJobsAreCancellable() const 0204 { 0205 Q_FOREACH (KisStrokeJob *item, m_jobsQueue) { 0206 if (!item->isCancellable()) { 0207 return false; 0208 } 0209 } 0210 return true; 0211 } 0212 0213 void KisStroke::clearQueueOnCancel() 0214 { 0215 QQueue<KisStrokeJob*>::iterator it = m_jobsQueue.begin(); 0216 0217 while (it != m_jobsQueue.end()) { 0218 if ((*it)->isCancellable()) { 0219 delete (*it); 0220 it = m_jobsQueue.erase(it); 0221 } else { 0222 ++it; 0223 } 0224 } 0225 } 0226 0227 bool KisStroke::isInitialized() const 0228 { 0229 return m_strokeInitialized; 0230 } 0231 0232 bool KisStroke::isEnded() const 0233 { 0234 return m_strokeEnded; 0235 } 0236 0237 bool KisStroke::isCancelled() const 0238 { 0239 return m_isCancelled; 0240 } 0241 0242 bool KisStroke::isExclusive() const 0243 { 0244 return m_strokeStrategy->isExclusive(); 0245 } 0246 0247 bool KisStroke::supportsWrapAroundMode() const 0248 { 0249 return m_strokeStrategy->supportsWrapAroundMode(); 0250 } 0251 0252 int KisStroke::worksOnLevelOfDetail() const 0253 { 0254 return m_worksOnLevelOfDetail; 0255 } 0256 0257 bool KisStroke::canForgetAboutMe() const 0258 { 0259 return m_strokeStrategy->canForgetAboutMe(); 0260 } 0261 0262 bool KisStroke::isAsynchronouslyCancellable() const 0263 { 0264 return m_strokeStrategy->isAsynchronouslyCancellable(); 0265 } 0266 0267 bool KisStroke::clearsRedoOnStart() const 0268 { 0269 return m_strokeStrategy->clearsRedoOnStart(); 0270 } 0271 0272 qreal KisStroke::balancingRatioOverride() const 0273 { 0274 return m_strokeStrategy->balancingRatioOverride(); 0275 } 0276 0277 KisStrokeJobData::Sequentiality KisStroke::nextJobSequentiality() const 0278 { 0279 return !m_jobsQueue.isEmpty() ? 0280 m_jobsQueue.head()->sequentiality() : KisStrokeJobData::SEQUENTIAL; 0281 } 0282 0283 int KisStroke::nextJobLevelOfDetail() const 0284 { 0285 return !m_jobsQueue.isEmpty() ? 0286 m_jobsQueue.head()->levelOfDetail() : worksOnLevelOfDetail(); 0287 } 0288 0289 void KisStroke::enqueue(KisStrokeJobStrategy *strategy, 0290 KisStrokeJobData *data) 0291 { 0292 // factory methods can return null, if no action is needed 0293 if(!strategy) { 0294 delete data; 0295 return; 0296 } 0297 0298 m_jobsQueue.enqueue(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), true)); 0299 } 0300 0301 void KisStroke::prepend(KisStrokeJobStrategy *strategy, 0302 KisStrokeJobData *data, 0303 int levelOfDetail, 0304 bool isOwnJob) 0305 { 0306 // factory methods can return null, if no action is needed 0307 if(!strategy) { 0308 delete data; 0309 return; 0310 } 0311 0312 // LOG_MERGE_FIXME: 0313 Q_UNUSED(levelOfDetail); 0314 0315 m_jobsQueue.prepend(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), isOwnJob)); 0316 } 0317 0318 KisStrokeJob* KisStroke::dequeue() 0319 { 0320 return !m_jobsQueue.isEmpty() ? m_jobsQueue.dequeue() : 0; 0321 } 0322 0323 void KisStroke::setLodBuddy(KisStrokeSP buddy) 0324 { 0325 m_lodBuddy = buddy; 0326 } 0327 0328 KisStrokeSP KisStroke::lodBuddy() const 0329 { 0330 return m_lodBuddy; 0331 } 0332 0333 KisStroke::Type KisStroke::type() const 0334 { 0335 if (m_type == LOD0) { 0336 KIS_ASSERT_RECOVER_NOOP(m_lodBuddy && "LOD0 strokes must always have a buddy"); 0337 } else if (m_type == LODN) { 0338 KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail > 0 && "LODN strokes must work on LOD > 0!"); 0339 } else if (m_type == LEGACY) { 0340 KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail == 0 && "LEGACY strokes must work on LOD == 0!"); 0341 } 0342 0343 return m_type; 0344 }