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 }