[multimedia/kdenlive/release/26.08] src/timeline2: Fix duplicate clip only duplicating one clip

Jean-Baptiste Mardelle <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 80cdc71f7ee8aaf4b64e6f3e2df19de73d16cb38 by Jean-Baptiste Mardelle.
Committed on 23/07/2026 at 07:36.
Pushed by mardelle into branch 'release/26.08'.

Fix duplicate clip only duplicating one clip
Related to !878

M  +15   -6    src/timeline2/model/timelinefunctions.cpp
M  +4    -3    src/timeline2/model/timelinefunctions.hpp
M  +13   -14   src/timeline2/view/timelinecontroller.cpp

https://invent.kde.org/multimedia/kdenlive/-/commit/80cdc71f7ee8aaf4b64e6f3e2df19de73d16cb38

diff --git a/src/timeline2/model/timelinefunctions.cpp b/src/timeline2/model/timelinefunctions.cpp
index ad3177521d..1974f2ef23 100644
--- a/src/timeline2/model/timelinefunctions.cpp
+++ b/src/timeline2/model/timelinefunctions.cpp
@@ -1954,12 +1954,13 @@ QString TimelineFunctions::copyClips(const std::shared_ptr<TimelineItemModel> &t
     return copiedItems.toString();
 }
 
-bool TimelineFunctions::pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position)
+bool TimelineFunctions::pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position,
+                                   QString historyText, bool select)
 {
     std::function<bool(void)> undo = []() { return true; };
     std::function<bool(void)> redo = []() { return true; };
-    if (TimelineFunctions::pasteClips(timeline, pasteString, trackId, position, undo, redo)) {
-        pCore->pushUndo(undo, redo, i18n("Paste clips"));
+    if (TimelineFunctions::pasteClips(timeline, pasteString, trackId, position, undo, redo, 0, -1, select)) {
+        pCore->pushUndo(undo, redo, historyText.isEmpty() ? i18n("Paste clips") : historyText);
         return true;
     }
     return false;
@@ -2038,7 +2039,7 @@ bool TimelineFunctions::pasteClipsWithUndo(const std::shared_ptr<TimelineItemMod
 }
 
 bool TimelineFunctions::pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position, Fun &undo,
-                                   Fun &redo, int inPos, int duration)
+                                   Fun &redo, int inPos, int duration, bool select)
 {
     timeline->requestClearSelection();
     if (!semaphore.tryAcquire(1)) {
@@ -2573,7 +2574,7 @@ bool TimelineFunctions::pasteClips(const std::shared_ptr<TimelineItemModel> &tim
 
     if (!clipsImported) {
         // Clips from same document, directly proceed to pasting
-        bool result = TimelineFunctions::pasteTimelineClips(timeline, copiedItems, position, undo, redo, false, inPos, duration);
+        bool result = TimelineFunctions::pasteTimelineClips(timeline, copiedItems, position, undo, redo, false, inPos, duration, select);
         if (result && updatedPosition > 0) {
             pCore->seekMonitor(Kdenlive::ProjectMonitor, updatedPosition);
         }
@@ -2592,7 +2593,7 @@ bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemMod
 }
 
 bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemModel> &timeline, QDomDocument copiedItems, int position, Fun &timeline_undo,
-                                           Fun &timeline_redo, bool pushToStack, int inPos, int duration)
+                                           Fun &timeline_redo, bool pushToStack, int inPos, int duration, bool select)
 {
     // Wait until all bin clips are inserted
     QDomNodeList clips = copiedItems.documentElement().elementsByTagName(QStringLiteral("clip"));
@@ -2607,6 +2608,8 @@ bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemMod
         offset *= ratio;
     }
 
+    std::unordered_set<int> pastedItems;
+
     QDomElement documentMixes = copiedItems.createElement(QStringLiteral("mixes"));
     for (int i = 0; i < clips.count(); i++) {
         QDomElement prod = clips.at(i).toElement();
@@ -2710,6 +2713,7 @@ bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemMod
         if (targetPlaylist > 0) {
             timeline->m_allClips[newId]->setSubPlaylistIndex(targetPlaylist, curTrackId);
         }
+        pastedItems.insert(newId);
         correspondingIds[targetId] = newId;
         std::shared_ptr<EffectStackModel> destStack = timeline->getClipEffectStackModel(newId);
         destStack->fromXml(prod.firstChildElement(QStringLiteral("effects")), timeline_undo, timeline_redo);
@@ -2831,6 +2835,7 @@ bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemMod
             if (compoDuration != compoDuration2) {
                 timeline->requestItemResize(newId, compoDuration2, true, true, timeline_undo, timeline_redo, false);
             }
+            pastedItems.insert(newId);
             res = res && timeline->requestCompositionMove(newId, curTrackId, aTrackPos, position + newPos, true, true, timeline_undo, timeline_redo);
         }
     }
