65 if (!imageSP)
return 0;
94 : QAbstractTableModel(parent)
99 using namespace std::placeholders;
101 std::function<
void (
int)> scrubHorizHeaderUpdateCallback(
104 m_d->scrubHeaderUpdateCompressor.reset(
113 if (
m_d->image == p_image ) {
122 ai->disconnect(
this);
126 m_d->numFramesOverride =
m_d->effectiveNumFrames();
143 if (
m_d->framesCache) {
144 m_d->framesCache->disconnect(
this);
147 m_d->framesCache = cache;
149 if (
m_d->framesCache) {
161 if (
m_d->animationPlayer == player)
return;
163 if (
m_d->animationPlayer) {
164 m_d->animationPlayer->disconnect(
this);
167 m_d->animationPlayer = player;
169 if (
m_d->animationPlayer) {
183 if (
m_d->document == document)
return;
190 return m_d->document;
195 const int growThreshold =
m_d->effectiveNumFrames() - 1;
196 const int growValue = time + 8;
198 const int shrinkThreshold =
m_d->effectiveNumFrames() - 3;
199 const int shrinkValue = qMax(
m_d->baseNumFrames(), qMin(growValue, shrinkThreshold));
200 const bool canShrink =
m_d->baseNumFrames() <
m_d->effectiveNumFrames();
202 if (time >= growThreshold) {
203 beginInsertColumns(QModelIndex(),
m_d->effectiveNumFrames(), growValue - 1);
204 m_d->numFramesOverride = growValue;
206 }
else if (time < shrinkThreshold && canShrink) {
207 beginRemoveColumns(QModelIndex(), shrinkValue,
m_d->effectiveNumFrames() - 1);
208 m_d->numFramesOverride = shrinkValue;
216 return m_d->numFramesOverride;
223 return index.column() ==
m_d->activeFrameIndex;
232 return m_d->withinClipRange(index.column());
240 if (!index.isValid())
return false;
256 if (orientation == Qt::Horizontal) {
259 return section ==
m_d->activeFrameIndex;
261 return m_d->cachedFrames.size() > section ?
m_d->cachedFrames[section] :
false;
263 return m_d->framesPerSecond();
265 return m_d->withinClipRange(section);
274 auto prioritizeCache = [
this](
int frame){
283 if (orientation == Qt::Horizontal) {
286 if (
value.toBool() &&
287 section !=
m_d->activeFrameIndex) {
289 int prevFrame =
m_d->activeFrameIndex;
290 m_d->activeFrameIndex = section;
302 if (
m_d->scrubInProgress) {
303 Q_EMIT dataChanged(this->index(0,
m_d->activeFrameIndex), this->index(rowCount() - 1,
m_d->activeFrameIndex));
312 m_d->scrubHeaderMin = qMin(
m_d->activeFrameIndex,
m_d->scrubHeaderMin);
313 m_d->scrubHeaderMax = qMax(
m_d->activeFrameIndex,
m_d->scrubHeaderMax);
314 m_d->scrubHeaderUpdateCompressor->start(
m_d->activeFrameIndex);
321 Q_EMIT dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame));
322 Q_EMIT dataChanged(this->index(0,
m_d->activeFrameIndex), this->index(rowCount() - 1,
m_d->activeFrameIndex));
323 Q_EMIT headerDataChanged (Qt::Horizontal, prevFrame, prevFrame);
324 Q_EMIT headerDataChanged (Qt::Horizontal,
m_d->activeFrameIndex,
m_d->activeFrameIndex);
329 SeekOptionFlags seekFlags = SeekOptionFlags(
value.toInt());
330 prioritizeCache(
m_d->activeFrameIndex);
341 Q_EMIT headerDataChanged (Qt::Horizontal,
m_d->scrubHeaderMin,
m_d->scrubHeaderMax);
342 m_d->scrubHeaderMin = activeColumn;
343 m_d->scrubHeaderMax = activeColumn;
351 KisImageBarrierLock locker(
m_d->image);
353 Q_FOREACH (
const QModelIndex &index, indexes) {
354 int time = index.column();
363 if (frameItems.isEmpty())
return false;
371 const QPoint &offset,
373 bool moveEmptyFrames,
376 if (srcIndexes.isEmpty())
return 0;
377 if (offset.isNull())
return 0;
384 Q_FOREACH (
const QModelIndex &srcIndex, srcIndexes) {
385 QModelIndex dstIndex = index(
386 srcIndex.row() + offset.y(),
387 srcIndex.column() + offset.x());
391 if (!srcNode || !dstNode)
return 0;
394 if (moveEmptyFrames || channel->
keyframeAt(srcIndex.column())) {
402 if (srcFrameItems.isEmpty())
return 0;
414 if (indicesToRemove.isEmpty())
return true;
416 std::sort(indicesToRemove.begin(), indicesToRemove.end(),
417 [] (
const QModelIndex &lhs,
const QModelIndex &rhs) {
418 return lhs.column() > rhs.column();
421 const int minColumn = indicesToRemove.last().column();
426 KisImageBarrierLock locker(
m_d->image);
428 Q_FOREACH (
const QModelIndex &index, indicesToRemove) {
430 for (
int column = index.column() + 1; column <
columnCount(); column++) {
431 indicesToOffset << this->index(index.row(), column);
436 const int oldTime =
m_d->image->animationInterface()->currentUITime();
437 const int newTime = minColumn;
456 KisImageBarrierLock locker(
m_d->image);
458 QMap<int, QModelIndexList> rowsList;
460 Q_FOREACH (
const QModelIndex &index, indexes) {
461 rowsList[index.row()].append(index);
465 Q_FOREACH (
int row, rowsList.keys()) {
470 std::sort(list.begin(), list.end(),
471 [] (
const QModelIndex &lhs,
const QModelIndex &rhs) {
472 return lhs.column() < rhs.column();
475 auto srcIt = list.begin();
476 auto dstIt = list.end();
483 while (srcIt < dstIt) {
489 parentCommand.get());
491 else if (channel->
keyframeAt(srcIt->column())) {
495 parentCommand.get());
498 parentCommand.get());
500 else if (channel->
keyframeAt(dstIt->column())) {
504 parentCommand.get());
507 parentCommand.get());
518 parentCommand.release(),
526 if (!
m_d->animationPlayer) {
530 if (
m_d->scrubInProgress != p_state) {
531 m_d->scrubInProgress = p_state;
533 if (
m_d->scrubInProgress ==
true) {
534 m_d->scrubStartFrame =
m_d->activeFrameIndex;
536 if (
m_d->animationPlayer->playbackState() ==
PLAYING) {
537 m_d->shouldReturnToPlay =
true;
538 m_d->animationPlayer->setPlaybackState(
PAUSED);
542 if (
m_d->shouldReturnToPlay) {
543 m_d->animationPlayer->setPlaybackState(
PLAYING);
546 m_d->scrubStartFrame = -1;
547 m_d->shouldReturnToPlay =
false;
554 return m_d->scrubInProgress;
560 if (time !=
m_d->activeFrameIndex) {
567 Q_EMIT headerDataChanged(Qt::Horizontal, 0,
columnCount() - 1);
572 if (
m_d->image &&
m_d->image->animationInterface() ) {
574 const int lastFrame = interface->activePlaybackRange().end();
575 if (lastFrame >
m_d->numFramesOverride) {
576 beginInsertColumns(QModelIndex(),
m_d->numFramesOverride, interface->activePlaybackRange().end());
577 m_d->numFramesOverride = interface->activePlaybackRange().end();
581 dataChanged(index(0,0), index(rowCount(),
columnCount()));
588 m_d->cachedFrames.resize(numFrames);
590 for (
int i = 0; i < numFrames; i++) {
591 m_d->cachedFrames[i] =
595 Q_EMIT headerDataChanged(Qt::Horizontal, 0, numFrames);
614 if (
m_d->image.isNull())
return;
636 return m_d->image->animationInterface()->currentUITime();
641 if (!rasterChan)
return false;
644 return rasterChan->
areClones(activeKeyframeTime, index.column());
653 return rasterChan->
clonesOf(index.column()).count();
float value(const T *src, size_t ch)
The KisCanvasAnimationState class stores all of the canvas-specific animation state.
class KisFrameDisplayProxy * displayProxy()
int activeFrame() const
Gets the active frame, the frame that is intended to be shown. This should always reflect the actual ...
KisImageWSP image() const
void setActivePlaybackRange(const KisTimeSpan range)
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
KisImageAnimationInterface * animationInterface() const
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
virtual void insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd=nullptr)
Insert an existing keyframe into the channel at the specified time.
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Remove a keyframe from the channel at the specified time.
static void swapKeyframes(KisKeyframeChannel *channelA, int timeA, KisKeyframeChannel *channelB, int timeB, KUndo2Command *parentUndoCmd=nullptr)
Swap two keyframes across channel(s) at the specified times.
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
int activeKeyframeTime(int time) const
Get the time of the active keyframe. Useful for snapping any time to that of the most recent keyframe...
static KisPart * instance()
std::unique_ptr< KisPlaybackEngine > playbackEngine
void prioritizeFrameForCache(KisImageSP image, int frame)
static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
runSingleCommandStroke creates a stroke and runs cmd in it. The text() field of cmd is used as a titl...
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
QSet< int > clonesOf(int time)
bool areClones(int timeA, int timeB)
bool cloneOfActiveFrame(const QModelIndex &index) const
bool removeFramesAndOffset(QModelIndexList indicesToRemove)
void setFrameCache(KisAnimationFrameCacheSP cache)
KUndo2Command * createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand=0)
bool setData(const QModelIndex &index, const QVariant &value, int role) override
bool isFrameCached(const int frame)
void setPlaybackRange(const KisTimeSpan &range)
void setAnimationPlayer(KisCanvasAnimationState *player)
void scrubHorizontalHeaderUpdate(int activeHeader)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
virtual KisNodeSP nodeAt(QModelIndex index) const =0
void setImage(KisImageWSP image)
KisTimeBasedItemModel(QObject *parent)
void slotPlaybackFrameChanged()
KisDocument * document() const
int cloneCount(const QModelIndex &index) const
void setScrubState(bool active)
void slotPlaybackStateChanged(PlaybackState state)
void setLastVisibleFrame(int time)
virtual KisKeyframeChannel * channelByID(QModelIndex index, const QString &id) const =0
QVariant data(const QModelIndex &index, int role) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void slotCurrentTimeChanged(int time)
bool isPlaybackActive() const
KisImageWSP image() const
bool isPlaybackPaused() const
void setDocument(class KisDocument *document)
bool removeFrames(const QModelIndexList &indexes)
~KisTimeBasedItemModel() override
const QScopedPointer< Private > m_d
void stopPlayback() const
void slotPlaybackRangeChanged()
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override
virtual QMap< QString, KisKeyframeChannel * > channelsAt(QModelIndex index) const =0
bool mirrorFrames(QModelIndexList indexes)
void slotFramerateChanged()
bool contains(int time) const
KisSharedPtr< T > toStrongRef() const
toStrongRef returns a KisSharedPtr which may be dereferenced.
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
KisSharedPtr< KisAnimationFrameCache > KisAnimationFrameCacheSP
KUndo2MagicString kundo2_i18n(const char *text)
KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
void removeKeyframes(KisImageSP image, const FrameItemList &frames)
void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset)
KUndo2Command * createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, bool moveEmpty, KUndo2Command *parentCommand)
int effectiveNumFrames() const
int baseNumFrames() const
QVector< bool > cachedFrames
bool withinClipRange(const int time)
KisAnimationFrameCacheWSP framesCache
QScopedPointer< KisSignalCompressorWithParam< int > > scrubHeaderUpdateCompressor
QPointer< KisCanvasAnimationState > animationPlayer