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 }