[multimedia/kdenlive] src: Fix all remaining unqualified access qmllint warnings

Julius Künzel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit ecaf30f46d2ed29662142f26549c721e52e257cc by Julius Künzel.
Committed on 03/08/2026 at 21:27.
Pushed by jlskuz into branch 'master'.

Fix all remaining unqualified access qmllint warnings

M  +5    -0    src/monitor/view/EffectToolBar.qml
M  +13   -19   src/monitor/view/MaskToolBar.qml
M  +23   -0    src/monitor/view/MonitorAutomask.qml
M  +4    -3    src/monitor/view/MonitorZoomButton.qml
M  +5    -0    src/monitor/view/MultiScreenToolBar.qml
M  +5    -0    src/monitor/view/SceneToolBar.qml
M  +104  -39   src/timeline2/view/qml/Clip.qml
M  +13   -11   src/timeline2/view/qml/ClipThumbs.qml
M  +59   -11   src/timeline2/view/qml/Composition.qml
M  +17   -9    src/timeline2/view/qml/KeyframeDelegate.qml
M  +23   -10   src/timeline2/view/qml/KeyframeView.qml
M  +35   -40   src/timeline2/view/qml/Ruler.qml
M  +12   -9    src/timeline2/view/qml/RulerZone.qml
M  +30   -23   src/timeline2/view/qml/SubTitle.qml
M  +3    -2    src/timeline2/view/qml/SubtitleTrackHead.qml
M  +58   -11   src/timeline2/view/qml/Timeline.qml
M  +82   -14   src/timeline2/view/qml/Track.qml

https://invent.kde.org/multimedia/kdenlive/-/commit/ecaf30f46d2ed29662142f26549c721e52e257cc

