diff --git a/plugins/dockers/animation/kis_animation_curves_model.cpp b/plugins/dockers/animation/kis_animation_curves_model.cpp --- a/plugins/dockers/animation/kis_animation_curves_model.cpp +++ b/plugins/dockers/animation/kis_animation_curves_model.cpp @@ -290,11 +290,10 @@ KisImageBarrierLockerWithFeedback locker(image()); if (timeOffset != 0) { - bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, command.data()); + bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, false, command.data()); if (!ok) return false; } - using KisAnimationUtils::FrameItem; using KisAnimationUtils::FrameItemList; FrameItemList frameItems; diff --git a/plugins/dockers/animation/kis_animation_utils.h b/plugins/dockers/animation/kis_animation_utils.h --- a/plugins/dockers/animation/kis_animation_utils.h +++ b/plugins/dockers/animation/kis_animation_utils.h @@ -64,21 +64,21 @@ void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset); - KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, - const FrameItemList &dstFrames, - bool copy, KUndo2Command *parentCommand = 0); + KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, + bool copy, bool moveEmpty, KUndo2Command *parentCommand = 0); /** * @brief implements safe moves of the frames (even if there are cycling move dependencies) * @param movePairs the jobs for the moves * @param copy shows if the frames should be copied or not + * @param moveEmpty allows an empty frame to replace a populated one * @param parentCommand the command that should be a parent of the created command * @return a created undo command */ KRITAANIMATIONDOCKER_EXPORT KUndo2Command* createMoveKeyframesCommand(const FrameMovePairList &movePairs, - bool copy, KUndo2Command *parentCommand = 0); + bool copy, bool moveEmptyFrames, KUndo2Command *parentCommand = 0); bool supportsContentFrames(KisNodeSP node); diff --git a/plugins/dockers/animation/kis_animation_utils.cpp b/plugins/dockers/animation/kis_animation_utils.cpp --- a/plugins/dockers/animation/kis_animation_utils.cpp +++ b/plugins/dockers/animation/kis_animation_utils.cpp @@ -174,18 +174,6 @@ std::sort(points->begin(), points->end(), LessOperator(offset)); } - KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, - const FrameItemList &dstFrames, - bool copy, - KUndo2Command *parentCommand) { - - FrameMovePairList movedFrames; - for (int i = 0; i < srcFrames.size(); i++) { - movedFrames << std::make_pair(srcFrames[i], dstFrames[i]); - } - return createMoveKeyframesCommand(movedFrames, copy, parentCommand); - } - bool supportsContentFrames(KisNodeSP node) { return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer"); @@ -202,19 +190,17 @@ KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true); if (srcNode == dstNode) { - // TODO: add warning! - if (!srcChannel) return; + if (!srcChannel) return; // TODO: add warning! srcChannel->swapFrames(srcTime, dstTime, parentCommand); } else { - // TODO: add warning! - if (!srcChannel || !dstChannel) return; + if (!srcChannel || !dstChannel) return; // TODO: add warning! dstChannel->swapExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); } } - void moveOneFrameItem(const FrameItem &src, const FrameItem &dst, bool copy, KUndo2Command *parentCommand) + void moveOneFrameItem(const FrameItem &src, const FrameItem &dst, bool copy, bool moveEmptyFrames, KUndo2Command *parentCommand) { const int srcTime = src.time; KisNodeSP srcNode = src.node; @@ -225,24 +211,28 @@ KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true); if (srcNode == dstNode) { - // TODO: add warning! - if (!srcChannel) return; + if (!srcChannel) return; // TODO: add warning! KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); + KisKeyframeSP dstKeyFrame = srcChannel->keyframeAt(dstTime); if (srcKeyframe) { if (copy) { srcChannel->copyKeyframe(srcKeyframe, dstTime, parentCommand); } else { srcChannel->moveKeyframe(srcKeyframe, dstTime, parentCommand); } + } else { + if (dstKeyFrame && moveEmptyFrames && !copy) { + //Destination is effectively replaced by an empty frame. + dstChannel->deleteKeyframe(dstKeyFrame, parentCommand); + } } } else { - // TODO: add warning! - if (!srcChannel || !dstChannel) return; + if (!srcChannel || !dstChannel) return; // TODO: add warning! KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); - // TODO: add warning! - if (!srcKeyframe) return; + + if (!srcKeyframe) return; // TODO: add warning! dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); @@ -252,39 +242,53 @@ } } - KUndo2Command *createMoveKeyframesCommand(const FrameMovePairList &movePairs, bool copy, KUndo2Command *parentCommand) + KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, + const FrameItemList &dstFrames, + bool copy, + bool moveEmpty, + KUndo2Command *parentCommand) + { + FrameMovePairList srcDstPairs; + for (int i = 0; i < srcFrames.size(); i++) { + srcDstPairs << std::make_pair(srcFrames[i], dstFrames[i]); + } + return createMoveKeyframesCommand(srcDstPairs, copy, moveEmpty, parentCommand); + } + + KUndo2Command* createMoveKeyframesCommand(const FrameMovePairList &srcDstPairs, + bool copy, + bool moveEmptyFrames, + KUndo2Command *parentCommand) { KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( !copy ? kundo2_i18np("Move Keyframe", "Move %1 Keyframes", - movePairs.size()) : + srcDstPairs.size()) : kundo2_i18np("Copy Keyframe", "Copy %1 Keyframes", - movePairs.size()), + srcDstPairs.size()), parentCommand, - [movePairs, copy] () -> KUndo2Command* { + [srcDstPairs, copy, moveEmptyFrames] () -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); using MoveChain = QList; - - QHash moveMap; - Q_FOREACH (const FrameMovePair &pair, movePairs) { + Q_FOREACH (const FrameMovePair &pair, srcDstPairs) { moveMap.insert(pair.first, {pair.second}); } auto it = moveMap.begin(); while (it != moveMap.end()) { MoveChain &chain = it.value(); - const FrameItem &lastFrame = chain.last(); + const FrameItem &previousFrame = chain.last(); - auto tailIt = moveMap.find(lastFrame); + auto tailIt = moveMap.find(previousFrame); if (tailIt == it || tailIt == moveMap.end()) { ++it; @@ -315,7 +319,7 @@ FrameItem srcItem = *frameIt++; if (!isCycle) { - moveOneFrameItem(srcItem, dstItem, copy, cmd.data()); + moveOneFrameItem(srcItem, dstItem, copy, moveEmptyFrames, cmd.data()); } else { swapOneFrameItem(srcItem, dstItem, cmd.data()); } diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h --- a/plugins/dockers/animation/kis_time_based_item_model.h +++ b/plugins/dockers/animation/kis_time_based_item_model.h @@ -53,7 +53,7 @@ bool removeFrames(const QModelIndexList &indexes); - bool removeFramesAndOffset(QModelIndexList indexes); + bool removeFramesAndOffset(QModelIndexList indicesToRemove); bool mirrorFrames(QModelIndexList indexes); @@ -81,7 +81,9 @@ virtual QMap channelsAt(QModelIndex index) const = 0; KisImageWSP image() const; - KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand = 0, bool moveEmptyFrames = true); + KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, + bool copyFrames, bool moveEmptyFrames, + KUndo2Command *parentCommand = 0); private Q_SLOTS: diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp --- a/plugins/dockers/animation/kis_time_based_item_model.cpp +++ b/plugins/dockers/animation/kis_time_based_item_model.cpp @@ -281,7 +281,7 @@ return true; } -KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand, bool moveEmptyFrames) +KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand) { if (srcIndexes.isEmpty()) return 0; if (offset.isNull()) return 0; @@ -298,10 +298,7 @@ KisNodeSP srcNode = nodeAt(srcIndex); KisNodeSP dstNode = nodeAt(dstIndex); - - if (!srcNode || !dstNode) { - return 0; - } + if (!srcNode || !dstNode) return 0; Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { if (moveEmptyFrames || channel->keyframeAt(srcIndex.column())) { @@ -318,31 +315,32 @@ KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems, dstFrameItems, copyFrames, + moveEmptyFrames, parentCommand); } -bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indexes) +bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indicesToRemove) { - if (indexes.isEmpty()) return true; + if (indicesToRemove.isEmpty()) return true; - std::sort(indexes.begin(), indexes.end(), + std::sort(indicesToRemove.begin(), indicesToRemove.end(), [] (const QModelIndex &lhs, const QModelIndex &rhs) { return lhs.column() > rhs.column(); }); - const int minColumn = indexes.last().column(); + const int minColumn = indicesToRemove.last().column(); - KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indexes.size())); + KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indicesToRemove.size())); { KisImageBarrierLockerWithFeedback locker(m_d->image); - Q_FOREACH (const QModelIndex &index, indexes) { - QModelIndexList movedIndexes; + Q_FOREACH (const QModelIndex &index, indicesToRemove) { + QModelIndexList indicesToOffset; for (int column = index.column() + 1; column < columnCount(); column++) { - movedIndexes << this->index(index.row(), column); + indicesToOffset << this->index(index.row(), column); } - createOffsetFramesCommand(movedIndexes, QPoint(-1, 0), false, parentCommand, true); + createOffsetFramesCommand(indicesToOffset, QPoint(-1, 0), false, true, parentCommand); } const int oldTime = m_d->image->animationInterface()->currentUITime(); diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -705,7 +705,7 @@ if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); - cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, 0); + cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { @@ -814,7 +814,7 @@ setLastVisibleFrame(columnCount() + (count * timing) - 1); - createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, parentCommand); + createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); @@ -920,7 +920,7 @@ indexes << index(row, column); } - createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, parentCommand.data()); + createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, false, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h --- a/plugins/dockers/animation/timeline_frames_view.h +++ b/plugins/dockers/animation/timeline_frames_view.h @@ -86,7 +86,7 @@ void slotInsertMultipleKeyframes() {insertMultipleKeyframes(false);} void slotInsertMultipleKeyframeColumns() {insertMultipleKeyframes(true);} - void slotRemoveSelectedFrames(bool entireColumn = false, bool needsOffset = false); + void slotRemoveSelectedFrames(bool entireColumn = false, bool pull = false); void slotRemoveSelectedFramesAndShift() {slotRemoveSelectedFrames(false, true);} void slotRemoveSelectedColumns() {slotRemoveSelectedFrames(true);} diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1369,15 +1369,15 @@ return indexes; } -void TimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool needsOffset) +void TimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool pull) { - const QModelIndexList indexes = calculateSelectionSpan(entireColumn); + const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn); - if (!indexes.isEmpty()) { - if (needsOffset) { - m_d->model->removeFramesAndOffset(indexes); + if (!selectedIndices.isEmpty()) { + if (pull) { + m_d->model->removeFramesAndOffset(selectedIndices); } else { - m_d->model->removeFrames(indexes); + m_d->model->removeFrames(selectedIndices); } } } @@ -1439,24 +1439,25 @@ void TimelineFramesView::cutCopyImpl(bool entireColumn, bool copy) { - const QModelIndexList indexes = calculateSelectionSpan(entireColumn, !copy); - if (indexes.isEmpty()) return; + const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn, !copy); + if (selectedIndices.isEmpty()) return; int minColumn = std::numeric_limits::max(); int minRow = std::numeric_limits::max(); - Q_FOREACH (const QModelIndex &index, indexes) { + Q_FOREACH (const QModelIndex &index, selectedIndices) { minRow = qMin(minRow, index.row()); minColumn = qMin(minColumn, index.column()); } const QModelIndex baseIndex = m_d->model->index(minRow, minColumn); - QMimeData *data = m_d->model->mimeDataExtended(indexes, + QMimeData *data = m_d->model->mimeDataExtended(selectedIndices, baseIndex, copy ? TimelineFramesModel::CopyFramesPolicy : TimelineFramesModel::MoveFramesPolicy); + if (data) { QClipboard *cb = QApplication::clipboard(); cb->setMimeData(data);