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);
331 if (!
m_d->image->hasUpdatesRunning()) {
343 Q_EMIT headerDataChanged (Qt::Horizontal,
m_d->scrubHeaderMin,
m_d->scrubHeaderMax);
344 m_d->scrubHeaderMin = activeColumn;
345 m_d->scrubHeaderMax = activeColumn;
353 KisImageBarrierLock locker(
m_d->image);
355 Q_FOREACH (
const QModelIndex &index, indexes) {
356 int time = index.column();
365 if (frameItems.isEmpty())
return false;
373 const QPoint &offset,
375 bool moveEmptyFrames,
378 if (srcIndexes.isEmpty())
return 0;
379 if (offset.isNull())
return 0;
386 Q_FOREACH (
const QModelIndex &srcIndex, srcIndexes) {
387 QModelIndex dstIndex = index(
388 srcIndex.row() + offset.y(),
389 srcIndex.column() + offset.x());
393 if (!srcNode || !dstNode)
return 0;
396 if (moveEmptyFrames || channel->
keyframeAt(srcIndex.column())) {
404 if (srcFrameItems.isEmpty())
return 0;
416 if (indicesToRemove.isEmpty())
return true;
418 std::sort(indicesToRemove.begin(), indicesToRemove.end(),
419 [] (
const QModelIndex &lhs,
const QModelIndex &rhs) {
420 return lhs.column() > rhs.column();
423 const int minColumn = indicesToRemove.last().column();
428 KisImageBarrierLock locker(
m_d->image);
430 Q_FOREACH (
const QModelIndex &index, indicesToRemove) {
432 for (
int column = index.column() + 1; column <
columnCount(); column++) {
433 indicesToOffset << this->index(index.row(), column);
438 const int oldTime =
m_d->image->animationInterface()->currentUITime();
439 const int newTime = minColumn;
458 KisImageBarrierLock locker(
m_d->image);
460 QMap<int, QModelIndexList> rowsList;
462 Q_FOREACH (
const QModelIndex &index, indexes) {
463 rowsList[index.row()].append(index);
467 Q_FOREACH (
int row, rowsList.keys()) {
472 std::sort(list.begin(), list.end(),
473 [] (
const QModelIndex &lhs,
const QModelIndex &rhs) {
474 return lhs.column() < rhs.column();
477 auto srcIt = list.begin();
478 auto dstIt = list.end();
485 while (srcIt < dstIt) {
491 parentCommand.data());
493 else if (channel->
keyframeAt(srcIt->column())) {
497 parentCommand.data());
500 parentCommand.data());
502 else if (channel->
keyframeAt(dstIt->column())) {
506 parentCommand.data());
509 parentCommand.data());
520 parentCommand.take(),
528 if (!
m_d->animationPlayer) {
532 if (
m_d->scrubInProgress != p_state) {
533 m_d->scrubInProgress = p_state;
535 if (
m_d->scrubInProgress ==
true) {
536 m_d->scrubStartFrame =
m_d->activeFrameIndex;
538 if (
m_d->animationPlayer->playbackState() ==
PLAYING) {
539 m_d->shouldReturnToPlay =
true;
540 m_d->animationPlayer->setPlaybackState(
PAUSED);
544 if (
m_d->shouldReturnToPlay) {
545 m_d->animationPlayer->setPlaybackState(
PLAYING);
548 m_d->scrubStartFrame = -1;
549 m_d->shouldReturnToPlay =
false;
556 return m_d->scrubInProgress;
562 if (time !=
m_d->activeFrameIndex) {
569 Q_EMIT headerDataChanged(Qt::Horizontal, 0,
columnCount() - 1);
574 if (
m_d->image &&
m_d->image->animationInterface() ) {
576 const int lastFrame = interface->activePlaybackRange().end();
577 if (lastFrame >
m_d->numFramesOverride) {
578 beginInsertColumns(QModelIndex(),
m_d->numFramesOverride, interface->activePlaybackRange().end());
579 m_d->numFramesOverride = interface->activePlaybackRange().end();
583 dataChanged(index(0,0), index(rowCount(),
columnCount()));
590 m_d->cachedFrames.resize(numFrames);
592 for (
int i = 0; i < numFrames; i++) {
593 m_d->cachedFrames[i] =
597 Q_EMIT headerDataChanged(Qt::Horizontal, 0, numFrames);
616 if (
m_d->image.isNull())
return;
638 return m_d->image->animationInterface()->currentUITime();
643 if (!rasterChan)
return false;
646 return rasterChan->
areClones(activeKeyframeTime, index.column());
655 return rasterChan->
clonesOf(index.column()).count();
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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()
QScopedPointer< 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