[multimedia/kdenlive] src/dialogs: timeremap: add per-keyframe type storage to RemapView

Jean-Baptiste Mardelle <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 46db2832fe38c4e1b6c4ce92f9d24e74d415b4a1 by Jean-Baptiste Mardelle, on behalf of Yash Bavadiya.
Committed on 18/08/2026 at 08:31.
Pushed by mardelle into branch 'master'.

timeremap: add per-keyframe type storage to RemapView

M  +60   -1    src/dialogs/timeremap.cpp
M  +8    -0    src/dialogs/timeremap.h

https://invent.kde.org/multimedia/kdenlive/-/commit/46db2832fe38c4e1b6c4ce92f9d24e74d415b4a1

diff --git a/src/dialogs/timeremap.cpp b/src/dialogs/timeremap.cpp
index 4e82518c2f..ac3d648da4 100644
--- a/src/dialogs/timeremap.cpp
+++ b/src/dialogs/timeremap.cpp
@@ -77,6 +77,7 @@ void RemapView::updateInPos(int pos)
 {
     if (m_currentKeyframe.second > -1) {
         m_keyframesOrigin = m_keyframes;
+        m_keyframeTypesOrigin = m_keyframeTypes;
         if (m_moveNext) {
             int offset = pos - m_currentKeyframe.second;
             QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
@@ -106,6 +107,7 @@ void RemapView::updateOutPos(int pos)
             return;
         }
         m_keyframesOrigin = m_keyframes;
+        m_keyframeTypesOrigin = m_keyframeTypes;
         QMap<int, int> updated;
         int offset = pos - m_currentKeyframe.first;
         if (m_moveNext) {
@@ -114,8 +116,10 @@ void RemapView::updateOutPos(int pos)
                 updated.insert(it.key(), it.value());
                 it++;
             }
+            shiftKeyframeTypes(m_currentKeyframe.first, offset);
             m_currentKeyframe.first = pos;
         } else {
+            moveKeyframeType(m_currentKeyframe.first, pos);
             m_keyframes.remove(m_currentKeyframe.first);
             m_currentKeyframe.first = pos;
             m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
@@ -200,6 +204,7 @@ void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration
         m_duration = -1;
         m_selectedKeyframes.clear();
         m_keyframes.clear();
+        m_keyframeTypes.clear();
         return;
     }
     if (service) {
@@ -210,6 +215,7 @@ void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration
     bool keyframeAdded = false;
     if (m_duration > 0 && m_service && !m_keyframes.isEmpty()) {
         m_keyframesOrigin = m_keyframes;
+        m_keyframeTypesOrigin = m_keyframeTypes;
         if (duration > m_duration) {
             // The clip was resized, ensure we have a keyframe at the end of the clip will freeze at last keyframe
             QMap<int, int>::iterator it = m_keyframes.end();
@@ -223,6 +229,7 @@ void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration
                 // Move last keyframe
                 it--;
                 int updatedVal = it.value() + ((lastPos - it.key()) * speeds.first);
+                moveKeyframeType(lastKeyframePos, lastPos);
                 m_keyframes.remove(lastKeyframePos);
                 m_keyframes.insert(lastPos, updatedVal);
             }
@@ -244,9 +251,12 @@ void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration
                 if (!m_keyframes.contains(lastPos)) {
                     int lastKeyframeValue = m_keyframes.value(posToMove);
                     std::pair<double, double> speeds = getSpeed({posToMove, lastKeyframeValue});
+                    moveKeyframeType(posToMove, lastPos);
                     m_keyframes.remove(posToMove);
                     while (!toDelete.isEmpty()) {
-                        m_keyframes.remove(toDelete.takeFirst());
+                        int deletedPos = toDelete.takeFirst();
+                        m_keyframes.remove(deletedPos);
+                        m_keyframeTypes.remove(deletedPos);
                     }
                     int updatedVal = m_keyframes.value(m_keyframes.lastKey()) + ((lastPos - m_keyframes.lastKey()) / speeds.first);
                     m_keyframes.insert(lastPos, updatedVal);
@@ -281,6 +291,7 @@ void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration
 void RemapView::loadKeyframes(const QString &mapData)
 {
     m_keyframes.clear();
+    m_keyframeTypes.clear();
     if (mapData.isEmpty()) {
         if (m_inFrame > 0) {
             // Necessary otherwise clip will freeze before first keyframe
@@ -444,6 +455,7 @@ void RemapView::mouseMoveEvent(QMouseEvent *event)
                     }
                     i.toFront();
                     QMap<int, int> updated;
+                    QMap<int, int> updatedTypes;
                     while (i.hasNext()) {
                         i.next();
                         if (i.key() < m_currentKeyframe.first) {
@@ -451,6 +463,9 @@ void RemapView::mouseMoveEvent(QMouseEvent *event)
                         }
                         // m_keyframes.insert(i.key() + delta, i.value());
                         updated.insert(i.key() + delta, i.value());
+                        if (m_keyframeTypes.contains(i.key())) {
+                            updatedTypes.insert(i.key() + delta, m_keyframeTypes.take(i.key()));
+                        }
                         m_keyframes.remove(i.key());
                     }
                     QMapIterator<int, int> j(updated);
@@ -458,6 +473,7 @@ void RemapView::mouseMoveEvent(QMouseEvent *event)
                         j.next();
                         m_keyframes.insert(j.key(), j.value());
                     }
+                    m_keyframeTypes.insert(updatedTypes);
                     QMap<int, int> updatedSelection;
                     QMapIterator<int, int> k(m_previousSelection);
                     while (k.hasNext()) {
@@ -661,6 +677,11 @@ void RemapView::centerCurrentKeyframe()
         // no move
         return;
     }
+    if (nextKeyframes.isEmpty()) {
+        moveKeyframeType(m_currentKeyframe.first - offset, m_currentKeyframe.first);
+    } else {
+        shiftKeyframeTypes(m_currentKeyframe.first - offset, offset);
+    }
     m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
     QMapIterator<int, int> i(nextKeyframes);
     while (i.hasNext()) {
@@ -725,6 +746,32 @@ void RemapView::centerCurrentTopKeyframe()
     update();
 }
 
+int RemapView::keyframeTypeAt(int pos) const
+{
+    return m_keyframeTypes.value(pos, KeyframeType::Linear);
+}
+
+void RemapView::moveKeyframeType(int oldPos, int newPos)
+{
+    if (m_keyframeTypes.contains(oldPos)) {
+        m_keyframeTypes.insert(newPos, m_keyframeTypes.take(oldPos));
+    }
+}
+
+void RemapView::shiftKeyframeTypes(int fromPos, int offset)
+{
+    QMap<int, int> shifted;
+    QMutableMapIterator<int, int> i(m_keyframeTypes);
+    while (i.hasNext()) {
+        i.next();
+        if (i.key() >= fromPos) {
+            shifted.insert(i.key() + offset, i.value());
+            i.remove();
+        }
+    }
+    m_keyframeTypes.insert(shifted);
+}
+
 std::pair<int, int> RemapView::getClosestKeyframe(int pos, bool bottomKeyframe) const
 {
     int deltaMin = -1;
@@ -772,6 +819,7 @@ void RemapView::mousePressEvent(QMouseEvent *event)
     pos = qBound(0, pos, m_maxLength);
     m_moveKeyframeMode = NoMove;
     m_keyframesOrigin = m_keyframes;
+    m_keyframeTypesOrigin = m_keyframeTypes;
     m_oldInFrame = m_inFrame;
     if (event->button() == Qt::LeftButton) {
         if (event->position().y() < m_centerPos) {
@@ -1119,6 +1167,7 @@ void RemapView::updateBeforeSpeed(double speed)
     QList<int> toDelete;
     if (*it != m_keyframes.first() && it != m_keyframes.end()) {
         m_keyframesOrigin = m_keyframes;
+        m_keyframeTypesOrigin = m_keyframeTypes;
         it--;
         double updatedLength = qFuzzyIsNull(speed) ? 0 : (m_currentKeyframe.second - it.value()) * 100. / speed;
         int lengthInt = qRound(updatedLength);
@@ -1127,6 +1176,11 @@ void RemapView::updateBeforeSpeed(double speed)
             // Rounded target position is identical to the current position; nothing to do.
             return;
         }
+        if (m_moveNext) {
+            shiftKeyframeTypes(m_currentKeyframe.first, offsetInt);
+        } else {
+            moveKeyframeType(m_currentKeyframe.first, it.key() + lengthInt);
+        }
         m_keyframes.remove(m_currentKeyframe.first);
         m_currentKeyframe.first = it.key() + lengthInt;
         m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
@@ -1164,6 +1218,7 @@ void RemapView::updateAfterSpeed(double speed)
 {
     QMutexLocker lock(&m_kfrMutex);
     m_keyframesOrigin = m_keyframes;
+    m_keyframeTypesOrigin = m_keyframeTypes;
     QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
     if (*it != m_keyframes.last()) {
         it++;
@@ -1177,12 +1232,14 @@ void RemapView::updateAfterSpeed(double speed)
             return;
         }
         if (m_moveNext) {
+            shiftKeyframeTypes(it.key(), offsetInt);
             while (it != m_keyframes.end()) {
                 toDelete << it.key();
                 updatedKfrs.insert(it.key() + offsetInt, it.value());
                 it++;
             }
         } else {
+            moveKeyframeType(it.key(), m_currentKeyframe.first + lengthInt);
             m_keyframes.insert(m_currentKeyframe.first + lengthInt, it.value());
             m_keyframes.remove(it.key());
         }
@@ -1284,8 +1341,10 @@ void RemapView::addKeyframe()
 {
     // insert or remove keyframe at interpolated position
     m_keyframesOrigin = m_keyframes;
+    m_keyframeTypesOrigin = m_keyframeTypes;
     if (m_keyframes.contains(m_bottomPosition + m_inFrame)) {
         m_keyframes.remove(m_bottomPosition + m_inFrame);
+        m_keyframeTypes.remove(m_bottomPosition + m_inFrame);
         if (m_currentKeyframe.first == m_bottomPosition + m_inFrame) {
             m_currentKeyframe = m_currentKeyframeOriginal = {-1, -1};
             Q_EMIT selectedKf(m_currentKeyframe, {-1, -1});
diff --git a/src/dialogs/timeremap.h b/src/dialogs/timeremap.h
index 7ba0deaef1..9c847f3f2f 100644
--- a/src/dialogs/timeremap.h
+++ b/src/dialogs/timeremap.h
@@ -101,6 +101,9 @@ private:
     /** @brief the keyframes for the remap effect. first value is output, second is source time */
     QMap<int, int>m_keyframes;
     QMap<int, int>m_keyframesOrigin;
+    /** @brief the interpolation type of each keyframe, keyed by output position. Absent key means linear */
+    QMap<int, int> m_keyframeTypes;
+    QMap<int, int> m_keyframeTypesOrigin;
     std::shared_ptr<ProjectClip> m_clip;
     std::shared_ptr<Mlt::Producer> m_service;
     QPointF m_lastZoomHandle;
@@ -124,6 +127,11 @@ private:
     QMap<int,int>m_previousSelection;
     std::pair<int,int> getClosestKeyframe(int pos, bool bottomKeyframe = false) const;
     std::pair<double,double> getSpeed(std::pair<int,int>kf);
+    int keyframeTypeAt(int pos) const;
+    /** @brief Move the interpolation type of the keyframe at oldPos to newPos */
+    void moveKeyframeType(int oldPos, int newPos);
+    /** @brief Shift the interpolation types of all keyframes at or after fromPos by offset */
+    void shiftKeyframeTypes(int fromPos, int offset);
 
 Q_SIGNALS:
     void seekToPos(int,int);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.