@@ -2870,6 +2875,10 @@ bool TimelineFunctions::pasteTimelineClips(const std::shared_ptr<TimelineItemMod
         qDebug() << "after Selection " << timeline->m_currentSelection.size();
         return true;
     };
+    if (select) {
+        // Select all items if requested
+        timeline->requestSetSelection(pastedItems);
+    }
     PUSH_FRONT_LAMBDA(unselect, timeline_undo);
     PUSH_FRONT_LAMBDA(unselect, timeline_redo);
     // UPDATE_UNDO_REDO_NOLOCK(timeline_redo, timeline_undo, undo, redo);
diff --git a/src/timeline2/model/timelinefunctions.hpp b/src/timeline2/model/timelinefunctions.hpp
index 34e588c954..1044072f65 100644
--- a/src/timeline2/model/timelinefunctions.hpp
+++ b/src/timeline2/model/timelinefunctions.hpp
@@ -54,15 +54,16 @@ struct TimelineFunctions
     static QString copyClips(const std::shared_ptr<TimelineItemModel> &timeline, const std::unordered_set<int> &itemIds, int mainClip = -1);
 
     /** @brief Paste the clips as described by the string. Returns true on success*/
-    static bool pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position);
+    static bool pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position,
+                           QString historyText = QString(), bool select = false);
     static bool pasteClips(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position, Fun &undo, Fun &redo,
-                           int inPos = 0, int duration = -1);
+                           int inPos = 0, int duration = -1, bool select = false);
     static bool pasteClipsWithUndo(const std::shared_ptr<TimelineItemModel> &timeline, const QString &pasteString, int trackId, int position, Fun &undo,
                                    Fun &redo);
     static bool pasteTimelineClips(const std::shared_ptr<TimelineItemModel> &timeline, const QDomDocument &copiedItems, int position, int inPos = 0,
                                    int duration = -1);
     static bool pasteTimelineClips(const std::shared_ptr<TimelineItemModel> &timeline, QDomDocument copiedItems, int position, Fun &timeline_undo,
-                                   Fun &timeline_redo, bool pushToStack, int inPos = 0, int duration = -1);
+                                   Fun &timeline_redo, bool pushToStack, int inPos = 0, int duration = -1, bool select = false);
 
     /** @brief Request the addition of multiple clips to the timeline
      * If the addition of any of the clips fails, the entire operation is undone.
diff --git a/src/timeline2/view/timelinecontroller.cpp b/src/timeline2/view/timelinecontroller.cpp
index 49e110df4a..ec70542bcd 100644
--- a/src/timeline2/view/timelinecontroller.cpp
+++ b/src/timeline2/view/timelinecontroller.cpp
@@ -923,25 +923,24 @@ void TimelineController::cutItem()
 
 void TimelineController::duplicateClip()
 {
-    int clipId = getMainSelectedClip();
-    if (clipId == -1) {
+    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
+    if (selectedIds.empty()) {
         pCore->displayMessage(i18n("No clip selected"), ErrorMessage, 500);
         return;
     }
+    int clipId = getMainSelectedClip();
+    if (clipId == -1) {
+        clipId = *selectedIds.begin();
+    }
     int trackId = m_model->getItemTrackId(clipId);
-    int endPos = m_model->getItemPosition(clipId) + m_model->getItemPlaytime(clipId);
-    Fun undo = []() { return true; };
-    Fun redo = []() { return true; };
-    int newId = -1;
-    PlaylistState::ClipState state = m_model->m_allClips[clipId]->clipState();
-    bool res = TimelineFunctions::cloneClip(m_model, clipId, newId, state, -1, undo, redo);
-    if (res && newId != -1) {
-        res = (m_model->requestClipMove(newId, trackId, endPos, true, true, true, true, undo, redo) == TimelineModel::MoveSuccess);
+    // Get paste position (last frame of the selection)
+    int lastFrame = 0;
+    for (auto &id : selectedIds) {
+        int last = m_model->getItemPosition(id) + m_model->getItemPlaytime(id);
+        lastFrame = qMax(last, lastFrame);
     }
-    if (res) {
-        pCore->pushUndo(undo, redo, i18n("Duplicate clip"));
-    } else {
-        undo();
+    const QString copyString = TimelineFunctions::copyClips(m_model, selectedIds, clipId);
+    if (!TimelineFunctions::pasteClips(m_model, copyString, trackId, lastFrame, i18n("Duplicate clip"), true)) {
         pCore->displayMessage(i18n("Could not duplicate clip"), ErrorMessage, 500);
     }
 }
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.