diff --git a/src/monitor/view/EffectToolBar.qml b/src/monitor/view/EffectToolBar.qml
index 241c1e58ae..41e6f384e1 100644
--- a/src/monitor/view/EffectToolBar.qml
+++ b/src/monitor/view/EffectToolBar.qml
@@ -127,6 +127,11 @@ MouseArea {
                 id: zoomButton
                 monitorController: barZone.monitorController
                 isClipMonitor: barZone.isClipMonitor
+                onPopupAboutToHide: {
+                    if (!barZone.containsMouse) {
+                        effecttoolbar.opacity = 0
+                    }
+                }
             }
             K.MonitorToolButton {
                 objectName: "moveBar"
diff --git a/src/monitor/view/MaskToolBar.qml b/src/monitor/view/MaskToolBar.qml
index e5fad86450..e0c3036f30 100644
--- a/src/monitor/view/MaskToolBar.qml
+++ b/src/monitor/view/MaskToolBar.qml
@@ -15,21 +15,16 @@ MouseArea {
     hoverEnabled: true
     required property K.MonitorProxy monitorController
     required property bool isClipMonitor
+    required property int maskMode
     property bool rightSide: true
     acceptedButtons: Qt.NoButton
     width: 2.4 * K.UiUtils.baseSizeMedium
     height: parent.height
-    Timer {
-        id: hideTimer
-        interval: 3000
-        running: false
-        repeat: false
-        onTriggered: {
-            generateLabel.visible = false
-        }
-    }
     SystemPalette { id: activePalette }
 
+    signal generateMask()
+    signal exitMaskPreview()
+
     Rectangle {
         id: effecttoolbar
         objectName: "effecttoolbar"
@@ -69,15 +64,9 @@ MouseArea {
                 iconName: "media-record"
                 toolTipText: KI18n.i18n("Generate Mask")
                 checkable: false
-                visible: root.maskMode != MaskModeType.MaskPreview
+                visible: barZone.maskMode != MaskModeType.MaskPreview
                 onClicked: {
-                    generateLabel.visible = true
-                    if (root.keyframes.length > 0 || (root.boxCoords[2] > 0 && root.boxCoords[3] > 0)) {
-                        root.generateMask()
-                    } else {
-                        // Display the message for 3 seconds
-                        hideTimer.start()
-                    }
+                    barZone.generateMask()
                 }
             }
             K.MonitorToolButton {
@@ -105,16 +94,21 @@ MouseArea {
             K.MonitorToolButton {
                 objectName: "abortMask"
                 iconName: "dialog-close"
-                toolTipText: root.maskMode != MaskModeType.MaskPreview ? KI18n.i18n("Exit Mask Creation") : KI18n.i18n("Exit Preview Mode")
+                toolTipText: barZone.maskMode != MaskModeType.MaskPreview ? KI18n.i18n("Exit Mask Creation") : KI18n.i18n("Exit Preview Mode")
                 checkable: false
                 onClicked: {
-                    root.exitMaskPreview()
+                    barZone.exitMaskPreview()
                 }
             }
             K.MonitorZoomButton {
                 id: zoomButton
                 monitorController: barZone.monitorController
                 isClipMonitor: barZone.isClipMonitor
+                onPopupAboutToHide: {
+                    if (!barZone.containsMouse) {
+                        effecttoolbar.opacity = 0
+                    }
+                }
             }
 
             K.MonitorToolButton {
diff --git a/src/monitor/view/MonitorAutomask.qml b/src/monitor/view/MonitorAutomask.qml
index 805270faf5..e3f634e815 100644
--- a/src/monitor/view/MonitorAutomask.qml
+++ b/src/monitor/view/MonitorAutomask.qml
@@ -333,6 +333,15 @@ Item {
             }
         }
     }
+    Timer {
+        id: hideGenerateLabelTimer
+        interval: 3000
+        running: false
+        repeat: false
+        onTriggered: {
+            generateLabel.visible = false
+        }
+    }
     Label {
         id: generateLabel
         anchors.top: parent.top
@@ -393,6 +402,20 @@ Item {
         }
         monitorController: root.controller
         isClipMonitor: root.isClipMonitor
+        maskMode: root.maskMode
+
+        onExitMaskPreview: () => root.exitMaskPreview()
+        onGenerateMask: () => {
+            generateLabel.visible = true
+
+            var hasObjectSelected = root.keyframes.length > 0 || (root.boxCoords[2] > 0 && root.boxCoords[3] > 0)
+            if (hasObjectSelected) {
+                root.generateMask()
+            } else {
+                // Display the message for 3 seconds
+                hideGenerateLabelTimer.start()
+            }
+        }
     }
     Timer {
         id: firstTimer
diff --git a/src/monitor/view/MonitorZoomButton.qml b/src/monitor/view/MonitorZoomButton.qml
index 20da6ff87e..ec3dbdc0fd 100644
--- a/src/monitor/view/MonitorZoomButton.qml
+++ b/src/monitor/view/MonitorZoomButton.qml
@@ -18,6 +18,9 @@ K.MonitorToolButton {
     required property K.MonitorProxy monitorController
     required property bool isClipMonitor
     property bool menuVisible: zoomContextMenu.visible
+
+    signal popupAboutToHide()
+
     onClicked: {
         if (zoomContextMenu.visible) {
             zoomContextMenu.close()
@@ -31,9 +34,7 @@ K.MonitorToolButton {
         padding: 0
         margins: 0
         onAboutToHide: {
-            if (!barZone.containsMouse) {
-                scenetoolbar.opacity = 0
-            }
+            zoomButton.popupAboutToHide()
         }
 
         ColumnLayout {
diff --git a/src/monitor/view/MultiScreenToolBar.qml b/src/monitor/view/MultiScreenToolBar.qml
index 01cf7a08c1..e2a3a61046 100644
--- a/src/monitor/view/MultiScreenToolBar.qml
+++ b/src/monitor/view/MultiScreenToolBar.qml
@@ -75,6 +75,11 @@ MouseArea {
                 id: zoomButton
                 monitorController: barZone.monitorController
                 isClipMonitor: barZone.isClipMonitor
+                onPopupAboutToHide: {
+                    if (!barZone.containsMouse) {
+                        scenetoolbar.opacity = 0
+                    }
+                }
             }
             K.MonitorToolButton {
                 objectName: "moveBar"
diff --git a/src/monitor/view/SceneToolBar.qml b/src/monitor/view/SceneToolBar.qml
index 5bae8057d1..a7955912f6 100644
--- a/src/monitor/view/SceneToolBar.qml
+++ b/src/monitor/view/SceneToolBar.qml
@@ -94,6 +94,11 @@ MouseArea {
                 id: zoomButton
                 monitorController: barZone.monitorController
                 isClipMonitor: barZone.isClipMonitor
+                onPopupAboutToHide: {
+                    if (!barZone.containsMouse) {
+                        scenetoolbar.opacity = 0
+                    }
+                }
             }
             K.MonitorToolButton {
                 objectName: "addMarker"
diff --git a/src/timeline2/view/qml/Clip.qml b/src/timeline2/view/qml/Clip.qml
index 7a004146ac..314c9e45b2 100644
--- a/src/timeline2/view/qml/Clip.qml
+++ b/src/timeline2/view/qml/Clip.qml
@@ -79,8 +79,21 @@ Rectangle {
     required property double speed
     required property string clipThumbId
     required property bool forceReloadAudioThumb
-    required property bool dragInProgress
-    required property bool snapping
+    required property bool clipDragInProgress
+    required property bool someDragInProgress
+    required property int snapping
+    required property bool isMixSelected
+    required property bool isDragProxied
+    required property int thumbsFormat
+    required property Item dragContainer
+    required property var dragProxyCursorShape
+    required property bool isDragProxyAreaPressed
+    required property int timelineScrollViewWidth
+    required property bool isTimlineCursorOnClip
+    required property bool minClipWidthForViews
+    required property int scrollStart
+    required property bool dragProxyMovesComposition
+    required property int trimmingOffset
 
     readonly property int maxLabelWidth: 20 * K.UiUtils.baseSizeMedium * Math.sqrt(timeScale)
 
@@ -93,14 +106,13 @@ Rectangle {
     readonly property bool isLocked: parentTrack && parentTrack.isLocked === true
     property color borderColor: "#000000"
     property bool isComposition: false
-    readonly property int slipOffset: boundValue(outPoint - maxDuration + 1, root.trimmingOffset, inPoint)
-    property int scrollStart: visible ? scrollView.contentX - (clipRoot.modelStart * timeScale) : 0
+    readonly property int slipOffset: boundValue(outPoint - maxDuration + 1, trimmingOffset, inPoint)
     readonly property bool trimInProgress: trimInMixArea.pressed || trimInMouseArea.pressed || trimOutMouseArea.pressed
     readonly property bool isUserInteracting: mouseArea.pressed || mixArea.pressed || trimInProgress || fadeInMouseArea.pressed || fadeOutMouseArea.pressed
-    readonly property bool hideClipViews: !visible || clipRoot.width < root.minClipWidthForViews
+    readonly property bool hideClipViews: !visible || clipRoot.width < minClipWidthForViews
     readonly property bool hideDecorations: !K.KdenliveSettings.showClipOverlays || !visible || trimInMouseArea.drag.active || trimOutMouseArea.drag.active || fadeInMouseArea.drag.active || fadeOutMouseArea.drag.active
     width : Math.round(clipDuration * timeScale)
-    opacity: dragInProgress ? 0.8 : 1.0
+    opacity: clipDragInProgress ? 0.8 : 1.0
 
     signal trimmingIn(var clip, real newDuration, bool shiftTrim, bool controlTrim)
     signal trimmedIn(var clip, bool shiftTrim, bool controlTrim)
@@ -114,6 +126,15 @@ Rectangle {
     signal seek(int pos)
     signal zoomByWheel(var wheel)
 
+    signal initDrag(var itemCoord)
+    signal endDrag()
+    signal endDragIfFocused(int itemId)
+
+    signal clearGroupTrimData()
+    signal regainFocus(var x, var y)
+
+    signal blockAutoScroll(bool enabled)
+
     onVisibleChanged: {
         if (clipRoot.visible) {
             updateLabelOffset()
@@ -129,7 +150,7 @@ Rectangle {
         if (isAudio && thumbsLoader.item) {
             (thumbsLoader.item as ClipAudioThumbs).reload(1)
         }
-        if (!clipRoot.hideClipViews && clipRoot.width > scrollView.width) {
+        if (!clipRoot.hideClipViews && clipRoot.width > timelineScrollViewWidth) {
             let kfrView = effectRow.item as KeyframeView
             if (kfrView && kfrView.kfrCanvas) {
                 kfrView.kfrCanvas.requestPaint()
@@ -246,7 +267,7 @@ Rectangle {
 
     function updateDrag() {
         var itemPos = Qt.rect(0, 0, clipRoot.width, clipRoot.height)
-        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
+        clipRoot.initDrag(itemPos)
     }
     
     function showClipInfo() {
@@ -305,17 +326,17 @@ Rectangle {
             } else {
                 clipRoot.controller.copyClipEffect(clipRoot.clipId, dropSource)
             }
-            if (K.KdenliveSettings.seekonaddeffect && (proxy.position < clipRoot.modelStart || proxy.position > clipRoot.modelStart + clipRoot.clipDuration)) {
+            if (K.KdenliveSettings.seekonaddeffect && !clipRoot.isTimlineCursorOnClip) {
                 // If timeline cursor is not inside clip, seek to drop position
                 clipRoot.seek(clipRoot.modelStart + drag.x / clipRoot.timeScale)
             }
             dropSource = ''
             drag.acceptProposedAction()
-            root.regainFocus(mapToItem(root, drag.x, drag.y))
+            clipRoot.regainFocus(drag.x, drag.y)
             //console.log('KFR VIEW VISIBLE: ', effectRow.visible, ', SOURCE: ', effectRow.source, '\n HIDEVIEW:', clipRoot.hideClipViews<<', UNDEFINED: ', (clipRoot.keyframeModel == undefined))
         }
         onExited: {
-            root.endDrag()
+            clipRoot.endDrag()
         }
     }
     MouseArea {
@@ -324,14 +345,15 @@ Rectangle {
         anchors.fill: clipRoot
         acceptedButtons: Qt.RightButton
         hoverEnabled: !clipRoot.isPanning && (K.Core.activeTool === K.ToolType.SelectTool || K.Core.activeTool === K.ToolType.RippleTool)
-        cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape
+        cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : clipRoot.dragProxyCursorShape
+
         onPressed: mouse => {
             clipRoot.makeMainItem(true)
             if (mouse.button == Qt.RightButton) {
                 if (clipRoot.timeline.selection.indexOf(clipRoot.clipId) === -1) {
                     clipRoot.controller.requestAddToSelection(clipRoot.clipId, true)
                 }
-                clipRoot.showClipMenu(clipRoot.clipId, Math.round(mouse.x / clipRoot.timeline.scaleFactor))
+                clipRoot.showClipMenu(clipRoot.clipId, Math.round(mouse.x / clipRoot.timeScale))
             }
         }
         Keys.onShortcutOverride: event => {event.accepted = clipRoot.isGrabbed && (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down || event.key === Qt.Key_Escape)}
@@ -384,7 +406,7 @@ Rectangle {
             }
             if (clipRoot.clipId > -1) {
                 var itemPos = Qt.rect(0, 0, width, height)
-                initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
+                clipRoot.initDrag(itemPos)
             }
             clipRoot.showClipInfo()
         }
@@ -393,8 +415,8 @@ Rectangle {
             if (clipRoot.isPanning) {
                 return
             }
-            if (!dragProxyArea.pressed) {
-                root.endDragIfFocused(clipRoot.clipId)
+            if (!clipRoot.isDragProxyAreaPressed) {
+                clipRoot.endDragIfFocused(clipRoot.clipId)
             }
             if (pressed) {
                 if (!trimInMouseArea.containsMouse && !trimOutMouseArea.containsMouse && !compInArea.containsMouse && !compOutArea.containsMouse) {
@@ -411,7 +433,9 @@ Rectangle {
                 visible: !clipRoot.isAudio
                 opacity: clipRoot.clipState === K.PlaylistState.Disabled ? 0.2 : 1
                 parentClip: clipRoot
-                initialSpeed: clipRoot.speed
+                thumbsFormat: clipRoot.thumbsFormat
+                timeScale: clipRoot.timeScale
+                timelineScrollViewWidth: clipRoot.timelineScrollViewWidth
             }
         }
 
@@ -529,10 +553,11 @@ Rectangle {
                 Rectangle {
                     id: mixBackground
                     property double mixPos: mixBackground.width - clipRoot.mixCut * clipRoot.timeScale
-                    property bool mixSelected: root.selectedMix == clipRoot.clipId
                     anchors.fill: parent
                     visible: clipRoot.mixDuration > 0
-                    color: mixSelected ? Qt.rgba(clipRoot.timeline.selectionColor.r, clipRoot.timeline.selectionColor.g, clipRoot.timeline.selectionColor.b, 0.5) : "transparent"
+                    color: clipRoot.isMixSelected
+                           ? Qt.rgba(clipRoot.timeline.selectionColor.r, clipRoot.timeline.selectionColor.g, clipRoot.timeline.selectionColor.b, 0.5)
+                           : "transparent"
                     Loader {
                         active: mixBackground.visible && container.handleVisible && mixContainer.width > 2 * K.UiUtils.baseSizeMedium
                         asynchronous: true
@@ -540,8 +565,8 @@ Rectangle {
                         sourceComponent: MixShape {}
                     }
 
-                    opacity: mixArea.containsMouse || trimInMixArea.pressed || trimInMixArea.containsMouse || mixSelected ? 1 : 0.7
-                    border.color: mixSelected ? clipRoot.timeline.selectionColor : "white"
+                    opacity: mixArea.containsMouse || trimInMixArea.pressed || trimInMixArea.containsMouse || clipRoot.isMixSelected ? 1 : 0.7
+                    border.color: clipRoot.isMixSelected ? clipRoot.timeline.selectionColor : "white"
                     border.width: clipRoot.mixDuration > 0 ? 2 : 0
                     radius: 3
                     Rectangle {
@@ -568,7 +593,7 @@ Rectangle {
                             }
                             clipRoot.controller.requestMixSelection(clipRoot.clipId);
                             if (mouse.button == Qt.RightButton) {
-                                clipRoot.showMixMenu(clipRoot.clipId, Math.round(mouse.x / clipRoot.timeline.scaleFactor))
+                                clipRoot.showMixMenu(clipRoot.clipId, Math.round(mouse.x / clipRoot.timeScale))
                             }
                         }
                         onEntered: {
@@ -1070,7 +1095,9 @@ Rectangle {
 
                     return false
                 }
-                enabled: !clipRoot.isLocked && (pressed || (container.handleVisible && (mixArea.enabled || clipRoot.mixDuration == 0))) && clipRoot.clipId == dragProxy.draggedItem
+                enabled: !clipRoot.isLocked
+                         && (pressed || (container.handleVisible && (mixArea.enabled || clipRoot.mixDuration == 0)))
+                         && clipRoot.isDragProxied
                 hoverEnabled: true
                 drag.target: trimInMouseArea
                 drag.axis: Drag.XAxis
@@ -1104,7 +1131,7 @@ Rectangle {
                         if (!controlTrim && K.Core.activeTool !== K.ToolType.RippleTool) {
                             clipRoot.updateDrag()
                         } else {
-                            root.endDrag()
+                            clipRoot.endDrag()
                         }
                     } else {
                         if (K.Core.activeTool === K.ToolType.RippleTool) { //TODO
@@ -1114,8 +1141,7 @@ Rectangle {
                         } else if (shiftTrim) {
                             clipRoot.controller.requestRemoveFromSelection(clipRoot.clipId)
                         }
-
-                        root.groupTrimData = undefined
+                        clipRoot.clearGroupTrimData()
                     }
                 }
                 onDoubleClicked: {
@@ -1156,10 +1182,10 @@ Rectangle {
                     }
                 }
                 onEntered: {
-                    if (!pressed && !root.isDragging()) {
+                    if (!pressed && !clipRoot.someDragInProgress) {
                         trimIn.opacity = 1
                         var itemPos = Qt.rect(0, 0, width, height)
-                        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
+                        clipRoot.initDrag(itemPos)
                         var s = KI18n.i18n("In:%1, Position:%2", clipRoot.timeline.simplifiedTC(clipRoot.inPoint),clipRoot.timeline.simplifiedTC(clipRoot.modelStart))
                         clipRoot.timeline.showToolTip(s)
                         if (clipRoot.mixDuration == 0) {
@@ -1195,7 +1221,9 @@ Rectangle {
                 hoverEnabled: true
                 visible: enabled && (K.Core.activeTool === K.ToolType.SelectTool
                                      || (K.Core.activeTool === K.ToolType.RippleTool && clipRoot.mixDuration <= 0))
-                enabled: !clipRoot.isLocked && (pressed || container.handleVisible) && clipRoot.clipId == dragProxy.draggedItem
+                enabled: !clipRoot.isLocked
+                         && (pressed || container.handleVisible)
+                         && clipRoot.isDragProxied
                 property bool shiftTrim: false
                 property bool controlTrim: false
                 property bool sizeChanged: false
@@ -1231,7 +1259,7 @@ Rectangle {
                         if (!controlTrim && K.Core.activeTool !== K.ToolType.RippleTool) {
                             clipRoot.updateDrag()
                         } else {
-                            root.endDrag()
+                            clipRoot.endDrag()
                         }
                     } else {
                         if (K.Core.activeTool === K.ToolType.RippleTool) {
@@ -1241,7 +1269,7 @@ Rectangle {
                         } else if (shiftTrim) {
                             clipRoot.controller.requestRemoveFromSelection(clipRoot.clipId)
                         }
-                        root.groupTrimData = undefined
+                        clipRoot.clearGroupTrimData()
                     }
                 }
                 onDoubleClicked: {
@@ -1261,10 +1289,10 @@ Rectangle {
                     }
                 }
                 onEntered: {
-                    if (!pressed && !root.isDragging()) {
+                    if (!pressed && !clipRoot.someDragInProgress) {
                         trimOut.opacity = 1
                         var itemPos = Qt.rect(0, 0, width, height)
-                        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
+                        clipRoot.initDrag(itemPos)
                         var s = KI18n.i18n("Out:%1, Position:%2", clipRoot.timeline.simplifiedTC(clipRoot.outPoint),clipRoot.timeline.simplifiedTC(clipRoot.modelStart + clipRoot.clipDuration))
                         clipRoot.timeline.showToolTip(s)
                         if (!fadeOutMouseArea.containsMouse) {
@@ -1596,11 +1624,48 @@ Rectangle {
                     when: effectRow.status == Loader.Ready && effectRow.item
                     restoreMode: Binding.RestoreBindingOrValue
                 }
+                Binding {
+                    target: effectRow.item
+                    property: "timelineScrollViewWidth"
+                    value: clipRoot.timelineScrollViewWidth
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "timeline"
+                    value: clipRoot.timeline
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "timeScale"
+                    value: clipRoot.timeScale
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "isPanning"
+                    value: clipRoot.isPanning
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+
             }
             Connections {
                 target: effectRow.item
                 function onSeek(position) { clipRoot.seek(position) }
             }
+            Connections {
+                target: effectRow.item
+                function onUpdateEffectKeyframe(clipId, oldFrame, newFrame) { clipRoot.timeline.updateEffectKeyframe(clipId, oldFrame, newFrame) }
+            }
+            Connections {
+                target: effectRow.item
+                function onBlockAutoScroll(enabled) { clipRoot.blockAutoScroll(enabled) }
+            }
         }
 
         states: [
@@ -1664,7 +1729,7 @@ Rectangle {
                 width: compInArea.containsMouse ? parent.width : 5
                 height: width
                 radius: width / 2
-                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !clipRoot.dragInProgress
+                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !clipRoot.clipDragInProgress
                 color: Qt.darker('mediumpurple')
                 border.width: 3
                 border.color: 'mediumpurple'
@@ -1705,7 +1770,7 @@ Rectangle {
                 width: compOutArea.containsMouse ? parent.height : 5
                 height: width
                 radius: width / 2
-                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !clipRoot.dragInProgress
+                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !clipRoot.clipDragInProgress
                 color: Qt.darker('mediumpurple')
                 border.width: 3
                 border.color: 'mediumpurple'
@@ -1727,7 +1792,7 @@ Rectangle {
             drag.axis: Drag.XAxis
             drag.minimumX: - Math.ceil(width / 2)
             drag.maximumX: container.width + Math.ceil(width / 4)
-            visible: container.handleVisible && mouseArea.containsMouse && !clipRoot.dragInProgress
+            visible: container.handleVisible && mouseArea.containsMouse && !clipRoot.clipDragInProgress
             property int startFadeOut
             property int lastDuration: -1
             property int startMousePos
@@ -1806,7 +1871,7 @@ Rectangle {
                 color: 'darkred'
                 border.width: 3
                 border.color: 'red'
-                enabled: !clipRoot.isLocked && !dragProxy.isComposition
+                enabled: !clipRoot.isLocked && !clipRoot.dragProxyMovesComposition
                 Drag.active: fadeOutMouseArea.drag.active
                 Behavior on width { NumberAnimation { duration: 100 } }
                 Rectangle {
@@ -1843,7 +1908,7 @@ Rectangle {
             property int startMousePos
             property bool dragStarted: false
             property string fadeString: clipRoot.timeline.simplifiedTC(clipRoot.fadeIn)
-            visible: container.handleVisible && mouseArea.containsMouse && !clipRoot.dragInProgress
+            visible: container.handleVisible && mouseArea.containsMouse && !clipRoot.clipDragInProgress
             onClicked: {
                 if (clipRoot.fadeIn == 0) {
                     clipRoot.timeline.adjustFade(clipRoot.clipId, 'fadein', 0, -2)
@@ -1915,7 +1980,7 @@ Rectangle {
                 color: 'green'
                 border.width: 3
                 border.color: '#FF66FFFF'
-                enabled: !clipRoot.isLocked && !dragProxy.isComposition
+                enabled: !clipRoot.isLocked && !clipRoot.dragProxyMovesComposition
                 Drag.active: fadeInMouseArea.drag.active
                 Behavior on width { NumberAnimation { duration: 100 } }
                 Rectangle {
diff --git a/src/timeline2/view/qml/ClipThumbs.qml b/src/timeline2/view/qml/ClipThumbs.qml
index 188dccacd7..0758fe63ff 100644
--- a/src/timeline2/view/qml/ClipThumbs.qml
+++ b/src/timeline2/view/qml/ClipThumbs.qml
@@ -20,16 +20,18 @@ Row {
     // When increasing the Qt dependency, we can revert to use :
     // required property Clip parentClip
     required property var parentClip
-    property real initialSpeed: 1
-    property bool fixedThumbs: parentClip.itemType === K.ClipType.Image || parentClip.itemType === K.ClipType.Text || parentClip.itemType === K.ClipType.TextTemplate
-    property int thumbWidth: parent.height * K.Core.getCurrentDar()
-    property bool enableCache: parentClip.itemType === K.ClipType.Video || parentClip.itemType === K.ClipType.AV
+    required property int thumbsFormat
+    required property real timeScale
+    required property int timelineScrollViewWidth
+    readonly property bool fixedThumbs: parentClip.itemType === K.ClipType.Image || parentClip.itemType === K.ClipType.Text || parentClip.itemType === K.ClipType.TextTemplate
+    readonly property int thumbWidth: parent.height * K.Core.getCurrentDar()
+    readonly property bool enableCache: parentClip.itemType === K.ClipType.Video || parentClip.itemType === K.ClipType.AV
 
     Repeater {
         id: thumbRepeater
         // switching the model allows one to have different view modes.
         // We set the model to the number of frames we want to show
-        model: switch (parentTrack.trackThumbsFormat) {
+        model: switch (thumbRow.thumbsFormat) {
                    case 0:
                        // in/out
                        if (parent.width > thumbRow.thumbWidth) {
@@ -57,12 +59,12 @@ Row {
         property real imageWidth: Math.max(thumbRow.thumbWidth, parent.width / thumbRepeater.count)
         property int thumbStartFrame: thumbRow.fixedThumbs ? 0 :
                                                     (thumbRow.parentClip.speed >= 0)
-                                                    ? Math.round(thumbRow.parentClip.inPoint * thumbRow.initialSpeed)
-                                                    : Math.round((thumbRow.parentClip.maxDuration - thumbRow.parentClip.inPoint) * -thumbRow.initialSpeed - 1)
+                                                    ? Math.round(thumbRow.parentClip.inPoint * thumbRow.parentClip.speed)
+                                                    : Math.round((thumbRow.parentClip.maxDuration - thumbRow.parentClip.inPoint) * -thumbRow.parentClip.speed - 1)
         property int thumbEndFrame: thumbRow.fixedThumbs ? 0 :
                                                   (thumbRow.parentClip.speed >= 0)
-                                                  ? Math.round(thumbRow.parentClip.outPoint * thumbRow.initialSpeed)
-                                                  : Math.round((thumbRow.parentClip.maxDuration - thumbRow.parentClip.outPoint) * -thumbRow.initialSpeed - 1)
+                                                  ? Math.round(thumbRow.parentClip.outPoint * thumbRow.parentClip.speed)
+                                                  : Math.round((thumbRow.parentClip.maxDuration - thumbRow.parentClip.outPoint) * -thumbRow.parentClip.speed - 1)
 
         Image {
             id: thumbImage
@@ -79,12 +81,12 @@ Row {
                                        ? 0
                                        : thumbRepeater.count < 3
                                          ? (index === 0 ? thumbRepeater.thumbStartFrame : thumbRepeater.thumbEndFrame)
-                                         : Math.floor(thumbRow.parentClip.inPoint * thumbRow.initialSpeed + Math.round((index) * width / timeline.scaleFactor)* thumbRow.parentClip.speed)
+                                         : Math.floor((thumbRow.parentClip.inPoint + Math.round(index * width / thumbRow.timeScale))* thumbRow.parentClip.speed)
             horizontalAlignment: thumbRepeater.count < 3
                                  ? (index === 0 ? Image.AlignLeft : Image.AlignRight)
                                  : Image.AlignLeft
             source: thumbRepeater.count < 3 ? thumbPath
-                                            : (index * width < thumbRow.parentClip.scrollStart - width || index * width > thumbRow.parentClip.scrollStart + scrollView.width)? '' : thumbPath
+                                            : (index * width < thumbRow.parentClip.scrollStart - width || index * width > thumbRow.parentClip.scrollStart + thumbRow.timelineScrollViewWidth) ? '' : thumbPath
             onStatusChanged: {
                 if (status === Image.Ready && (index === 0  || index === thumbRepeater.count - 1)) {
                     thumbPlaceholder.source = source
diff --git a/src/timeline2/view/qml/Composition.qml b/src/timeline2/view/qml/Composition.qml
index 50f4237da3..db54d5380f 100644
--- a/src/timeline2/view/qml/Composition.qml
+++ b/src/timeline2/view/qml/Composition.qml
@@ -42,6 +42,13 @@ Item {
     required property int aTrack
     required property int clipId // Id of the clip in the model
     required property int displayHeight
+    required property int scrollStart
+    required property int minClipWidthForViews
+    required property bool clipDragInProgress
+    required property Item dragContainer
+    required property int timelineScrollViewWidth
+    required property var dragProxyCursorShape
+    required property bool isDragProxyAreaPressed
 
     property bool isAudio: false
     property int originalTrackId: trackId
@@ -56,10 +63,8 @@ Item {
     property color borderColor: 'black'
     readonly property bool trimInProgress: trimInMouseArea.pressed || trimOutMouseArea.pressed
     readonly property bool isUserInteracting: mouseArea.pressed || trimInProgress
-    readonly property bool hideCompoViews: !visible || width < root.minClipWidthForViews
+    readonly property bool hideCompoViews: !visible || width < minClipWidthForViews
     readonly property bool hideDecorations: !K.KdenliveSettings.showClipOverlays || trimInMouseArea.drag.active || trimOutMouseArea.drag.active
-    visible: scrollView.lastVisibleFrame > compositionRoot.modelStart && scrollView.firstVisibleFrame <= (compositionRoot.modelStart + compositionRoot.clipDuration)
-    property int scrollStart: visible ? scrollView.contentX - (compositionRoot.modelStart * compositionRoot.timeScale) : 0
 
     // We set coordinates to ensure the item can be found using childAt in timeline.qml getItemAtPosq
     property int trackOffset: 0
@@ -73,12 +78,19 @@ Item {
     signal seek(int pos)
     signal zoomByWheel(var wheel)
 
+    signal blockAutoScroll(bool enabled)
+    signal showCompositionMenu()
+    signal makeMainItem()
+
+    signal initDrag(var itemCoord)
+    signal endDragIfFocused(int itemId)
+
     onScrollStartChanged: {
         if (!compositionRoot.visible) {
             return
         }
         updateLabelOffset()
-        if (compositionRoot.width > scrollView.width) {
+        if (compositionRoot.width > timelineScrollViewWidth) {
             let kfrView = effectRow.item as KeyframeView
             if (kfrView && kfrView.kfrCanvas) {
                 kfrView.kfrCanvas.requestPaint()
@@ -171,7 +183,7 @@ Item {
     function updateDrag() {
         console.log('XXXXXXXXXXXXXXX\n\nXXXXXXXXXXXXX \nUPDATING COMPO DRAG')
         var itemPos = Qt.rect(0, displayRect.y, displayRect.width, displayRect.height)
-        initDrag(compositionRoot, itemPos, compositionRoot.clipId, compositionRoot.modelStart, compositionRoot.trackId, true)
+        compositionRoot.initDrag(itemPos)
     }
 
     Rectangle {
@@ -185,7 +197,7 @@ Item {
         color: Qt.darker('mediumpurple')
         border.color: compositionRoot.grouped ? compositionRoot.timeline.groupColor : mouseArea.containsMouse ? activePalette.highlight : compositionRoot.borderColor
         border.width: compositionRoot.isGrabbed ? 8 : 2
-        opacity: dragProxyArea.drag.active && dragProxy.draggedItem == compositionRoot.clipId ? 0.5 : 1.0
+        opacity: compositionRoot.clipDragInProgress ? 0.5 : 1.0
 
         states: [
             State {
@@ -219,7 +231,7 @@ Item {
             id: mouseArea
             anchors.fill: parent
             acceptedButtons: Qt.RightButton
-            enabled: !compositionRoot.isPanning && K.Core.activeTool === K.ToolType.SelectTool && !dragProxyArea.pressed
+            enabled: !compositionRoot.isPanning && K.Core.activeTool === K.ToolType.SelectTool && !compositionRoot.isDragProxyAreaPressed
             hoverEnabled: !compositionRoot.isPanning && K.Core.activeTool === K.ToolType.SelectTool
             Keys.onShortcutOverride: event => {event.accepted = compositionRoot.isGrabbed && (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down || event.key === Qt.Key_Escape)}
             Keys.onLeftPressed: event => {
@@ -239,16 +251,16 @@ Item {
             Keys.onEscapePressed: {
                 compositionRoot.timeline.grabCurrent()
             }
-            cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape
+            cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active) ? Qt.SizeHorCursor : compositionRoot.dragProxyCursorShape
 
             onPressed: mouse => {           
                 compositionRoot.forceActiveFocus();
-                root.mainItemId = compositionRoot.clipId
+                compositionRoot.makeMainItem()
                 if (mouse.button == Qt.RightButton) {
                     if (compositionRoot.timeline.selection.indexOf(compositionRoot.clipId) === -1) {
                         compositionRoot.controller.requestAddToSelection(compositionRoot.clipId, true)
                     }
-                    root.showCompositionMenu()
+                    compositionRoot.showCompositionMenu()
                 }
             }
             onEntered: {
@@ -257,7 +269,7 @@ Item {
                 compositionRoot.timeline.showToolTip(s)
             }
             onExited: {
-                root.endDragIfFocused(compositionRoot.clipId)
+                compositionRoot.endDragIfFocused(compositionRoot.clipId)
                 if (!trimInMouseArea.containsMouse && !trimOutMouseArea.containsMouse) {
                     compositionRoot.timeline.showToolTip()
                 }
@@ -525,11 +537,47 @@ Item {
                     value: compositionRoot.clipId
                     when: effectRow.status == Loader.Ready && effectRow.item
                 }
+                Binding {
+                    target: effectRow.item
+                    property: "timelineScrollViewWidth"
+                    value: compositionRoot.timelineScrollViewWidth
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "timeline"
+                    value: compositionRoot.timeline
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "timeScale"
+                    value: compositionRoot.timeScale
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
+                Binding {
+                    target: effectRow.item
+                    property: "isPanning"
+                    value: compositionRoot.isPanning
+                    when: effectRow.status == Loader.Ready && effectRow.item
+                    restoreMode: Binding.RestoreBindingOrValue
+                }
             }
             Connections {
                 target: effectRow.item
                 function onSeek(position) { compositionRoot.seek(position) }
             }
+            Connections {
+                target: effectRow.item
+                function onUpdateEffectKeyframe(clipId, oldFrame, newFrame) { compositionRoot.timeline.updateEffectKeyframe(clipId, oldFrame, newFrame) }
+            }
+            Connections {
+                target: effectRow.item
+                function onBlockAutoScroll(enabled) { compositionRoot.blockAutoScroll(enabled) }
+            }
         }
     }
 }
diff --git a/src/timeline2/view/qml/KeyframeDelegate.qml b/src/timeline2/view/qml/KeyframeDelegate.qml
index 89edf6e8f3..b507bf0323 100644
--- a/src/timeline2/view/qml/KeyframeDelegate.qml
+++ b/src/timeline2/view/qml/KeyframeDelegate.qml
@@ -19,17 +19,22 @@ Rectangle {
     required property K.TimelineController timeline
     required property var kfrModel
     required property double timeScale
+    required property int keyframeModelOffset
     required property int parentInPoint
     required property int parentItemId
     required property bool allowUserInteraction
+    required property int keyframeContainerHeight
+    required property int keyframeContainerWidth
 
     readonly property bool isUserInteracting: kfMouseArea.pressed || kf1MouseArea.pressed
     readonly property bool isInsideVisibleAreaX: x > K.UiUtils.baseSizeMedium / 2 && x < parent.width - K.UiUtils.baseSizeMedium / 2
 
     signal requestRepaint()
     signal seek(int position)
+    signal seekToIx(int ix)
     signal keyframeSelected(int index, bool add, bool setActive)
     signal resetSelection()
+    signal focusKeyframeContainer()
 
     property int frame : model.frame
     property int frameType : model.type
@@ -50,6 +55,9 @@ Rectangle {
     }
     width: Math.max(1, timeScale / 2)
     color: kfMouseArea.containsMouse ? 'darkred' : 'transparent'
+
+    SystemPalette { id: activePalette }
+
     MouseArea {
         id: kfMouseArea
         anchors.fill: parent
@@ -81,7 +89,7 @@ Rectangle {
                     parent.x = keyframe.parentInPoint * keyframe.timeScale
                     return
                 }
-                var newPos = Math.min(Math.round(parent.x / keyframe.timeScale), Math.round(keyframeContainer.width / keyframe.timeScale) - 1)
+                var newPos = Math.min(Math.round(parent.x / keyframe.timeScale), Math.round(keyframe.keyframeContainerWidth / keyframe.timeScale) - 1)
                 if (newPos < 1) {
                     newPos = 1
                 }
@@ -104,12 +112,12 @@ Rectangle {
     Rectangle {
         id: keyframeVal
         x: - K.UiUtils.baseSizeMedium / 2
-        y: keyframeContainer.height - keyframe.value - K.UiUtils.baseSizeMedium / 2
+        y: keyframe.keyframeContainerHeight - keyframe.value - K.UiUtils.baseSizeMedium / 2
         width: K.UiUtils.baseSizeMedium
         height: width
         radius: width / 2
-        color: keyframe.model.active ? 'red' : keyframe.model.selected ? 'orange' : (kf1MouseArea.containsMouse || kf1MouseArea.pressed) ? root.textColor : root.videoColor
-        border.color: kf1MouseArea.containsMouse || kf1MouseArea.pressed ? activePalette.highlight : root.textColor
+        color: keyframe.model.active ? 'red' : keyframe.model.selected ? 'orange' : (kf1MouseArea.containsMouse || kf1MouseArea.pressed) ? activePalette.text : keyframe.timeline.videoColor
+        border.color: kf1MouseArea.containsMouse || kf1MouseArea.pressed ? activePalette.highlight : activePalette.text
 
         MouseArea {
             id: kf1MouseArea
@@ -127,7 +135,7 @@ Rectangle {
                 drag.axis = keyframe.model.moveOnly ? Drag.XAxis : (mouse.modifiers & Qt.ShiftModifier) ? Drag.YAxis : Drag.XAndYAxis
             }
             onClicked: mouse => {
-                keyframeContainer.focus = true
+                keyframe.focusKeyframeContainer()
                 if (mouse.modifiers & Qt.ControlModifier && keyframe.model.selected) {
                     keyframe.keyframeSelected(keyframe.index, true, false)
                 } else {
@@ -135,7 +143,7 @@ Rectangle {
                 }
                 var ix = keyframe.kfrModel.activeKeyframe()
                 if (ix > -1) {
-                    keyframe.seek(keyframes.itemAt(ix).frame + keyframeContainer.modelStart - keyframe.parentInPoint)
+                    keyframe.seekToIx(ix)
                 }
             }
             onReleased: {
@@ -144,7 +152,7 @@ Rectangle {
                 }
                 var newPos = keyframe.frame == keyframe.parentInPoint ? keyframe.parentInPoint : Math.round((keyframe.x + parent.x + K.UiUtils.baseSizeMedium / 2) / keyframe.timeScale) + keyframe.parentInPoint
                 if (newPos === keyframe.frame && keyframe.value == keyframe.height - parent.y - K.UiUtils.baseSizeMedium / 2) {
-                    var pos = keyframeContainer.modelStart + keyframe.frame - keyframe.parentInPoint
+                    var pos = keyframe.keyframeModelOffset + keyframe.frame - keyframe.parentInPoint
                     keyframe.seek(pos)
                     return
                 }
@@ -179,7 +187,7 @@ Rectangle {
                     if (keyframe.frame == keyframe.parentInPoint) {
                         parent.x = - K.UiUtils.baseSizeMedium / 2
                     } else {
-                        var newPos = Math.min(Math.round((parent.x + K.UiUtils.baseSizeMedium / 2) / keyframe.timeScale), Math.round(keyframeContainer.width / keyframe.timeScale) - keyframe.frame + keyframeContainer.inPoint - 1)
+                        var newPos = Math.min(Math.round((parent.x + K.UiUtils.baseSizeMedium / 2) / keyframe.timeScale), Math.round(keyframe.keyframeContainerWidth / keyframe.timeScale) - keyframe.frame + keyframe.parentInPoint - 1)
                         if (keyframe.frame + newPos <= keyframe.parentInPoint) {
                             newPos = keyframe.parentInPoint + 1 - keyframe.frame
                         }
@@ -192,7 +200,7 @@ Rectangle {
                         }
                     }
                     keyframe.requestRepaint()
-                    newVal = (keyframeContainer.height - (parent.y + mouse.y)) / keyframeContainer.height
+                    newVal = (keyframe.keyframeContainerHeight - (parent.y + mouse.y)) / keyframe.keyframeContainerHeight
                     movingVal = keyframe.kfrModel.realValue(Math.min(Math.max(newVal, 0), 1))
                 }
             }
diff --git a/src/timeline2/view/qml/KeyframeView.qml b/src/timeline2/view/qml/KeyframeView.qml
index 4c491e2e09..fee1a3da48 100644
--- a/src/timeline2/view/qml/KeyframeView.qml
+++ b/src/timeline2/view/qml/KeyframeView.qml
@@ -22,8 +22,14 @@ Rectangle
     property bool selected
     property var kfrModel
     property int scrollStart
+    property int timelineScrollViewWidth
+    property real timeScale
+    property bool isPanning
+    property var timeline
     property alias kfrCanvas: keyframecanvas
     signal seek(int position)
+    signal updateEffectKeyframe(int clipId, int oldFrame, int newFrame)
+    signal blockAutoScroll(bool enabled)
 
     onKfrCountChanged: {
         parent.hasKeyframes = kfrCount > 1
@@ -58,7 +64,7 @@ Rectangle
                 var oldFrame = activeKeyframe.value
                 var newPos = Math.max(oldFrame - 1 - keyframeContainer.inPoint, 0)
                 if (newPos != oldFrame) {
-                    timeline.updateEffectKeyframe(clipId, oldFrame, newPos)
+                    keyframeContainer.updateEffectKeyframe(clipId, oldFrame, newPos)
                     event.accepted = true
                 }
             }
@@ -73,7 +79,7 @@ Rectangle
                 var oldFrame = activeKeyframe.value
                 var newPos = Math.min(oldFrame + 1 - keyframeContainer.inPoint, keyframeContainer.outPoint - keyframeContainer.inPoint)
                 if (newPos != oldFrame) {
-                    timeline.updateEffectKeyframe(clipId, oldFrame, newPos)
+                    keyframeContainer.updateEffectKeyframe(clipId, oldFrame, newPos)
                 }
             }
             event.accepted = true
@@ -108,11 +114,12 @@ Rectangle
             model: keyframeContainer.kfrModel
             KeyframeDelegate {
                 id: keyframe
-                timeScale: root.timeScale
+                timeScale: keyframeContainer.timeScale
                 kfrModel: keyframeContainer.kfrModel
-                timeline: root.timeline
+                timeline: keyframeContainer.timeline
+                keyframeModelOffset: keyframeContainer.modelStart
                 parentInPoint: keyframeContainer.inPoint
-                allowUserInteraction: !root.isPanning
+                allowUserInteraction: !keyframeContainer.isPanning
                 parentItemId: keyframeContainer.clipId
 
                 onRequestRepaint: {
@@ -123,6 +130,10 @@ Rectangle
                     keyframeContainer.seek(position)
                 }
 
+                onSeekToIx: (ix) => {
+                    keyframeContainer.seek((keyframes.itemAt(ix) as KeyframeDelegate).frame + keyframe.keyframeModelOffset - keyframe.parentInPoint)
+                }
+
                 onKeyframeSelected: (index, add, setActive) => {
                     keyframeContainer.kfrModel.setActiveKeyframe(setActive ? keyframe.index : -1)
                     keyframeContainer.activeIndex = setActive ? index : -1
@@ -131,7 +142,9 @@ Rectangle
 
                 onResetSelection: { keyframeContainer.resetSelection() }
 
-                onIsUserInteractingChanged: { root.blockAutoScroll = isUserInteracting }
+                onIsUserInteractingChanged: { keyframeContainer.blockAutoScroll(isUserInteracting) }
+
+                onFocusKeyframeContainer: () => { keyframeContainer.focus = true }
             }
         }
     }
@@ -139,10 +152,10 @@ Rectangle
         id: keyframecanvas
         contextType: "2d"
         renderStrategy: Canvas.Threaded
-        property int offset: keyframeContainer.scrollStart < 0 || parent.width <= scrollView.width ? 0 : keyframeContainer.scrollStart
+        property int offset: keyframeContainer.scrollStart < 0 || parent.width <= keyframeContainer.timelineScrollViewWidth ? 0 : keyframeContainer.scrollStart
         anchors.left: parent.left
         anchors.leftMargin: offset
-        width: keyframeContainer.kfrCount > 0 ? Math.min(parent.width, scrollView.width) : 0
+        width: keyframeContainer.kfrCount > 0 ? Math.min(parent.width, keyframeContainer.timelineScrollViewWidth) : 0
         height: keyframeContainer.kfrCount > 0 ? parent.height : 0
         opacity: keyframeContainer.selected ? 1 : 0.5
         Component {
@@ -165,7 +178,7 @@ Rectangle
         Path {
             id: myPath
             startX: 0
-            startY: parent.height
+            startY: keyframeContainer.parent.height
         }
 
         onPaint: {
@@ -428,7 +441,7 @@ Rectangle
                         paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
                         break;
                 }
-                if (xpos > scrollView.width) {
+                if (xpos > keyframeContainer.timelineScrollViewWidth) {
                     break;
                 }
             }
diff --git a/src/timeline2/view/qml/Ruler.qml b/src/timeline2/view/qml/Ruler.qml
index 9df9a1ceea..58c2be7900 100644
--- a/src/timeline2/view/qml/Ruler.qml
+++ b/src/timeline2/view/qml/Ruler.qml
@@ -21,6 +21,9 @@ Item {
     required property K.TimelineItemModel controller
     required property K.MonitorProxy monitorProxy
     required property K.MarkerSortModel guidesModel
+    required property int snapping
+    required property int rulercontainerWidth
+    required property int scrollViewContentX
 
     FontMetrics {
         id: fontMetrics
@@ -34,21 +37,25 @@ Item {
     // The space we want between each ticks in the ruler
     property real tickSpacing: timeline.scaleFactor
     property alias rulerZone : zone
-    property int workingPreview : timeline.workingPreview
-    property int timecodeOffset : timeline.timecodeOffset
+    readonly property int workingPreview : timeline.workingPreview
+    readonly property int timecodeOffset : timeline.timecodeOffset
     property int labelMod: 1
     property int zoneHeight: Math.ceil(K.UiUtils.baseSizeMedium / 2) + 1
     property bool showZoneLabels: false
     readonly property bool resizeActive: zone.resizeActive || effectZone.resizeActive // Used to decide which mouse cursor we should display
     property bool hoverGuide: false
     property bool hoverResizeHandle: false
-    property int cursorShape: resizeActive ? Qt.SizeHorCursor : hoverResizeHandle ? Qt.SizeHorCursor : hoverGuide ? Qt.PointingHandCursor : Qt.ArrowCursor
-    property var effectZones: timeline.masterEffectZones
-    property int guideLabelHeight: K.KdenliveSettings.showmarkers ? fontMetrics.height : 0
-    property int previewHeight: Math.ceil(timecodeContainer.height / 5)
+    readonly property int cursorShape: resizeActive ? Qt.SizeHorCursor : hoverResizeHandle ? Qt.SizeHorCursor : hoverGuide ? Qt.PointingHandCursor : Qt.ArrowCursor
+    readonly property var effectZones: timeline.masterEffectZones
+    readonly property int guideLabelHeight: K.KdenliveSettings.showmarkers ? fontMetrics.height : 0
+    readonly property int previewHeight: Math.ceil(timecodeContainer.height / 5)
     property color dimmedColor: (activePalette.text.r + activePalette.text.g + activePalette.text.b > 1.5) ? Qt.darker(activePalette.text, 1.3) : Qt.lighter(activePalette.text, 1.3)
     property color dimmedColor2: (activePalette.text.r + activePalette.text.g + activePalette.text.b > 1.5) ? Qt.darker(activePalette.text, 2.2) : Qt.lighter(activePalette.text, 2.2)
     
+    signal showRulerMenu()
+    signal zoomByWheel(var wheel)
+    signal markerActivated(int frame)
+
     function adjustStepSize() {
         if (timeline.scaleFactor > 19) {
             // Frame size >= 20 pixels
@@ -61,7 +68,7 @@ Item {
         }
         rulerRoot.labelMod = Math.max(1, Math.ceil((rulerRoot.labelSize + K.UiUtils.baseSizeMedium) / rulerRoot.tickSpacing))
         //console.log('LABELMOD: ', Math.ceil((rulerRoot.labelSize + root.fontUnit) / rulerRoot.tickSpacing)))
-        tickRepeater.model = Math.ceil(rulercontainer.width / rulerRoot.tickSpacing) + 2
+        tickRepeater.model = Math.ceil(rulercontainerWidth / rulerRoot.tickSpacing) + 2
     }
 
     function adjustFormat() {
@@ -73,7 +80,7 @@ Item {
     function repaintRuler() {
         // Enforce repaint
         tickRepeater.model = 0
-        tickRepeater.model = Math.ceil(rulercontainer.width / rulerRoot.tickSpacing) + 2
+        tickRepeater.model = Math.ceil(rulercontainerWidth / rulerRoot.tickSpacing) + 2
     }
 
     // Timeline preview stuff
@@ -225,7 +232,7 @@ Item {
                     onPositionChanged: mouse => {
                         if (pressed) {
                             var newFrame = Math.max(0, Math.round(guideRoot.model.frame + (mouseX - xOffset) / rulerRoot.timeline.scaleFactor))
-                            newFrame = rulerRoot.controller.suggestSnapPoint(newFrame, mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping)
+                            newFrame = rulerRoot.controller.suggestSnapPoint(newFrame, mouse.modifiers & Qt.ShiftModifier ? -1 : rulerRoot.snapping)
                             if (newFrame != destFrame) {
                                 var frame = rulerRoot.timeline.moveGuideWithoutUndo(movingMarkerId, newFrame)
                                 if (frame > -1) {
@@ -241,7 +248,7 @@ Item {
                             rulerRoot.monitorProxy.position = guideRoot.model.frame
                         }
                         if (mouse.button == Qt.RightButton) {
-                            root.showRulerMenu()
+                            rulerRoot.showRulerMenu()
                         }
                     }
                     onEntered: {
@@ -300,7 +307,7 @@ Item {
 
                                 var deltaFrames = Math.round(realDeltaX / rulerRoot.timeline.scaleFactor)
                                 var newStartPosition = Math.max(0, startPosition + deltaFrames)
-                                newStartPosition = rulerRoot.controller.suggestSnapPoint(newStartPosition, mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping)
+                                newStartPosition = rulerRoot.controller.suggestSnapPoint(newStartPosition, mouse.modifiers & Qt.ShiftModifier ? -1 : rulerRoot.snapping)
                                 var newDuration = Math.max(1, originalEndPosition - newStartPosition)
 
                                 currentNewStartPosition = newStartPosition
@@ -387,7 +394,7 @@ Item {
                                 
                                 var deltaFrames = Math.round(realDeltaX / rulerRoot.timeline.scaleFactor)
                                 var newDuration = Math.max(1, startDuration + deltaFrames)
-                                newDuration = rulerRoot.controller.suggestSnapPoint(newDuration + startPosition, mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping) - startPosition
+                                newDuration = rulerRoot.controller.suggestSnapPoint(newDuration + startPosition, mouse.modifiers & Qt.ShiftModifier ? -1 : rulerRoot.snapping) - startPosition
                                 
                                 rangeSpan.width = Math.max(1, newDuration * rulerRoot.timeline.scaleFactor)
                                 
@@ -504,7 +511,7 @@ Item {
                                 rulerRoot.timeline.moveGuideWithoutUndo(movingMarkerId, prevFrame)
                                 rulerRoot.timeline.moveGuideById(movingMarkerId, destFrame)
                             } else {
-                                root.markerActivated(prevFrame)
+                                rulerRoot.markerActivated(prevFrame)
                             }
                             rulerRoot.timeline.pauseGuideSorting(false)
                             anchors.left = parent.left
@@ -513,7 +520,7 @@ Item {
                         onPositionChanged: mouse => {
                             if (pressed) {
                                 var newFrame = Math.max(0, Math.round(guideRoot.model.frame + (mouseX - xOffset) / rulerRoot.timeline.scaleFactor))
-                                newFrame = rulerRoot.controller.suggestSnapPoint(newFrame, mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping)
+                                newFrame = rulerRoot.controller.suggestSnapPoint(newFrame, mouse.modifiers & Qt.ShiftModifier ? -1 : rulerRoot.snapping)
                                 if (newFrame != destFrame) {
                                     var frame = rulerRoot.timeline.moveGuideWithoutUndo(movingMarkerId, newFrame)
                                     if (frame > -1) {
@@ -529,7 +536,7 @@ Item {
                                 rulerRoot.monitorProxy.position = guideRoot.model.frame
                             }
                             if (mouse.button == Qt.RightButton) {
-                                root.showRulerMenu()
+                                rulerRoot.showRulerMenu()
                             }
                         }
                         onEntered: {
@@ -555,8 +562,8 @@ Item {
         anchors.right: parent.right
     Repeater {
         id: tickRepeater
-        model: Math.ceil(rulercontainer.width / rulerRoot.tickSpacing) + 2
-        property int offset: Math.floor(scrollView.contentX /rulerRoot.tickSpacing)
+        model: Math.ceil(rulerRoot.rulercontainerWidth / rulerRoot.tickSpacing) + 2
+        property int offset: Math.floor(rulerRoot.scrollViewContentX / rulerRoot.tickSpacing)
         Item {
             required property int index
             property int realPos: (tickRepeater.offset + index) * rulerRoot.tickSpacing / rulerRoot.timeline.scaleFactor
@@ -591,7 +598,7 @@ Item {
                 var pos = Math.max(mouseX, 0)
                 var frame = Math.round(pos / rulerRoot.timeline.scaleFactor)
                 if (mouse.modifiers & Qt.AltModifier) {
-                    frame = rulerRoot.controller.suggestPlayheadSnapPoint(frame, root.snapping)
+                    frame = rulerRoot.controller.suggestPlayheadSnapPoint(frame, rulerRoot.snapping)
                 }
                 rulerRoot.monitorProxy.position = frame
                 mouse.accepted = true
@@ -602,7 +609,7 @@ Item {
                 var pos = Math.max(mouseX, 0)
                 var frame = Math.round(pos / rulerRoot.timeline.scaleFactor)
                 if (mouse.modifiers & Qt.AltModifier) {
-                    frame = rulerRoot.controller.suggestPlayheadSnapPoint(frame, root.snapping)
+                    frame = rulerRoot.controller.suggestPlayheadSnapPoint(frame, rulerRoot.snapping)
                 }
                 rulerRoot.monitorProxy.position = frame
             }
@@ -614,7 +621,7 @@ Item {
         }
         onWheel: wheel => {
             if (wheel.modifiers & Qt.ControlModifier) {
-                root.zoomByWheel(wheel)
+                rulerRoot.zoomByWheel(wheel)
             } else {
                 wheel.accepted = false
             }
@@ -625,16 +632,10 @@ Item {
         id: zone
         z: 3
         timeline: rulerRoot.timeline
-        Binding {
-            target: zone
-            property: "frameIn"
-            value: rulerRoot.timeline.zoneIn
-        }
-        Binding {
-            target: zone
-            property: "frameOut"
-            value: rulerRoot.timeline.zoneOut
-        }
+        frameIn: rulerRoot.timeline.zoneIn
+        frameOut: rulerRoot.timeline.zoneOut
+        snapping: rulerRoot.snapping
+        showZoneLabels: rulerRoot.showZoneLabels
         color: rulerRoot.timeline.useRuler ? Qt.rgba(activePalette.highlight.r,activePalette.highlight.g,activePalette.highlight.b,0.9) :
         Qt.rgba(activePalette.highlight.r,activePalette.highlight.g,activePalette.highlight.b,0.5)
         anchors.bottom: parent.bottom
@@ -663,16 +664,10 @@ Item {
         id: effectZone
         z: 2
         timeline: rulerRoot.timeline
-        Binding {
-            target: effectZone
-            property: "frameIn"
-            value: rulerRoot.timeline.effectZone.x
-        }
-        Binding {
-            target: effectZone
-            property: "frameOut"
-            value: rulerRoot.timeline.effectZone.y
-        }
+        frameIn: rulerRoot.timeline.effectZone.x
+        frameOut: rulerRoot.timeline.effectZone.y
+        snapping: rulerRoot.snapping
+        showZoneLabels: rulerRoot.showZoneLabels
         color: "orchid"
         anchors.bottom: parent.bottom
         height: rulerRoot.zoneHeight - 1
diff --git a/src/timeline2/view/qml/RulerZone.qml b/src/timeline2/view/qml/RulerZone.qml
index 0c77b357fd..2ef444772b 100644
--- a/src/timeline2/view/qml/RulerZone.qml
+++ b/src/timeline2/view/qml/RulerZone.qml
@@ -14,8 +14,11 @@ Rectangle {
     id: rzone
     required property K.TimelineController timeline
 
-    property int frameIn: 0
-    property int frameOut: 0
+    required property int frameIn
+    required property int frameOut
+    required property int snapping
+    required property bool showZoneLabels
+
     property bool resizeActive: false
 
     signal updateZone(point start, point end, bool update)
@@ -84,12 +87,12 @@ Rectangle {
             onPositionChanged: mouse => {
                 if (mouse.buttons === Qt.LeftButton) {
                     rzone.resizeActive = true
-                    var offset = Math.round(mouseX/ rzone.timeline.scaleFactor)
+                    var offset = Math.round(mouseX / rzone.timeline.scaleFactor)
                     if (offset != 0) {
-                        var newPos = Math.max(0, controller.suggestSnapPoint(rzone.frameIn + offset,mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping))
+                        var newPos = Math.max(0, rzone.timeline.suggestSnapPoint(rzone.frameIn + offset, mouse.modifiers & Qt.ShiftModifier ? -1 : rzone.snapping))
                         if (newPos == rzone.frameIn + offset) {
                             // No snap at start, check end
-                            var newPos = Math.max(0, controller.suggestSnapPoint(rzone.frameOut + offset,mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping))
+                            var newPos = Math.max(0, rzone.timeline.suggestSnapPoint(rzone.frameOut + offset, mouse.modifiers & Qt.ShiftModifier ? -1 : rzone.snapping))
                             if (newPos == rzone.frameOut + offset) {
                                 newPos = rzone.frameIn + offset
                             } else {
@@ -142,7 +145,7 @@ Rectangle {
         id: durationRect
         anchors.bottom: rzone.top
         visible: (!rzone.timeline.useRuler && moveMouseArea.containsMouse && !moveMouseArea.pressed)
-                 || ((rzone.timeline.useRuler || trimInMouseArea.drag.active || trimOutMouseArea.drag.active) && showZoneLabels && parent.width > 3 * width)
+                 || ((rzone.timeline.useRuler || trimInMouseArea.drag.active || trimOutMouseArea.drag.active) && rzone.showZoneLabels && parent.width > 3 * width)
                  || (rzone.timeline.useRuler && !trimInMouseArea.drag.active && !trimOutMouseArea.drag.active)
         anchors.horizontalCenter: parent.horizontalCenter
         width: durationLabel.contentWidth + 4
@@ -197,7 +200,7 @@ Rectangle {
                 onPositionChanged: mouse => {
                     if (mouse.buttons === Qt.LeftButton) {
                         rzone.resizeActive = true
-                        var newPos = controller.suggestSnapPoint(rzone.frameIn + Math.round(trimIn.x / rzone.timeline.scaleFactor), mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping)
+                        var newPos = rzone.timeline.suggestSnapPoint(rzone.frameIn + Math.round(trimIn.x / rzone.timeline.scaleFactor), mouse.modifiers & Qt.ShiftModifier ? -1 : rzone.snapping)
                         if (newPos < 0) {
                             newPos = 0
                         }
@@ -247,8 +250,8 @@ Rectangle {
                     if (mouse.buttons === Qt.LeftButton) {
                         rzone.resizeActive = true
                         rzone.frameOut = Math.max(
-                                               controller.suggestSnapPoint(rzone.frameIn + Math.round((trimOut.x + trimOut.width) / rzone.timeline.scaleFactor),
-                                                                           mouse.modifiers & Qt.ShiftModifier ? -1 : root.snapping),
+                                               rzone.timeline.suggestSnapPoint(rzone.frameIn + Math.round((trimOut.x + trimOut.width) / rzone.timeline.scaleFactor),
+                                                                           mouse.modifiers & Qt.ShiftModifier ? -1 : rzone.snapping),
                                                rzone.frameIn + 1)
                     }
                 }
diff --git a/src/timeline2/view/qml/SubTitle.qml b/src/timeline2/view/qml/SubTitle.qml
index b4e34313fc..a400ece71b 100644
--- a/src/timeline2/view/qml/SubTitle.qml
+++ b/src/timeline2/view/qml/SubTitle.qml
@@ -20,6 +20,10 @@ Item {
     required property K.TimelineItemModel controller
     required property bool isPanning
     required property double timeScale
+    required property int snapping
+    required property Flickable timelineScrollView
+    required property int consumerPosition
+    required property var groupTrimData
 
     readonly property bool isUserInteracting: subtitleClipArea.pressed || startMouseArea.pressed || endMouseArea.pressed
 
@@ -33,10 +37,15 @@ Item {
     property bool selected
     property bool isGrabbed: false
     property int subLayer
-    height: subtitleTrack.height / (timeline.maxSubLayer + 1)
     property int handleWidth: Math.max(2, Math.ceil(K.UiUtils.baseSizeMedium / 4))
     y: height * subLayer
 
+    signal subtitleMoving(var subtitleItem)
+    signal setGroupTrimData(var data)
+    signal editSubtitle(int id, string newText, string oldText)
+    signal showSubtitleClipMenu()
+    signal continuousScrolling(int x, int y, int upMove)
+
     function editText()
     {
         subtitleBase.textEditBegin = true
@@ -135,7 +144,7 @@ Item {
                     return
                 }
                 console.log('ENTERED ITEM CLICKED:', subtitleRoot.subtitle, ' ID: ', subtitleRoot.subId, 'START FRAME: ', subtitleRoot.startFrame)
-                oldStartX = scrollView.contentX + mapToItem(scrollView, mouseX, 0).x
+                subtitleRoot.oldStartX = subtitleRoot.timelineScrollView.contentX + mapToItem(subtitleRoot.timelineScrollView, mouseX, 0).x
                 oldStartFrame = subtitleRoot.startFrame
                 oldLayer = subtitleRoot.subLayer
                 snappedFrame = oldStartFrame
@@ -143,8 +152,7 @@ Item {
                 x = subtitleBase.x
                 startMove = mouse.button & Qt.LeftButton
                 if (startMove) {
-                    root.subtitleMoving = true
-                    root.subtitleItem = subtitleClipArea
+                    subtitleRoot.subtitleMoving(subtitleClipArea)
                     incrementalOffset = 0
                 }
                 if (subtitleRoot.timeline.selection.indexOf(subtitleRoot.subId) === -1) {
@@ -162,9 +170,9 @@ Item {
             function checkOffset(offset) {
                 if (pressed && !subtitleBase.textEditBegin && startMove) {
                     incrementalOffset += offset
-                    newStart = Math.max(0, oldStartFrame + (scrollView.contentX + mapToItem(scrollView,mouseX, 0).x + incrementalOffset - subtitleRoot.oldStartX)/ subtitleRoot.timeScale)
-                    snappedFrame = subtitleRoot.controller.suggestSubtitleMove(subtitleRoot.subId, subtitleRoot.subLayer, newStart, root.consumerPosition, root.snapping)
-                    root.continuousScrolling(scrollView.contentX + mapToItem(scrollView, mouseX, 0).x + incrementalOffset, subtitleRoot.timeScale)
+                    newStart = Math.max(0, oldStartFrame + (subtitleRoot.timelineScrollView.contentX + mapToItem(subtitleRoot.timelineScrollView, mouseX, 0).x + incrementalOffset - subtitleRoot.oldStartX)/ subtitleRoot.timeScale)
+                    snappedFrame = subtitleRoot.controller.suggestSubtitleMove(subtitleRoot.subId, subtitleRoot.subLayer, newStart, subtitleRoot.consumerPosition, subtitleRoot.snapping)
+                    subtitleRoot.continuousScrolling(subtitleRoot.timelineScrollView.contentX + mapToItem(subtitleRoot.timelineScrollView, mouseX, 0).x + incrementalOffset, subtitleRoot.timeScale)
                 }
             }
             onPositionChanged: (mouse) => {
@@ -180,8 +188,7 @@ Item {
                 }
             }
             onReleased: mouse => {
-                root.subtitleMoving = false
-                root.subtitleItem = undefined
+                subtitleRoot.subtitleMoving(undefined)
                 if (subtitleBase.textEditBegin) {
                     mouse.accepted = false
                     return
@@ -194,7 +201,7 @@ Item {
                     if (mouse.y > subtitleRoot.height && mouse.modifiers & Qt.ShiftModifier) {
                         snappedLayer++
                     }
-                    console.log("old start frame", oldStartFrame/subtitleRoot.timeline.scaleFactor, "new frame after shifting ", oldStartFrame/subtitleRoot.timeline.scaleFactor + delta)
+                    console.log("old start frame", oldStartFrame/subtitleRoot.timeline.scaleFactor, "new frame after shifting ", oldStartFrame / subtitleRoot.timeline.scaleFactor + delta)
                     subtitleRoot.controller.requestSubtitleMove(subtitleRoot.subId, oldLayer, oldStartFrame, false, false)
                     subtitleRoot.controller.requestSubtitleMove(subtitleRoot.subId, snappedLayer, snappedFrame, true, true, true)
                     x = snappedFrame * subtitleRoot.timeScale
@@ -204,7 +211,7 @@ Item {
             onClicked: mouse => {
                 if (mouse.button == Qt.RightButton) {
                     //console.log('RIGHT BUTTON CLICKED')
-                    root.showSubtitleClipMenu()
+                    subtitleRoot.showSubtitleClipMenu()
                 }
             }
             onDoubleClicked: {
@@ -252,7 +259,7 @@ Item {
                 subtitleEdit.focus = false
                 parent.textEditBegin = false
                 if (subtitleRoot.subtitle != subtitleEdit.text) {
-                    subtitleModel.editSubtitle(subtitleRoot.subId, subtitleEdit.text, subtitleRoot.subtitle)
+                    subtitleRoot.editSubtitle(subtitleRoot.subId, subtitleEdit.text, subtitleRoot.subtitle)
                 }
             }
             anchors.fill: parent
@@ -320,14 +327,14 @@ Item {
                 newDuration = subtitleRoot.duration
                 shiftTrim = mouse.modifiers & Qt.ShiftModifier
                 if (!shiftTrim && (subtitleRoot.controller.isInGroup(subtitleRoot.subId) || subtitleRoot.controller.hasMultipleSelection())) {
-                    root.groupTrimData = subtitleRoot.controller.getGroupData(subtitleRoot.subId)
+                    subtitleRoot.setGroupTrimData(subtitleRoot.controller.getGroupData(subtitleRoot.subId))
                 }
             }
             onPositionChanged: {
                 if (pressed) {
                     newDuration = subtitleRoot.endFrame - Math.round(leftstart.x / subtitleRoot.timeScale)
                     if (newDuration != originalDuration && subtitleBase.x >= 0) {
-                        var frame = subtitleRoot.controller.requestItemResize(subtitleRoot.subId, newDuration , false, false, root.snapping, shiftTrim);
+                        var frame = subtitleRoot.controller.requestItemResize(subtitleRoot.subId, newDuration , false, false, subtitleRoot.snapping, shiftTrim);
                         if (frame > 0) {
                             newStart = subtitleRoot.endFrame - frame
                         }
@@ -339,15 +346,15 @@ Item {
                 trimIn.opacity = 0
                 leftstart.anchors.left = subtitleBase.left
                 if (oldStartFrame != newStart) {
-                    if (shiftTrim || (root.groupTrimData == undefined || K.Core.activeTool === K.ToolType.RippleTool)) {
+                    if (shiftTrim || (subtitleRoot.groupTrimData == undefined || K.Core.activeTool === K.ToolType.RippleTool)) {
                         subtitleRoot.controller.requestItemResize(subtitleRoot.subId, subtitleRoot.endFrame - oldStartFrame, false, false);
                         subtitleRoot.controller.requestItemResize(subtitleRoot.subId, subtitleRoot.endFrame - newStart, false, true, -1, shiftTrim);
                     } else {
                         var updatedGroupData = subtitleRoot.controller.getGroupData(subtitleRoot.subId)
-                        subtitleRoot.controller.processGroupResize(root.groupTrimData, updatedGroupData, false)
+                        subtitleRoot.controller.processGroupResize(subtitleRoot.groupTrimData, updatedGroupData, false)
                     }
                 }
-                root.groupTrimData = undefined
+                subtitleRoot.setGroupTrimData(undefined)
             }
             onEntered: {
                 if (!pressed) {
@@ -418,7 +425,7 @@ Item {
                 oldMouseX = mouseX
                 shiftTrim = mouse.modifiers & Qt.ShiftModifier
                 if (!shiftTrim && (subtitleRoot.controller.isInGroup(subtitleRoot.subId) || subtitleRoot.controller.hasMultipleSelection())) {
-                    root.groupTrimData = subtitleRoot.controller.getGroupData(subtitleRoot.subId)
+                    subtitleRoot.setGroupTrimData(subtitleRoot.controller.getGroupData(subtitleRoot.subId))
                 }
             }
             onPositionChanged: {
@@ -426,9 +433,9 @@ Item {
                     if ((mouseX != oldMouseX && subtitleRoot.duration > 1) || (subtitleRoot.duration <= 1 && mouseX > oldMouseX)) {
                         sizeChanged = true
                         //duration = subtitleBase.width + (mouseX - oldMouseX)/ timeline.scaleFactor
-                        newDuration = Math.round((subtitleBase.width + mouseX - oldMouseX)/timeScale)
+                        newDuration = Math.round((subtitleBase.width + mouseX - oldMouseX) / subtitleRoot.timeScale)
                         // Perform resize without changing model
-                        var frame = subtitleRoot.controller.requestItemResize(subtitleRoot.subId, newDuration , true, false, root.snapping, shiftTrim);
+                        var frame = subtitleRoot.controller.requestItemResize(subtitleRoot.subId, newDuration , true, false, subtitleRoot.snapping, shiftTrim);
                         if (frame > 0) {
                             newDuration = frame
                         }
@@ -440,18 +447,18 @@ Item {
                 rightend.anchors.right = subtitleBase.right
                 console.log(' GOT RESIZE: ', newDuration, ' > ', originalDuration)
                 if (mouseX != oldMouseX || sizeChanged) {
-                    if (shiftTrim || (root.groupTrimData == undefined || K.Core.activeTool === K.ToolType.RippleTool)) {
+                    if (shiftTrim || (subtitleRoot.groupTrimData == undefined || K.Core.activeTool === K.ToolType.RippleTool)) {
                         // Restore original size
                         subtitleRoot.controller.requestItemResize(subtitleRoot.subId, originalDuration , true, false);
                         // Perform real resize
                         subtitleRoot.controller.requestItemResize(subtitleRoot.subId, newDuration , true, true, -1, shiftTrim)
                     } else {
                         var updatedGroupData = subtitleRoot.controller.getGroupData(subtitleRoot.subId)
-                        subtitleRoot.controller.processGroupResize(root.groupTrimData, updatedGroupData, true)
+                        subtitleRoot.controller.processGroupResize(subtitleRoot.groupTrimData, updatedGroupData, true)
                     }
                     sizeChanged = false
                 }
-                root.groupTrimData = undefined
+                subtitleRoot.setGroupTrimData(undefined)
             }
             onEntered: {
                 console.log('ENTER MOUSE END AREA')
diff --git a/src/timeline2/view/qml/SubtitleTrackHead.qml b/src/timeline2/view/qml/SubtitleTrackHead.qml
index d607501fb1..55c2e57785 100644
--- a/src/timeline2/view/qml/SubtitleTrackHead.qml
+++ b/src/timeline2/view/qml/SubtitleTrackHead.qml
@@ -31,6 +31,7 @@ Rectangle {
     required property color selectedTrackColor
     required property color frameColor
     required property int trackTagWidth
+    required property bool isSubtitleTrackVisible
 
     signal toogleExpandTrack()
 
@@ -70,7 +71,7 @@ Rectangle {
         anchors.right: parent.right
         anchors.top: expandSubButton.bottom
         anchors.left: subtitleLayerIndicator.right
-        visible: (subtitleTrack.visible && !trackHeader.collapsed)
+        visible: (trackHeader.isSubtitleTrackVisible && !trackHeader.collapsed)
         flat: true
         onActivated: index => {
             trackHeader.timeline.subtitlesMenuActivatedAsync(index)
@@ -161,7 +162,7 @@ Rectangle {
         anchors.top: trackHeader.top
         anchors.bottom: trackHeader.bottom
         property color bgColor: Qt.darker(trackHeader.trackHeaderColor, 0.55)
-        visible: trackHeader.timeline.maxSubLayer > 0 && subtitleTrack.visible && !trackHeader.collapsed
+        visible: trackHeader.timeline.maxSubLayer > 0 && trackHeader.isSubtitleTrackVisible && !trackHeader.collapsed
 
         Repeater {
             model: subtitleLayerIndicator.visible ? trackHeader.timeline.maxSubLayer + 1 : 0
diff --git a/src/timeline2/view/qml/Timeline.qml b/src/timeline2/view/qml/Timeline.qml
index 0eaf7ace30..7ecf022c12 100644
--- a/src/timeline2/view/qml/Timeline.qml
+++ b/src/timeline2/view/qml/Timeline.qml
@@ -34,17 +34,16 @@ Rectangle {
     required property K.SubtitleModel subtitleModel
 
     property bool validMenu: false
-    property bool subtitleMoving: false
-    property var subtitleItem
+    property var subtitleItem: undefined
     property color textColor: activePalette.text
     property var groupTrimData
     property bool trimInProgress: false
-    property bool isPanning: tracksArea.isCursorHidden
-    property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active || groupTrimData !== undefined || spacerGroup > -1 || trimInProgress || clipDropArea.containsDrag || compoArea.containsDrag
+    readonly property bool isPanning: tracksArea.isCursorHidden
+    readonly property bool isItemDragInProgress: compoArea.containsDrag || clipDropArea.containsDrag
+    readonly property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active || groupTrimData !== undefined || spacerGroup > -1 || trimInProgress || isItemDragInProgress
     property int trimmingOffset: 0
     property int trimmingClickFrame: -1
 
-    readonly property bool isItemDragInProgress: compoArea.containsDrag || clipDropArea.containsDrag
 
     function screenForGlobalPos(globalPos) {
         const screens = Application.screens
@@ -395,14 +394,10 @@ function getTrackColor(audio, header) {
         sameTrackIndicator.visible = false
     }
 
-    function isDragging() {
-        return dragInProgress
-    }
-
     function initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) {
         var mappedItemCoord = itemObject.mapToItem(tracksContainerArea, itemCoord)
 
-        dragProxy.x = itemObject.modelStart * root.timeScale
+        dragProxy.x = itemPos * root.timeScale
         dragProxy.y = mappedItemCoord.y
         dragProxy.width = itemObject.clipDuration * root.timeScale
         dragProxy.height = mappedItemCoord.height
@@ -1256,6 +1251,7 @@ function getTrackColor(audio, header) {
                     selectedTrackColor: root.selectedTrackColor
                     trackHeaderColor: root.getTrackColor(false, true)
                     trackTagWidth: root.trackTagWidth
+                    isSubtitleTrackVisible: subtitleTrack.visible
 
                     onToogleExpandTrack: {
                         if (subtitleTrack.height > root.collapsedHeight) {
@@ -1852,6 +1848,14 @@ function getTrackColor(audio, header) {
                         controller: root.controller
                         monitorProxy: root.proxy
                         guidesModel: root.guidesModel
+                        snapping: root.snapping
+                        rulercontainerWidth: rulercontainer.width
+                        scrollViewContentX: scrollView.contentX
+
+                        onShowRulerMenu: () => { root.showRulerMenu() }
+                        onZoomByWheel: (wheel) => { root.zoomByWheel(wheel) }
+                        onMarkerActivated: (frame) => { root.markerActivated(frame) }
+
                         K.TimelinePlayhead {
                             id: playhead
                             height: Math.round(K.UiUtils.baseSizeMedium * .8)
@@ -2528,6 +2532,22 @@ function getTrackColor(audio, header) {
             snapping: root.snapping
             isPanning: root.isPanning
             isItemDragInProgress: root.isItemDragInProgress
+            groupTrimData: root.groupTrimData
+            timelineDragContainer: dragContainer
+            // timelineDragContainer: dragProxyArea
+            dragProxyCursorShape: dragProxyArea.cursorShape
+            draggedItemId: dragProxy.draggedItem
+            isDragProxyAreaPressed: dragProxyArea.pressed
+            isDragProxyAreaDragActive: dragProxyArea.drag.active
+            playheadPos: root.proxy.position
+            timelineScrollView: scrollView
+            minClipWidthForViews: root.minClipWidthForViews
+            mainItemId: root.mainItemId
+            trimmingOffset: root.trimmingOffset
+            selectedMix: root.selectedMix
+            dragProxyMovesComposition: dragProxy.isComposition
+            isSomeDragInProgress: root.dragInProgress
+
             z: tracksRepeater.count - index
 
             onBlockAutoScroll: (enabled) => { root.blockAutoScroll = enabled }
@@ -2542,6 +2562,23 @@ function getTrackColor(audio, header) {
                 root.clickFrame = clickFrame
                 root.showClipMenu(clipId)
             }
+
+            onShowCompositionMenu: () => { root.showCompositionMenu() }
+
+            onInitDrag:(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) => {
+                root.initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition)
+            }
+
+            onUpdateGroupTrimData: (data) => { root.groupTrimData = data }
+
+            onUpdateTrimInProgress: (trimInProgress) => { root.trimInProgress = trimInProgress }
+
+            onRegainFocus: (x, y) => { root.regainFocus(mapToItem(root, x, y)) }
+
+            onEndDragIfFocused: (itemId) => { root.endDragIfFocused(itemId) }
+
+            onEndDrag: () => { root.endDrag() }
+
         }
     }
 
@@ -2569,6 +2606,9 @@ function getTrackColor(audio, header) {
         model: root.subtitleModel
         delegate: SubTitle {
             required property var model
+
+            height: subtitleTrack.height / (timeline.maxSubLayer + 1)
+
             subId: model.id
             selected: model.selected
             startFrame: model.startframe
@@ -2581,8 +2621,15 @@ function getTrackColor(audio, header) {
             controller: root.controller
             isPanning: root.isPanning
             timeScale: root.timeScale
+            consumerPosition: root.consumerPosition
+            groupTrimData: root.groupTrimData
 
             onIsUserInteractingChanged: { root.blockAutoScroll = isUserInteracting }
+            onSubtitleMoving: (subtitleItem) => { root.subtitleItem = subtitleItem }
+            onSetGroupTrimData: (data) => { root.groupTrimData = data }
+            onEditSubtitle: (id, newText, oldText) => { root.subtitleModel.editSubtitle(id, newText, oldText) }
+            onShowSubtitleClipMenu: () => { root.showSubtitleClipMenu() }
+            onContinuousScrolling: (x, y, upMove) => { root.continuousScrolling(x, y, upMove) }
         }
     }
 
@@ -2649,7 +2696,7 @@ function getTrackColor(audio, header) {
                     dragProxyArea.moveItem()
                 }
                 if (scrollView.contentX == 0 || (root.clipBeingMovedId == -1 && root.clipBeingDroppedId == -1 && !rubberSelect.visible)) {
-                    if (root.subtitleMoving) {
+                    if (root.subtitleItem) {
                         root.subtitleItem.checkOffset(horizontal)
                     } else {
                         horizontal = 0
diff --git a/src/timeline2/view/qml/Track.qml b/src/timeline2/view/qml/Track.qml
index fd383f51ed..304bc5a9f1 100644
--- a/src/timeline2/view/qml/Track.qml
+++ b/src/timeline2/view/qml/Track.qml
@@ -26,12 +26,35 @@ Item {
     required property var effectZones
     required property bool isPanning
     required property bool isItemDragInProgress
+    required property bool isSomeDragInProgress
+    required property var groupTrimData
+    required property Item timelineDragContainer
+    required property var dragProxyCursorShape
+    required property bool dragProxyMovesComposition
+    required property int draggedItemId
+    required property bool isDragProxyAreaPressed
+    required property bool isDragProxyAreaDragActive
+    required property int playheadPos
+    required property var timelineScrollView
+    required property int minClipWidthForViews
+    required property int mainItemId
+    required property int trimmingOffset
+    required property int selectedMix
 
     signal blockAutoScroll(bool enabled)
     signal seek(int pos)
     signal zoomByWheel(var wheel)
     signal showMixMenu(int clipId, int clickFrame)
     signal showClipMenu(int clipId, int clickFrame)
+    signal showCompositionMenu()
+    signal updateGroupTrimData(var data)
+    signal updateTrimInProgress(bool trimInProgress)
+    signal regainFocus(var x, var y)
+    signal endDragIfFocused(int itemId)
+    signal endDrag()
+
+
+    signal initDrag(var itemObject, var itemCoord, var itemId, var itemPos, var itemTrack, var isComposition)
 
     property alias trackModel: trackModel.model
     property alias rootIndex : trackModel.rootIndex
@@ -116,7 +139,7 @@ Item {
     function trimedClip(clip, shiftTrim, controlTrim, right) {
         //bubbleHelp.hide()
         trackRoot.timeline.showToolTip();
-        if (shiftTrim || (root.groupTrimData == undefined/*TODO > */ || K.Core.activeTool === K.ToolType.RippleTool /* < TODO*/) || controlTrim) {
+        if (shiftTrim || (trackRoot.groupTrimData == undefined/*TODO > */ || K.Core.activeTool === K.ToolType.RippleTool /* < TODO*/) || controlTrim) {
             // We only resize one element
             if (K.Core.activeTool === K.ToolType.RippleTool) {
                 trackRoot.timeline.requestItemRippleResize(clip.clipId, clip.originalDuration, right, false, 0, shiftTrim)
@@ -139,9 +162,9 @@ Item {
             }
         } else {
             var updatedGroupData = trackRoot.controller.getGroupData(clip.clipId)
-            trackRoot.controller.processGroupResize(root.groupTrimData, updatedGroupData, right)
+            trackRoot.controller.processGroupResize(trackRoot.groupTrimData, updatedGroupData, right)
         }
-        root.groupTrimData = undefined
+        trackRoot.updateGroupTrimData(undefined)
     }
 
     width: clipRow.width
@@ -176,11 +199,12 @@ Item {
             Loader {
                 id: clipLoader
                 active: trackRoot.isClip(itemOnTrack.model.clipType)
-                sourceComponent: Clip {                    
+                sourceComponent: Clip {
+                    id: clipItem
                     height: trackRoot.height
 
                     enabled: !trackRoot.isItemDragInProgress
-                    visible: fakeTid > -1 || (scrollView.lastVisibleFrame > modelStart && scrollView.firstVisibleFrame <= (modelStart + clipDuration))
+                    visible: fakeTid > -1 || (trackRoot.timelineScrollView.lastVisibleFrame > modelStart && trackRoot.timelineScrollView.firstVisibleFrame <= (modelStart + clipDuration))
 
                     parentTrack: trackRoot
                     timeline: trackRoot.timeline
@@ -208,7 +232,7 @@ Item {
                     fadeOutMethod: itemOnTrack.model.fadeOutMethod
                     showKeyframes: itemOnTrack.model.showKeyframes
                     isGrabbed: itemOnTrack.model.isGrabbed
-                    isMainItem: root.mainItemId == clipId
+                    isMainItem: trackRoot.mainItemId == clipId
                     keyframeModel: itemOnTrack.model.keyframeModel
                     clipDuration: itemOnTrack.model.duration
                     inPoint: itemOnTrack.model.in
@@ -234,27 +258,52 @@ Item {
                     itemType: itemOnTrack.model.clipType
                     trackId: itemOnTrack.model.trackId
                     isPanning: trackRoot.isPanning
-                    dragInProgress: dragProxyArea.drag.active && dragProxy.draggedItem === clipId
+                    clipDragInProgress: trackRoot.isDragProxyAreaDragActive && trackRoot.draggedItemId === clipId
+                    someDragInProgress: trackRoot.isSomeDragInProgress
                     snapping: trackRoot.snapping
+                    isMixSelected: trackRoot.selectedMix == clipId
+                    isDragProxied: trackRoot.draggedItemId == clipId
+                    thumbsFormat: trackRoot.trackThumbsFormat
+                    dragContainer: trackRoot.timelineDragContainer
+                    dragProxyCursorShape: trackRoot.dragProxyCursorShape
+                    isDragProxyAreaPressed: trackRoot.isDragProxyAreaPressed
+                    isTimlineCursorOnClip: trackRoot.playheadPos > clipItem.modelStart && trackRoot.playheadPos < clipItem.modelStart + clipItem.clipDuration
+                    timelineScrollViewWidth: trackRoot.timelineScrollView.width
+                    minClipWidthForViews: trackRoot.minClipWidthForViews
+                    scrollStart: visible ? trackRoot.timelineScrollView.contentX - (modelStart * timeScale) : 0
+                    dragProxyMovesComposition: trackRoot.dragProxyMovesComposition
+                    trimmingOffset: trackRoot.trimmingOffset
 
                     onInitGroupTrim: clipId => {
                         // We are resizing a group, remember coordinates of all elements
-                        root.groupTrimData = trackRoot.controller.getGroupData(clipId)
+                        trackRoot.updateGroupTrimData(trackRoot.controller.getGroupData(clipId))
                     }
                     onTrimmingIn: (clip, newDuration, shiftTrim, controlTrim) => { trackRoot.clipTrimming(clip, newDuration, shiftTrim, controlTrim, false) }
                     onTrimmedIn: (clip, shiftTrim, controlTrim) => { trackRoot.trimedClip(clip, shiftTrim, controlTrim, false) }
                     onTrimmingOut: (clip, newDuration, shiftTrim, controlTrim) => { trackRoot.clipTrimming(clip, newDuration, shiftTrim, controlTrim, true) }
                     onTrimmedOut: (clip, shiftTrim, controlTrim) => { trackRoot.trimedClip(clip, shiftTrim, controlTrim, true) }
                     onIsUserInteractingChanged: { trackRoot.blockAutoScroll(isUserInteracting) }
-                    onTrimInProgressChanged: { root.trimInProgress = trimInProgress }
+                    onBlockAutoScroll: (enabled) => { trackRoot.blockAutoScroll(enabled) }
+
+                    onTrimInProgressChanged: { trackRoot.updateTrimInProgress(trimInProgress) }
 
                     onShowMixMenu: (clipId, clickFrame) => trackRoot.showMixMenu(clipId, clickFrame)
                     onShowClipMenu: (clipId, clickFrame) => trackRoot.showClipMenu(clipId, clickFrame)
-                    onMakeMainItem: (enabled) => { root.mainItemId = enabled ? clipId : -1 }
-
+                    onMakeMainItem: (enabled) => { trackRoot.mainItemId = enabled ? clipId : -1 }
 
                     onSeek: (pos) => { trackRoot.seek(pos) }
                     onZoomByWheel: (wheel) => { trackRoot.zoomByWheel(wheel) }
+
+                    onInitDrag: (itemCoord) => {
+                        trackRoot.initDrag(clipItem, itemCoord, clipItem.clipId, clipItem.modelStart, clipItem.trackId, false)
+                    }
+
+                    onEndDrag: () => { trackRoot.endDrag() }
+                    onEndDragIfFocused: (itemId) => { trackRoot.endDragIfFocused(itemId) }
+
+                    onClearGroupTrimData: () => { trackRoot.updateGroupTrimData(undefined) }
+
+                    onRegainFocus: (x, y) => { trackRoot.regainFocus(x, y) }
                 }
                 onLoaded: {
                     console.log('loaded clip: ', itemOnTrack.model.start, ', ID: ', itemOnTrack.model.item, ', index: ', trackRoot.DelegateModel.itemsIndex,', TYPE:', itemOnTrack.model.clipType)
@@ -265,8 +314,8 @@ Item {
                 id: compositionLoader
                 active: itemOnTrack.model.clipType == K.ClipType.Composition
                 sourceComponent: Composition {
+                    id: compositionItem
                     opacity: 0.8
-
                     enabled: !trackRoot.isItemDragInProgress
 
                     parentTrack: trackRoot
@@ -286,7 +335,8 @@ Item {
                     isGrabbed: itemOnTrack.model.isGrabbed
                     keyframeModel: itemOnTrack.model.keyframeModel
                     aTrack: itemOnTrack.model.a_track
-                    visible: !itemOnTrack.model.hideItem
+                    readonly property bool isInViewFrame: trackRoot.timelineScrollView.lastVisibleFrame > modelStart && trackRoot.timelineScrollView.firstVisibleFrame <= (modelStart + clipDuration)
+                    visible: !itemOnTrack.model.hideItem && isInViewFrame
                     clipDuration: itemOnTrack.model.duration
                     inPoint: itemOnTrack.model.in
                     outPoint: itemOnTrack.model.out
@@ -294,6 +344,13 @@ Item {
                     clipName: itemOnTrack.model.name
                     selected: itemOnTrack.model.selected
                     isPanning: trackRoot.isPanning
+                    scrollStart: visible ? trackRoot.timelineScrollView.contentX - (modelStart * timeScale) : 0
+                    minClipWidthForViews: trackRoot.minClipWidthForViews
+                    clipDragInProgress: trackRoot.isDragProxyAreaDragActive && trackRoot.draggedItemId === clipId
+                    dragContainer: trackRoot.timelineDragContainer
+                    dragProxyCursorShape: trackRoot.dragProxyCursorShape
+                    isDragProxyAreaPressed: trackRoot.isDragProxyAreaPressed
+                    timelineScrollViewWidth: trackRoot.timelineScrollView.width
 
                     onTrimmingIn: (clip, newDuration) => {
                         var new_duration = trackRoot.controller.requestItemResize(clip.clipId, newDuration, false, false, trackRoot.snapping)
@@ -334,10 +391,21 @@ Item {
                     }
 
                     onIsUserInteractingChanged: { trackRoot.blockAutoScroll(isUserInteracting) }
-                    onTrimInProgressChanged: { root.trimInProgress = trimInProgress }
+                    onBlockAutoScroll: (enabled) => { trackRoot.blockAutoScroll(enabled) }
+
+                    onTrimInProgressChanged: { trackRoot.updateTrimInProgress(trimInProgress) }
+
+                    onShowCompositionMenu: () => trackRoot.showCompositionMenu()
+                    onMakeMainItem: () => { trackRoot.mainItemId = clipId }
 
                     onSeek: (pos) => { trackRoot.seek(pos) }
                     onZoomByWheel: (wheel) => { trackRoot.zoomByWheel(wheel) }
+
+
+                    onInitDrag: (itemCoord) => {
+                        trackRoot.initDrag(compositionItem, itemCoord, compositionItem.clipId, compositionItem.modelStart, compositionItem.trackId, true)
+                    }
+                    onEndDragIfFocused: (itemId) => { trackRoot.endDragIfFocused(itemId) }
                 }
                 onLoaded: {
                     console.log('loaded composition: ', itemOnTrack.model.start, ', ID: ', itemOnTrack.model.item, ', index: ', trackRoot.DelegateModel.itemsIndex)
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.