File indexing completed on 2024-12-22 04:14:45
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Jouni Pentikäinen <joupent@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KisTimeBasedItemModel.h" 0008 0009 #include <QPointer> 0010 #include <kis_config.h> 0011 0012 #include "kis_animation_frame_cache.h" 0013 #include "KisCanvasAnimationState.h" 0014 #include "animation/KisFrameDisplayProxy.h" 0015 #include "kis_signal_compressor_with_param.h" 0016 #include "kis_image.h" 0017 #include "kis_image_animation_interface.h" 0018 #include "kis_time_span.h" 0019 #include "KisAnimUtils.h" 0020 #include "kis_keyframe_channel.h" 0021 #include "kis_raster_keyframe_channel.h" 0022 #include "kis_processing_applicator.h" 0023 #include "KisImageBarrierLock.h" 0024 #include "commands_new/kis_switch_current_time_command.h" 0025 #include "kis_command_utils.h" 0026 #include "KisPart.h" 0027 #include "KisPlaybackEngine.h" 0028 #include "kis_animation_cache_populator.h" 0029 0030 struct KisTimeBasedItemModel::Private 0031 { 0032 Private() 0033 : animationPlayer(0) 0034 , document(nullptr) 0035 , numFramesOverride(0) 0036 , activeFrameIndex(0) 0037 , scrubInProgress(false) 0038 , scrubStartFrame(-1) 0039 , shouldReturnToPlay(false) 0040 , scrubHeaderMin(0) 0041 , scrubHeaderMax(0) 0042 {} 0043 0044 KisImageWSP image; 0045 KisAnimationFrameCacheWSP framesCache; 0046 QPointer<KisCanvasAnimationState> animationPlayer; 0047 KisDocument* document; 0048 0049 QVector<bool> cachedFrames; 0050 0051 int numFramesOverride; 0052 int activeFrameIndex; 0053 0054 bool scrubInProgress; 0055 int scrubStartFrame; 0056 bool shouldReturnToPlay; 0057 0058 QScopedPointer<KisSignalCompressorWithParam<int>> scrubHeaderUpdateCompressor; 0059 int scrubHeaderMin; 0060 int scrubHeaderMax; 0061 0062 int baseNumFrames() const { 0063 0064 auto imageSP = image.toStrongRef(); 0065 if (!imageSP) return 0; 0066 0067 KisImageAnimationInterface *i = imageSP->animationInterface(); 0068 if (!i) return 1; 0069 0070 return i->totalLength(); 0071 } 0072 0073 int effectiveNumFrames() const { 0074 if (image.isNull()) return 0; 0075 0076 return qMax(baseNumFrames(), numFramesOverride); 0077 } 0078 0079 int framesPerSecond() { 0080 return image->animationInterface()->framerate(); 0081 } 0082 0083 bool withinClipRange(const int time) { 0084 if (!image) { 0085 return true; 0086 } 0087 0088 KisTimeSpan clipRange = image->animationInterface()->documentPlaybackRange(); 0089 return clipRange.contains(time); 0090 } 0091 }; 0092 0093 KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent) 0094 : QAbstractTableModel(parent) 0095 , m_d(new Private()) 0096 { 0097 KisConfig cfg(true); 0098 0099 using namespace std::placeholders; 0100 0101 std::function<void (int)> scrubHorizHeaderUpdateCallback( 0102 std::bind(&KisTimeBasedItemModel::scrubHorizontalHeaderUpdate, this, _1)); 0103 0104 m_d->scrubHeaderUpdateCompressor.reset( 0105 new KisSignalCompressorWithParam<int>(100, scrubHorizHeaderUpdateCallback, KisSignalCompressor::FIRST_ACTIVE)); 0106 } 0107 0108 KisTimeBasedItemModel::~KisTimeBasedItemModel() 0109 {} 0110 0111 void KisTimeBasedItemModel::setImage(KisImageWSP p_image) 0112 { 0113 if (m_d->image == p_image ) { 0114 return; 0115 } 0116 0117 beginResetModel(); 0118 0119 if (m_d->image) { 0120 //Disconnect old image.. 0121 const KisImageAnimationInterface *ai = m_d->image->animationInterface(); 0122 ai->disconnect(this); 0123 } 0124 0125 m_d->image = p_image; 0126 m_d->numFramesOverride = m_d->effectiveNumFrames(); 0127 0128 if (m_d->image) { 0129 KisImageAnimationInterface *ai = m_d->image->animationInterface(); 0130 0131 connect(ai, SIGNAL(sigFramerateChanged()), this, SLOT(slotFramerateChanged())); 0132 connect(ai, SIGNAL(sigUiTimeChanged(int)), this, SLOT(slotCurrentTimeChanged(int))); 0133 connect(ai, SIGNAL(sigPlaybackRangeChanged()), this, SLOT(slotPlaybackRangeChanged())); 0134 } 0135 0136 endResetModel(); 0137 } 0138 0139 void KisTimeBasedItemModel::setFrameCache(KisAnimationFrameCacheSP cache) 0140 { 0141 if (KisAnimationFrameCacheSP(m_d->framesCache) == cache) return; 0142 0143 if (m_d->framesCache) { 0144 m_d->framesCache->disconnect(this); 0145 } 0146 0147 m_d->framesCache = cache; 0148 0149 if (m_d->framesCache) { 0150 connect(m_d->framesCache, SIGNAL(changed()), SLOT(slotCacheChanged())); 0151 } 0152 } 0153 0154 bool KisTimeBasedItemModel::isFrameCached(const int frame) 0155 { 0156 return m_d->framesCache && m_d->framesCache->frameStatus(frame) == KisAnimationFrameCache::Cached; 0157 } 0158 0159 void KisTimeBasedItemModel::setAnimationPlayer(KisCanvasAnimationState *player) 0160 { 0161 if (m_d->animationPlayer == player) return; 0162 0163 if (m_d->animationPlayer) { 0164 m_d->animationPlayer->disconnect(this); 0165 } 0166 0167 m_d->animationPlayer = player; 0168 0169 if (m_d->animationPlayer) { 0170 connect(m_d->animationPlayer, SIGNAL(sigPlaybackStateChanged(PlaybackState)), SLOT(slotPlaybackStateChanged(PlaybackState))); 0171 connect(m_d->animationPlayer, SIGNAL(sigFrameChanged()), SLOT(slotPlaybackFrameChanged())); 0172 0173 const int frame = player ? player->displayProxy()->activeFrame() : m_d->image->animationInterface()->currentUITime(); 0174 setHeaderData(frame, Qt::Horizontal, true, ActiveFrameRole); 0175 0176 // only prioritize the cache, no seek operation to prevent audio from playing 0177 setHeaderData(frame, Qt::Horizontal, QVariant(int(SEEK_NONE)), ScrubToRole); 0178 } 0179 } 0180 0181 void KisTimeBasedItemModel::setDocument(KisDocument *document) 0182 { 0183 if (m_d->document == document) return; 0184 0185 m_d->document = document; 0186 } 0187 0188 KisDocument* KisTimeBasedItemModel::document() const 0189 { 0190 return m_d->document; 0191 } 0192 0193 void KisTimeBasedItemModel::setLastVisibleFrame(int time) 0194 { 0195 const int growThreshold = m_d->effectiveNumFrames() - 1; 0196 const int growValue = time + 8; 0197 0198 const int shrinkThreshold = m_d->effectiveNumFrames() - 3; 0199 const int shrinkValue = qMax(m_d->baseNumFrames(), qMin(growValue, shrinkThreshold)); 0200 const bool canShrink = m_d->baseNumFrames() < m_d->effectiveNumFrames(); 0201 0202 if (time >= growThreshold) { 0203 beginInsertColumns(QModelIndex(), m_d->effectiveNumFrames(), growValue - 1); 0204 m_d->numFramesOverride = growValue; 0205 endInsertColumns(); 0206 } else if (time < shrinkThreshold && canShrink) { 0207 beginRemoveColumns(QModelIndex(), shrinkValue, m_d->effectiveNumFrames() - 1); 0208 m_d->numFramesOverride = shrinkValue; 0209 endRemoveColumns(); 0210 } 0211 } 0212 0213 int KisTimeBasedItemModel::columnCount(const QModelIndex &parent) const 0214 { 0215 Q_UNUSED(parent); 0216 return m_d->numFramesOverride; 0217 } 0218 0219 QVariant KisTimeBasedItemModel::data(const QModelIndex &index, int role) const 0220 { 0221 switch (role) { 0222 case ActiveFrameRole: { 0223 return index.column() == m_d->activeFrameIndex; 0224 } 0225 case CloneOfActiveFrame: { 0226 return cloneOfActiveFrame(index); 0227 } 0228 case CloneCount: { 0229 return cloneCount(index); 0230 } 0231 case WithinClipRange: 0232 return m_d->withinClipRange(index.column()); 0233 } 0234 0235 return QVariant(); 0236 } 0237 0238 bool KisTimeBasedItemModel::setData(const QModelIndex &index, const QVariant &value, int role) 0239 { 0240 if (!index.isValid()) return false; 0241 0242 switch (role) { 0243 case ActiveFrameRole: 0244 setHeaderData(index.column(), Qt::Horizontal, value, role); 0245 break; 0246 case ScrubToRole: 0247 setHeaderData(index.column(), Qt::Horizontal, value, role); 0248 break; 0249 } 0250 0251 return false; 0252 } 0253 0254 QVariant KisTimeBasedItemModel::headerData(int section, Qt::Orientation orientation, int role) const 0255 { 0256 if (orientation == Qt::Horizontal) { 0257 switch (role) { 0258 case ActiveFrameRole: 0259 return section == m_d->activeFrameIndex; 0260 case FrameCachedRole: 0261 return m_d->cachedFrames.size() > section ? m_d->cachedFrames[section] : false; 0262 case FramesPerSecondRole: 0263 return m_d->framesPerSecond(); 0264 case WithinClipRange: 0265 return m_d->withinClipRange(section); 0266 } 0267 } 0268 0269 return QVariant(); 0270 } 0271 0272 bool KisTimeBasedItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) 0273 { 0274 auto prioritizeCache = [this](int frame){ 0275 if (m_d->image) { 0276 if(!isFrameCached(frame)) { 0277 KisPart::instance()->prioritizeFrameForCache(m_d->image, frame); 0278 } 0279 } 0280 }; 0281 0282 0283 if (orientation == Qt::Horizontal) { 0284 switch (role) { 0285 case ActiveFrameRole: 0286 if (value.toBool() && 0287 section != m_d->activeFrameIndex) { 0288 0289 int prevFrame = m_d->activeFrameIndex; 0290 m_d->activeFrameIndex = section; 0291 0292 /** 0293 * Optimization Hack Alert: 0294 * 0295 * ideally, we should emit all four signals, but... The 0296 * point is this code is used in a tight loop during 0297 * playback, so it should run as fast as possible. To tell 0298 * the story short, commenting out these three lines makes 0299 * playback run 15% faster ;) 0300 */ 0301 0302 if (m_d->scrubInProgress) { 0303 emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); 0304 0305 /* 0306 * In order to try to correct rendering issues while preserving performance, we will 0307 * defer updates just long enough that visual artifacts aren't majorly noticeable. 0308 * By using a signal compressor, we're going to update the range of columns between 0309 * min / max. That min max is reset every time the update occurs. This should fix 0310 * rendering issues to a configurable framerate. 0311 */ 0312 m_d->scrubHeaderMin = qMin(m_d->activeFrameIndex, m_d->scrubHeaderMin); 0313 m_d->scrubHeaderMax = qMax(m_d->activeFrameIndex, m_d->scrubHeaderMax); 0314 m_d->scrubHeaderUpdateCompressor->start(m_d->activeFrameIndex); 0315 0316 // vvvvvvvvvvvvvvvvvvvvv Read above comment.. This fixes all timeline rendering issues, but at what cost??? 0317 //emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); 0318 //emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); 0319 //emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); 0320 } else { 0321 emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); 0322 emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); 0323 emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); 0324 emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); 0325 } 0326 } 0327 break; 0328 case ScrubToRole: 0329 SeekOptionFlags seekFlags = SeekOptionFlags(value.toInt()); 0330 prioritizeCache(m_d->activeFrameIndex); 0331 if (!m_d->image->hasUpdatesRunning()) { 0332 KisPart::instance()->playbackEngine()->seek(m_d->activeFrameIndex, seekFlags); 0333 } 0334 break; 0335 } 0336 } 0337 0338 return false; 0339 } 0340 0341 void KisTimeBasedItemModel::scrubHorizontalHeaderUpdate(int activeColumn) 0342 { 0343 emit headerDataChanged (Qt::Horizontal, m_d->scrubHeaderMin, m_d->scrubHeaderMax); 0344 m_d->scrubHeaderMin = activeColumn; 0345 m_d->scrubHeaderMax = activeColumn; 0346 } 0347 0348 bool KisTimeBasedItemModel::removeFrames(const QModelIndexList &indexes) 0349 { 0350 KisAnimUtils::FrameItemList frameItems; 0351 0352 { 0353 KisImageBarrierLock locker(m_d->image); 0354 0355 Q_FOREACH (const QModelIndex &index, indexes) { 0356 int time = index.column(); 0357 Q_FOREACH(KisKeyframeChannel *channel, channelsAt(index)) { 0358 if (channel->keyframeAt(time)) { 0359 frameItems << KisAnimUtils::FrameItem(channel->node(), channel->id(), index.column()); 0360 } 0361 } 0362 } 0363 } 0364 0365 if (frameItems.isEmpty()) return false; 0366 0367 KisAnimUtils::removeKeyframes(m_d->image, frameItems); 0368 0369 return true; 0370 } 0371 0372 KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, 0373 const QPoint &offset, 0374 bool copyFrames, 0375 bool moveEmptyFrames, 0376 KUndo2Command *parentCommand) 0377 { 0378 if (srcIndexes.isEmpty()) return 0; 0379 if (offset.isNull()) return 0; 0380 0381 KisAnimUtils::sortPointsForSafeMove(&srcIndexes, offset); 0382 0383 KisAnimUtils::FrameItemList srcFrameItems; 0384 KisAnimUtils::FrameItemList dstFrameItems; 0385 0386 Q_FOREACH (const QModelIndex &srcIndex, srcIndexes) { 0387 QModelIndex dstIndex = index( 0388 srcIndex.row() + offset.y(), 0389 srcIndex.column() + offset.x()); 0390 0391 KisNodeSP srcNode = nodeAt(srcIndex); 0392 KisNodeSP dstNode = nodeAt(dstIndex); 0393 if (!srcNode || !dstNode) return 0; 0394 0395 Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { 0396 if (moveEmptyFrames || channel->keyframeAt(srcIndex.column())) { 0397 srcFrameItems << KisAnimUtils::FrameItem(srcNode, channel->id(), srcIndex.column()); 0398 dstFrameItems << KisAnimUtils::FrameItem(dstNode, channel->id(), dstIndex.column()); 0399 } 0400 } 0401 } 0402 0403 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0); 0404 if (srcFrameItems.isEmpty()) return 0; 0405 0406 return 0407 KisAnimUtils::createMoveKeyframesCommand(srcFrameItems, 0408 dstFrameItems, 0409 copyFrames, 0410 moveEmptyFrames, 0411 parentCommand); 0412 } 0413 0414 bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indicesToRemove) 0415 { 0416 if (indicesToRemove.isEmpty()) return true; 0417 0418 std::sort(indicesToRemove.begin(), indicesToRemove.end(), 0419 [] (const QModelIndex &lhs, const QModelIndex &rhs) { 0420 return lhs.column() > rhs.column(); 0421 }); 0422 0423 const int minColumn = indicesToRemove.last().column(); 0424 0425 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indicesToRemove.size())); 0426 0427 { 0428 KisImageBarrierLock locker(m_d->image); 0429 0430 Q_FOREACH (const QModelIndex &index, indicesToRemove) { 0431 QModelIndexList indicesToOffset; 0432 for (int column = index.column() + 1; column < columnCount(); column++) { 0433 indicesToOffset << this->index(index.row(), column); 0434 } 0435 createOffsetFramesCommand(indicesToOffset, QPoint(-1, 0), false, true, parentCommand); 0436 } 0437 0438 const int oldTime = m_d->image->animationInterface()->currentUITime(); 0439 const int newTime = minColumn; 0440 0441 new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), 0442 oldTime, 0443 newTime, 0444 parentCommand); 0445 } 0446 0447 KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, 0448 KisStrokeJobData::BARRIER, 0449 KisStrokeJobData::EXCLUSIVE); 0450 return true; 0451 } 0452 0453 bool KisTimeBasedItemModel::mirrorFrames(QModelIndexList indexes) 0454 { 0455 QScopedPointer<KUndo2Command> parentCommand(new KUndo2Command(kundo2_i18n("Mirror Frames"))); 0456 0457 { 0458 KisImageBarrierLock locker(m_d->image); 0459 0460 QMap<int, QModelIndexList> rowsList; 0461 0462 Q_FOREACH (const QModelIndex &index, indexes) { 0463 rowsList[index.row()].append(index); 0464 } 0465 0466 0467 Q_FOREACH (int row, rowsList.keys()) { 0468 QModelIndexList &list = rowsList[row]; 0469 0470 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!list.isEmpty(), false); 0471 0472 std::sort(list.begin(), list.end(), 0473 [] (const QModelIndex &lhs, const QModelIndex &rhs) { 0474 return lhs.column() < rhs.column(); 0475 }); 0476 0477 auto srcIt = list.begin(); 0478 auto dstIt = list.end(); 0479 0480 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcIt != dstIt, false); 0481 --dstIt; 0482 0483 QList<KisKeyframeChannel*> channels = channelsAt(*srcIt).values(); 0484 0485 while (srcIt < dstIt) { 0486 Q_FOREACH (KisKeyframeChannel *channel, channels) { 0487 if (channel->keyframeAt(srcIt->column()) && channel->keyframeAt(dstIt->column())) { 0488 0489 channel->swapKeyframes(srcIt->column(), 0490 dstIt->column(), 0491 parentCommand.data()); 0492 } 0493 else if (channel->keyframeAt(srcIt->column())) { 0494 0495 channel->insertKeyframe(dstIt->column(), 0496 channel->keyframeAt(srcIt->column()), 0497 parentCommand.data()); 0498 0499 channel->removeKeyframe(srcIt->column(), 0500 parentCommand.data()); 0501 } 0502 else if (channel->keyframeAt(dstIt->column())) { 0503 0504 channel->insertKeyframe(srcIt->column(), 0505 channel->keyframeAt(dstIt->column()), 0506 parentCommand.data()); 0507 0508 channel->removeKeyframe(dstIt->column(), 0509 parentCommand.data()); 0510 } 0511 } 0512 0513 srcIt++; 0514 dstIt--; 0515 } 0516 } 0517 } 0518 0519 KisProcessingApplicator::runSingleCommandStroke(m_d->image, 0520 parentCommand.take(), 0521 KisStrokeJobData::BARRIER, 0522 KisStrokeJobData::EXCLUSIVE); 0523 return true; 0524 } 0525 0526 void KisTimeBasedItemModel::setScrubState(bool p_state) 0527 { 0528 if (!m_d->animationPlayer) { 0529 return; 0530 } 0531 0532 if (m_d->scrubInProgress != p_state) { 0533 m_d->scrubInProgress = p_state; 0534 0535 if (m_d->scrubInProgress == true) { 0536 m_d->scrubStartFrame = m_d->activeFrameIndex; 0537 0538 if (m_d->animationPlayer->playbackState() == PLAYING) { 0539 m_d->shouldReturnToPlay = true; 0540 m_d->animationPlayer->setPlaybackState(PAUSED); 0541 } 0542 0543 } else { 0544 if (m_d->shouldReturnToPlay) { 0545 m_d->animationPlayer->setPlaybackState(PLAYING); 0546 } 0547 0548 m_d->scrubStartFrame = -1; 0549 m_d->shouldReturnToPlay = false; 0550 } 0551 } 0552 } 0553 0554 bool KisTimeBasedItemModel::isScrubbing() 0555 { 0556 return m_d->scrubInProgress; 0557 } 0558 0559 0560 void KisTimeBasedItemModel::slotCurrentTimeChanged(int time) 0561 { 0562 if (time != m_d->activeFrameIndex) { 0563 setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole); 0564 } 0565 } 0566 0567 void KisTimeBasedItemModel::slotFramerateChanged() 0568 { 0569 emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); 0570 } 0571 0572 void KisTimeBasedItemModel::slotPlaybackRangeChanged() 0573 { 0574 if (m_d->image && m_d->image->animationInterface() ) { 0575 const KisImageAnimationInterface* const interface = m_d->image->animationInterface(); 0576 const int lastFrame = interface->activePlaybackRange().end(); 0577 if (lastFrame > m_d->numFramesOverride) { 0578 beginInsertColumns(QModelIndex(), m_d->numFramesOverride, interface->activePlaybackRange().end()); 0579 m_d->numFramesOverride = interface->activePlaybackRange().end(); 0580 endInsertColumns(); 0581 } 0582 0583 dataChanged(index(0,0), index(rowCount(), columnCount())); 0584 } 0585 } 0586 0587 void KisTimeBasedItemModel::slotCacheChanged() 0588 { 0589 const int numFrames = columnCount(); 0590 m_d->cachedFrames.resize(numFrames); 0591 0592 for (int i = 0; i < numFrames; i++) { 0593 m_d->cachedFrames[i] = 0594 m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached; 0595 } 0596 0597 emit headerDataChanged(Qt::Horizontal, 0, numFrames); 0598 } 0599 0600 0601 void KisTimeBasedItemModel::slotPlaybackFrameChanged() 0602 { 0603 if (m_d->animationPlayer->playbackState() != PlaybackState::PLAYING) return; 0604 setHeaderData(m_d->animationPlayer->displayProxy()->activeFrame(), Qt::Horizontal, true, ActiveFrameRole); 0605 } 0606 0607 void KisTimeBasedItemModel::slotPlaybackStateChanged(PlaybackState p_state) 0608 { 0609 if (p_state == PlaybackState::STOPPED) { 0610 setHeaderData(m_d->image->animationInterface()->currentUITime(), Qt::Horizontal, true, ActiveFrameRole); 0611 } 0612 } 0613 0614 void KisTimeBasedItemModel::setPlaybackRange(const KisTimeSpan &range) 0615 { 0616 if (m_d->image.isNull()) return; 0617 0618 KisImageAnimationInterface *i = m_d->image->animationInterface(); 0619 i->setActivePlaybackRange(range); 0620 } 0621 0622 bool KisTimeBasedItemModel::isPlaybackActive() const 0623 { 0624 return m_d->animationPlayer && m_d->animationPlayer->playbackState() == PlaybackState::PLAYING; 0625 } 0626 0627 bool KisTimeBasedItemModel::isPlaybackPaused() const 0628 { 0629 return m_d->animationPlayer && m_d->animationPlayer->playbackState() == PlaybackState::PAUSED; 0630 } 0631 0632 void KisTimeBasedItemModel::stopPlayback() const { 0633 KisPart::instance()->playbackEngine()->stop(); 0634 } 0635 0636 int KisTimeBasedItemModel::currentTime() const 0637 { 0638 return m_d->image->animationInterface()->currentUITime(); 0639 } 0640 0641 bool KisTimeBasedItemModel::cloneOfActiveFrame(const QModelIndex &index) const { 0642 KisRasterKeyframeChannel *rasterChan = dynamic_cast<KisRasterKeyframeChannel*>(channelByID(index, KisKeyframeChannel::Raster.id())); 0643 if (!rasterChan) return false; 0644 0645 const int activeKeyframeTime = rasterChan->activeKeyframeTime(m_d->activeFrameIndex); 0646 return rasterChan->areClones(activeKeyframeTime, index.column()); 0647 } 0648 0649 int KisTimeBasedItemModel::cloneCount(const QModelIndex &index) const { 0650 KisRasterKeyframeChannel *rasterChan = dynamic_cast<KisRasterKeyframeChannel*>(channelByID(index, KisKeyframeChannel::Raster.id())); 0651 0652 if (!rasterChan) { 0653 return 0; 0654 } 0655 return rasterChan->clonesOf(index.column()).count(); 0656 } 0657 0658 KisImageWSP KisTimeBasedItemModel::image() const 0659 { 0660 return m_d->image; 0661 }