Git commit 443776470b9e909230ec2f192909205143ce3113 by Dmitry Kazakov. Committed on 11/04/2018 at 16:06. Pushed by dkazakov into branch 'master'. Implement Add/Remove Hold Frames feature These actions allow the user change duration of the key frames that are part of the current selection. CC:kimageshop@kde.org M +51 -0 krita/krita.action M +93 -0 plugins/dockers/animation/timeline_frames_model.cpp M +2 -0 plugins/dockers/animation/timeline_frames_model.h M +82 -0 plugins/dockers/animation/timeline_frames_view.cpp M +6 -0 plugins/dockers/animation/timeline_frames_view.h https://commits.kde.org/krita/443776470b9e909230ec2f192909205143ce3113 diff --git a/krita/krita.action b/krita/krita.action index cdca2e1f2b5..41beb61392c 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -2344,6 +2344,57 @@ = + + + Insert Hold Frame + + Insert a hold frame after every keyframe + + 100000 + 0 + + false + + + + + + Insert N Hold Frames + + Insert N hold frames after every keyframe + + 100000 + 0 + + false + + + + + + Remove Hold Frame + + Insert a hold frame after every keyframe + + 100000 + 0 + + false + + + + + + Remove N Hold Frames + + Remove N hold frames after every keyframe + + 100000 + 0 + + false + + = = diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/= dockers/animation/timeline_frames_model.cpp index 6e28cc872ed..f98e1473393 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -739,6 +739,99 @@ bool TimelineFramesModel::insertFrames(int dstColumn, = const QList &dstRows, return true; } = +bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes= , int count) +{ + if (selectedIndexes.isEmpty() || count =3D=3D 0) return true; + + QScopedPointer parentCommand(new KUndo2Command(kundo2_i= 18np("Insert frame", "Insert %1 frames", count))); + + { + KisImageBarrierLockerWithFeedback locker(m_d->image); + + QSet uniqueKeyframesInSelection; + + Q_FOREACH (const QModelIndex &index, selectedIndexes) { + KisNodeSP node =3D nodeAt(index); + KIS_SAFE_ASSERT_RECOVER(node) { continue; } + + KisKeyframeChannel *channel =3D node->getKeyframeChannel(KisKe= yframeChannel::Content.id()); + if (!channel) continue; + + KisKeyframeSP keyFrame =3D channel->activeKeyframeAt(index.col= umn()); + + if (keyFrame) { + uniqueKeyframesInSelection.insert(keyFrame); + } + } + + int minSelectedTime =3D std::numeric_limits::max(); + QList keyframesToMove; + + for (auto it =3D uniqueKeyframesInSelection.begin(); it !=3D uniqu= eKeyframesInSelection.end(); ++it) { + KisKeyframeSP keyframe =3D *it; + + minSelectedTime =3D qMin(minSelectedTime, keyframe->time()); + + KisKeyframeChannel *channel =3D keyframe->channel(); + KisKeyframeSP nextKeyframe =3D channel->nextKeyframe(keyframe); + + if (nextKeyframe) { + keyframesToMove << nextKeyframe; + } + } + + std::sort(keyframesToMove.begin(), keyframesToMove.end(), + [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { + return lhs->time() > rhs->time(); + }); + + if (keyframesToMove.isEmpty()) return true; + + const int maxColumn =3D columnCount(); + + if (count > 0) { + setLastVisibleFrame(columnCount() + count); + } + + Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { + int plannedFrameMove =3D count; + + if (count < 0) { + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0,= false); + + KisKeyframeSP prevFrame =3D keyframe->channel()->previousK= eyframe(keyframe); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); + + plannedFrameMove =3D qMax(count, prevFrame->time() - keyfr= ame->time() + 1); + } + + KisNodeDummy *dummy =3D m_d->dummiesFacade->dummyForNode(keyfr= ame->channel()->node()); + KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } + + const int row =3D m_d->converter->rowForDummy(dummy); + KIS_SAFE_ASSERT_RECOVER(row >=3D 0) { continue; } + + QModelIndexList indexes; + for (int column =3D keyframe->time(); column < maxColumn; colu= mn++) { + indexes << index(row, column); + } + + createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0)= , false, parentCommand.data()); + } + + const int oldTime =3D m_d->image->animationInterface()->currentUIT= ime(); + const int newTime =3D minSelectedTime; + + new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), + oldTime, + newTime, parentCommand.data()); + } + + + KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentComm= and.take(), KisStrokeJobData::BARRIER); + return true; +} + = QString TimelineFramesModel::audioChannelFileName() const { diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/do= ckers/animation/timeline_frames_model.h index 2c7908438fe..0f053c29587 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -53,6 +53,8 @@ public: = bool insertFrames(int dstColumn, const QList &dstRows, int count); = + bool insertHoldFrames(QModelIndexList selectedIndexes, int count); + QString audioChannelFileName() const; void setAudioChannelFileName(const QString &fileName); = diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/d= ockers/animation/timeline_frames_view.cpp index d9a769bf9a6..e997ee1768b 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -311,6 +311,18 @@ void TimelineFramesView::setActionManager( KisActionMa= nager * actionManager) = action =3D m_d->actionMan->createAction("remove_frames"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveFrame())); + + action =3D m_d->actionMan->createAction("add_hold_frame"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrames())); + + action =3D m_d->actionMan->createAction("add_n_hold_frames"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFramesCust= om())); + + action =3D m_d->actionMan->createAction("remove_hold_frame"); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrames())); + + action =3D m_d->actionMan->createAction("remove_n_hold_frames"); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFramesCust= om())); } } = @@ -936,6 +948,12 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *= event) addActionToMenu(&menu, "remove_frames"); addActionToMenu(&menu, "remove_frames_and_shift"); menu.addSeparator(); + addActionToMenu(&menu, "add_hold_frame"); + addActionToMenu(&menu, "remove_hold_frame"); + menu.addSeparator(); + addActionToMenu(&menu, "add_n_hold_frames"); + addActionToMenu(&menu, "remove_n_hold_frames"); + menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); = @@ -959,6 +977,12 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *= event) menu.addSeparator(); addActionToMenu(&menu, "remove_frames_and_shift"); menu.addSeparator(); + addActionToMenu(&menu, "add_hold_frame"); + addActionToMenu(&menu, "remove_hold_frame"); + menu.addSeparator(); + addActionToMenu(&menu, "add_n_hold_frames"); + addActionToMenu(&menu, "remove_n_hold_frames"); + menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } @@ -996,6 +1020,14 @@ void TimelineFramesView::mousePressEvent(QMouseEvent = *event) addActionToMenu(&menu, "remove_frames"); } addActionToMenu(&menu, "remove_frames_and_shift"); + + menu.addSeparator(); + addActionToMenu(&menu, "add_hold_frame"); + addActionToMenu(&menu, "remove_hold_frame"); + menu.addSeparator(); + addActionToMenu(&menu, "add_n_hold_frames"); + addActionToMenu(&menu, "remove_n_hold_frames"); + if (haveFrames) { menu.addSeparator(); menu.addAction(m_d->multiframeColorSelectorAction); @@ -1339,6 +1371,56 @@ void TimelineFramesView::slotRemoveColumnsAndShift() slotRemoveFramesAndShift(true); } = +void TimelineFramesView::slotInsertHoldFrames(int count) +{ + QModelIndexList indexes; + + Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes= ()) { + if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole= ).toBool()) { + indexes << index; + } + } + + if (!indexes.isEmpty()) { + m_d->model->insertHoldFrames(indexes, count); + } +} + +void TimelineFramesView::slotRemoveHoldFrames(int count) +{ + slotInsertHoldFrames(-count); +} + +void TimelineFramesView::slotInsertHoldFramesCustom() +{ + bool ok =3D false; + + // TODO: save previous entered value till the end of the session + const int count =3D QInputDialog::getInt(this, + i18nc("@title:window", "Insert = hold frames"), + i18nc("@label:spinbox", "Enter = number of frames"), + 1, 1, 10000, 1, &ok); + + if (ok) { + slotInsertHoldFrames(count); + } +} + +void TimelineFramesView::slotRemoveHoldFramesCustom() +{ + bool ok =3D false; + + // TODO: save previous entered value till the end of the session + const int count =3D QInputDialog::getInt(this, + i18nc("@title:window", "Remove = hold frames"), + i18nc("@label:spinbox", "Enter = number of frames"), + 1, 1, 10000, 1, &ok); + + if (ok) { + slotRemoveHoldFrames(count); + } +} + = bool TimelineFramesView::viewportEvent(QEvent *event) { diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/doc= kers/animation/timeline_frames_view.h index 1290068e81e..47d4762dcfe 100644 --- a/plugins/dockers/animation/timeline_frames_view.h +++ b/plugins/dockers/animation/timeline_frames_view.h @@ -80,6 +80,12 @@ private Q_SLOTS: void slotRemoveColumns(); void slotRemoveColumnsAndShift(); = + void slotInsertHoldFrames(int count =3D 1); + void slotInsertHoldFramesCustom(); + + void slotRemoveHoldFrames(int count =3D 1); + void slotRemoveHoldFramesCustom(); + void slotReselectCurrentIndex(); = void slotUpdateInfiniteFramesCount();