Warning, /multimedia/kdenlive/src/timeline2/view/qml/timeline.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2017-2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-FileCopyrightText: 2017 Nicolas Carion
0004     SPDX-FileCopyrightText: 2020 Sashmita Raghav
0005     SPDX-FileCopyrightText: 2021 Julius Künzel <jk.kdedev@smartlab.uber.space>
0006 
0007     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 */
0009 
0010 import QtQuick 2.15
0011 import QtQml.Models 2.15
0012 import QtQuick.Controls 2.15
0013 import Kdenlive.Controls 1.0
0014 import 'Timeline.js' as Logic
0015 import com.enums 1.0
0016 import org.kde.kdenlive 1.0 as Kdenlive
0017 
0018 Rectangle {
0019     id: root
0020     objectName: "timelineview"
0021     SystemPalette { id: activePalette }
0022     color: activePalette.window
0023     property bool debugmode: false
0024     property bool validMenu: false
0025     property bool subtitleMoving: false
0026     property var subtitleItem
0027     property color textColor: activePalette.text
0028     property var groupTrimData
0029     property bool trimInProgress: false
0030     property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active || groupTrimData !== undefined || spacerGroup > -1 || trimInProgress || clipDropArea.containsDrag
0031     property int trimmingOffset: 0
0032     property int trimmingClickFrame: -1
0033     Timer {
0034         id: doubleClickTimer
0035         interval: root.doubleClickInterval
0036         repeat: false
0037     }
0038 
0039     signal clipClicked()
0040     signal mousePosChanged(int position)
0041     signal showClipMenu(int cid)
0042     signal showMixMenu(int cid)
0043     signal showCompositionMenu()
0044     signal showTimelineMenu()
0045     signal showRulerMenu()
0046     signal showHeaderMenu()
0047     signal showTargetMenu(int ix)
0048     signal zoomIn(bool onMouse)
0049     signal zoomOut(bool onMouse)
0050     signal processingDrag(bool dragging)
0051     signal showSubtitleClipMenu()
0052 
0053     FontMetrics {
0054         id: fontMetrics
0055         font: miniFont
0056     }
0057 
0058     onDragInProgressChanged: {
0059         processingDrag(!root.dragInProgress)
0060     }
0061 
0062     function endBinDrag() {
0063         clipDropArea.processDrop()
0064     }
0065 
0066     function startAudioRecord(tid) {
0067         var tk = Logic.getTrackById(tid)
0068         recordPlaceHolder.y = Qt.binding(function() { return tk.y + subtitleTrack.height })
0069         recordPlaceHolder.height = Qt.binding(function() { return tk.height })
0070         var startFrame = root.consumerPosition
0071         recordStartPlaceHolder.x = Qt.binding(function() { return startFrame * root.timeScale })
0072         recordPlaceHolder.visible = true
0073         recordPlaceHolder.width = Qt.binding(function() { return audiorec.recDuration * root.timeScale })
0074     }
0075 
0076     function stopAudioRecord() {
0077         recordPlaceHolder.visible = false
0078         recordStartPlaceHolder.x = 0
0079         recordStartPlaceHolder.y = 0
0080         recordPlaceHolder.width = 0
0081         recordPlaceHolder.height = 0
0082     }
0083 
0084     function fitZoom() {
0085         return scrollView.width / (timeline.duration * 1.1)
0086     }
0087 
0088     function scrollPos() {
0089         return scrollView.contentX
0090     }
0091 
0092     function goToStart(pos) {
0093         scrollView.contentX = pos
0094     }
0095 
0096     function switchSubtitleTrack() {
0097         if (subtitleTrack.height > root.collapsedHeight) {
0098             subtitleTrack.height = root.collapsedHeight
0099         } else {
0100             subtitleTrack.height = 5 * root.baseUnit
0101         }
0102     }
0103 
0104     function highlightSub(ix) {
0105         var currentSub = subtitlesRepeater.itemAt(ix)
0106         currentSub.editText()
0107     }
0108 
0109     function checkDeletion(itemId) {
0110         if (dragProxy.draggedItem === itemId) {
0111             endDrag()
0112         }
0113         if (itemId === mainItemId) {
0114             mainItemId = -1
0115         }
0116     }
0117 
0118     function getActiveTrackStreamPos() {
0119         // Return the relative y click position, to display the context menu
0120         return Logic.getTrackYFromId(timeline.activeTrack) + rulercontainer.height - scrollView.contentY
0121     }
0122 
0123     function updatePalette() {
0124         root.color = activePalette.window
0125         root.textColor = activePalette.text
0126         playhead.fillColor = activePalette.windowText
0127         ruler.dimmedColor = (activePalette.text.r + activePalette.text.g + activePalette.text.b > 1.5) ? Qt.darker(activePalette.text, 1.3) : Qt.lighter(activePalette.text, 1.3)
0128         ruler.dimmedColor2 = (activePalette.text.r + activePalette.text.g + activePalette.text.b > 1.5) ? Qt.darker(activePalette.text, 2.2) : Qt.lighter(activePalette.text, 2.2)
0129         ruler.repaintRuler()
0130         // Disable caching for track header icons
0131         root.paletteUnchanged = false
0132     }
0133 
0134     function moveSelectedTrack(offset) {
0135         var newTrack
0136         var max = tracksRepeater.count;
0137         if (timeline.activeTrack < 0 ) {
0138             if (offset <0) {
0139                 newTrack = -2
0140             } else {
0141                 if (showSubtitles) {
0142                     newTrack = 0
0143                 } else {
0144                     newTrack = max
0145                 }
0146             }
0147         } else {
0148             var cTrack = Logic.getTrackIndexFromId(timeline.activeTrack)
0149             newTrack = cTrack + offset
0150         }
0151         if (newTrack < 0) {
0152             if (showSubtitles && newTrack === -1) {
0153                 timeline.activeTrack = -2
0154                 return
0155             }
0156             newTrack = max - 1;
0157         } else if (newTrack >= max) {
0158             if (showSubtitles) {
0159                 timeline.activeTrack = -2
0160                 return
0161             }
0162             newTrack = 0;
0163         }
0164         timeline.activeTrack = tracksRepeater.itemAt(newTrack).trackInternalId
0165     }
0166 
0167     function zoomByWheel(wheel) {
0168         if (wheel.modifiers & Qt.AltModifier) {
0169             // Seek to next snap
0170             if (wheel.angleDelta.x > 0) {
0171                 timeline.triggerAction('monitor_seek_snap_backward')
0172             } else {
0173                 timeline.triggerAction('monitor_seek_snap_forward')
0174             }
0175         } else if (wheel.modifiers & Qt.ControlModifier) {
0176             root.wheelAccumulatedDelta += wheel.angleDelta.y;
0177             // Zoom
0178             if (root.wheelAccumulatedDelta >= defaultDeltasPerStep) {
0179                 root.zoomIn(true);
0180                 root.wheelAccumulatedDelta = 0;
0181             } else if (root.wheelAccumulatedDelta <= -defaultDeltasPerStep) {
0182                 root.zoomOut(true);
0183                 root.wheelAccumulatedDelta = 0;
0184             }
0185         } else if (wheel.modifiers & Qt.ShiftModifier) {
0186             if (scrollVertically || rubberSelect.visible) {
0187                 horizontalScroll(wheel)
0188             } else {
0189                 verticalScroll(wheel)
0190             }
0191         } else {
0192             if (scrollVertically) {
0193                 verticalScroll(wheel)
0194             } else {
0195                 horizontalScroll(wheel)
0196             }
0197         }
0198         wheel.accepted = true
0199     }
0200 
0201     function horizontalScroll(wheel) {
0202         var initialX = scrollView.contentX
0203         if (wheel.angleDelta.y < 0) {
0204             scrollView.contentX = Math.max(0, Math.min(scrollView.contentX - wheel.angleDelta.y, timeline.fullDuration * root.timeScale - scrollView.width))
0205         } else {
0206             scrollView.contentX = Math.max(scrollView.contentX - wheel.angleDelta.y, 0)
0207         }
0208         if (dragProxyArea.pressed && dragProxy.draggedItem > -1) {
0209             dragProxy.x += scrollView.contentX - initialX
0210             dragProxyArea.moveItem()
0211         } else if (rubberSelect.visible) {
0212             var newX = tracksArea.mouseX + scrollView.contentX
0213             if (newX < rubberSelect.originX) {
0214                 rubberSelect.x = newX
0215                 rubberSelect.width = rubberSelect.originX - newX
0216             } else {
0217                 rubberSelect.x = rubberSelect.originX
0218                 rubberSelect.width = newX - rubberSelect.originX
0219             }
0220         }
0221         root.mousePosChanged(getMousePos())
0222     }
0223 
0224     function verticalScroll(wheel) {
0225         if (wheel.angleDelta.y < 0) {
0226             scrollView.contentY = Math.max(0, Math.min(scrollView.contentY - wheel.angleDelta.y, trackHeaders.height + subtitleTrackHeader.height - tracksArea.height + horZoomBar.height + ruler.height))
0227         } else {
0228             scrollView.contentY = Math.max(scrollView.contentY - wheel.angleDelta.y, 0)
0229         }
0230     }
0231 
0232     function continuousScrolling(x, y) {
0233         // This provides continuous scrolling at the left/right edges.
0234         var maxScroll = trackHeaders.height - tracksArea.height + horZoomBar.height + ruler.height + subtitleTrack.height
0235         y = Math.min(y, maxScroll)
0236         y += ruler.height + subtitleTrack.height
0237         if (x > scrollView.contentX + scrollView.width - root.baseUnit * 3) {
0238             scrollTimer.horizontal = root.baseUnit
0239             scrollTimer.start()
0240         } else if (x < 50) {
0241             scrollView.contentX = 0;
0242             scrollTimer.horizontal = 0
0243             scrollTimer.stop()
0244         } else if (x < scrollView.contentX + root.baseUnit * 3) {
0245             scrollTimer.horizontal = -root.baseUnit
0246             scrollTimer.start()
0247         } else {
0248             if (y > scrollView.contentY + scrollView.height + ruler.height - root.baseUnit) {
0249                 scrollTimer.vertical = root.baseUnit
0250                 scrollTimer.horizontal = 0
0251                 scrollTimer.start()
0252             } else if (scrollView.contentY > 0 && (y - (scrollView.contentY + ruler.height ) < root.baseUnit)) {
0253                 scrollTimer.vertical = -root.baseUnit
0254                 scrollTimer.horizontal = 0
0255                 scrollTimer.start()
0256             } else {
0257                 scrollTimer.vertical = 0
0258                 scrollTimer.horizontal = 0
0259                 scrollTimer.stop()
0260             }
0261         }
0262     }
0263 
0264     function getMousePos() {
0265         if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
0266             return (dragProxy.masterObject.x + dragProxy.masterObject.mouseXPos) / root.timeScale
0267         }
0268         if (tracksArea.containsMouse) {
0269             if (subtitleMouseArea.containsMouse) {
0270                 return (subtitleMouseArea.mouseX) / root.timeScale
0271             } else {
0272                 return (scrollView.contentX + tracksArea.mouseX) / root.timeScale
0273             }
0274         } else {
0275             return -1;
0276         }
0277     }
0278     function getMouseX() {
0279         if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
0280             return (dragProxy.masterObject.x + dragProxy.masterObject.mouseXPos) - scrollView.contentX
0281         }
0282         if (tracksArea.containsMouse) {
0283             return tracksArea.mouseX
0284         } else {
0285             return -1;
0286         }
0287     }
0288 
0289     function getScrollPos() {
0290         return scrollView.contentX
0291     }
0292 
0293     function setScrollPos(pos) {
0294         return scrollView.contentX = pos
0295     }
0296 
0297     function getCopiedItemId() {
0298         return copiedClip
0299     }
0300 
0301     function getMouseTrack() {
0302         if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
0303             return dragProxy.masterObject.trackId
0304         }
0305         return Logic.getTrackIdFromPos(tracksArea.mouseY - ruler.height + scrollView.contentY - subtitleTrack.height)
0306     }
0307 
0308     function getTrackColor(audio, header) {
0309         var col = activePalette.alternateBase
0310         if (audio) {
0311             col = Qt.tint(col, "#06FF00CC")
0312         }
0313         if (header) {
0314             col = Qt.darker(col, 1.05)
0315         }
0316         return col
0317     }
0318 
0319     function centerViewOnCursor() {
0320         scrollView.contentX = Math.max(0, root.consumerPosition * root.timeScale - (scrollView.width / 2))
0321     }
0322 
0323     function clearDropData() {
0324         clipBeingDroppedId = -1
0325         droppedPosition = -1
0326         droppedTrack = -1
0327         clipDropArea.lastDragUuid = ""
0328         scrollTimer.running = false
0329         scrollTimer.stop()
0330         sameTrackIndicator.visible = false
0331     }
0332 
0333     function isDragging() {
0334         return dragInProgress
0335     }
0336 
0337     function initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) {
0338         dragProxy.x = itemObject.modelStart * timeScale
0339         dragProxy.y = itemCoord.y
0340         dragProxy.width = itemObject.clipDuration * timeScale
0341         dragProxy.height = itemCoord.height
0342         dragProxy.masterObject = itemObject
0343         dragProxy.draggedItem = itemId
0344         dragProxy.sourceTrack = itemTrack
0345         dragProxy.sourceFrame = itemPos
0346         dragProxy.isComposition = isComposition
0347         dragProxy.verticalOffset = isComposition ? itemObject.displayHeight : 0
0348     }
0349 
0350     function endDrag() {
0351         dragProxy.draggedItem = -1
0352         dragProxy.x = 0
0353         dragProxy.y = 0
0354         dragProxy.width = 0
0355         dragProxy.height = 0
0356         dragProxy.verticalOffset = 0
0357         doubleClickTimer.stop()
0358         root.blockAutoScroll = false
0359     }
0360 
0361     function regainFocus(mousePos) {
0362         var currentMouseTrack = Logic.getTrackIdFromPos(mousePos.y - ruler.height - subtitleTrack.height + scrollView.contentY)
0363         // Try to find correct item
0364         var sourceTrack = Logic.getTrackById(currentMouseTrack)
0365         var mouseYPos = (mousePos.y - ruler.height + scrollView.contentY) - sourceTrack.y
0366         var allowComposition = mouseYPos > sourceTrack.height / 2
0367         var tentativeClip = undefined
0368         root.mousePosChanged(Math.max(0, Math.floor((mousePos.x - trackHeaders.width + scrollView.contentX) / root.timeScale)))
0369         if (allowComposition) {
0370             tentativeClip = getItemAtPos(currentMouseTrack, (mousePos.x - trackHeaders.width + scrollView.contentX), true)
0371             if (tentativeClip) {
0372                 // Ensure mouse is really over the composition
0373                 if (!tentativeClip.doesContainMouse(root.mapToItem(tentativeClip, mousePos.x, mousePos.y))) {
0374                     tentativeClip = undefined
0375                 }
0376             }
0377         }
0378         if (!tentativeClip) {
0379             tentativeClip = getItemAtPos(currentMouseTrack, (mousePos.x - trackHeaders.width + scrollView.contentX), false)
0380         }
0381 
0382         if (tentativeClip && tentativeClip.clipId && tentativeClip.doesContainMouse(root.mapToItem(tentativeClip, mousePos.x, mousePos.y)) && root.activeTool !== ProjectTool.SpacerTool) {
0383             dragProxy.draggedItem = tentativeClip.clipId
0384             var tk = controller.getItemTrackId(tentativeClip.clipId)
0385             dragProxy.x = tentativeClip.x
0386             dragProxy.y = sourceTrack.y + (tentativeClip.isComposition ? tentativeClip.displayHeight : tentativeClip.y)
0387             //+ Logic.getTrackYFromId(tk)
0388             dragProxy.width = tentativeClip.width
0389             dragProxy.height = tentativeClip.itemHeight()
0390             dragProxy.masterObject = tentativeClip
0391             dragProxy.sourceTrack = tk
0392             dragProxy.sourceFrame = tentativeClip.modelStart
0393             dragProxy.isComposition = tentativeClip.isComposition
0394             dragProxy.verticalOffset = tentativeClip.isComposition ? tentativeClip.displayHeight : 0
0395             //console.log('missing item', tentativeClip.clipId, ', COORDS: ', dragProxy.x, 'x', dragProxy.y,'-',dragProxy.width,'x',dragProxy.height, ', TK id: ', tk, ', TKY: ', Logic.getTrackYFromId(tk),' STARTFRM: ', dragProxy.sourceFrame)
0396         } else {
0397             console.log('item not found')
0398             if (dragProxy.draggedItem > -1) {
0399                 endDrag()
0400             }
0401         }
0402         root.blockAutoScroll = false
0403     }
0404 
0405     function getAudioTracksCount(){
0406         var audioCount = 0;
0407         for (var i = 0; i < trackHeaderRepeater.count; i++) {
0408             if(trackHeaderRepeater.itemAt(i).isAudio) {
0409                 audioCount++;
0410             }
0411         }
0412         return audioCount;
0413     }
0414 
0415     function animateLockButton(trackId){
0416         // TODO: fix then multiple subtitles track is implemented
0417         if (trackId == -2) {
0418             flashLock.restart()
0419         } else {
0420             Logic.getTrackHeaderById(trackId).animateLock()
0421         }
0422     }
0423 
0424     function getItemAtPos(tk, posx, compositionWanted) {
0425         var track = Logic.getTrackById(tk)
0426         if (track == undefined || track.children == undefined) {
0427             return undefined
0428         }
0429         var container = track.children[0]
0430         var tentativeClip = undefined
0431         for (var i = 0 ; i < container.children.length; i++) {
0432             if (container.children[i].children.length === 0 || container.children[i].children[0].children.length === 0) {
0433                 continue
0434             }
0435             tentativeClip = container.children[i].children[0].childAt(posx, compositionWanted ? 5 : 0)
0436             if (tentativeClip && tentativeClip.clipId && (tentativeClip.isComposition === compositionWanted)) {
0437                 break
0438             }
0439         }
0440         return tentativeClip
0441     }
0442     Keys.onDownPressed: {
0443         root.moveSelectedTrack(1)
0444     }
0445     Keys.onUpPressed: {
0446         root.moveSelectedTrack(-1)
0447     }
0448     Keys.onShortcutOverride: event => {event.accepted = focus && event.key === Qt.Key_F2}
0449     Keys.onPressed: event => {
0450         if (event.key == Qt.Key_F2) {
0451             Logic.getTrackHeaderById(timeline.activeTrack).editName()
0452             event.accepted = true;
0453         }
0454     }
0455 
0456     property int activeTool: ProjectTool.SelectTool
0457     property int baseUnit: Math.max(12, fontMetrics.font.pixelSize)
0458     property int minClipWidthForViews: 1.5 * baseUnit
0459     property real fontUnit: fontMetrics.font.pointSize
0460     property int collapsedHeight: Math.max(28, baseUnit * 1.8)
0461     property int minHeaderWidth: 6 * collapsedHeight
0462     property int headerWidth: Math.max(minHeaderWidth, timeline.headerWidth())
0463     property bool autoTrackHeight: timeline.autotrackHeight
0464     property color selectedTrackColor: Qt.rgba(activePalette.highlight.r, activePalette.highlight.g, activePalette.highlight.b, 0.2)
0465     property color frameColor: Qt.rgba(activePalette.shadow.r, activePalette.shadow.g, activePalette.shadow.b, 0.5)
0466     property bool autoScrolling: timeline.autoScroll
0467     property bool blockAutoScroll: false
0468     property int duration: timeline.duration
0469     property color audioColor: timeline.audioColor
0470     property color videoColor: timeline.videoColor
0471     property color titleColor: timeline.titleColor
0472     property color imageColor: timeline.imageColor
0473     property color slideshowColor: timeline.slideshowColor
0474     property color lockedColor: timeline.lockedColor
0475     property color selectionColor: timeline.selectionColor
0476     property color groupColor: timeline.groupColor
0477     property color thumbColor1: timeline.thumbColor1
0478     property color thumbColor2: timeline.thumbColor2
0479     property int doubleClickInterval: timeline.doubleClickInterval()
0480     property int mainItemId: -1
0481     property int clickFrame: -1
0482     property int clipBeingDroppedId: -1
0483     property string clipBeingDroppedData
0484     property int droppedPosition: -1
0485     property int droppedTrack: -1
0486     property int clipBeingMovedId: -1
0487     property int consumerPosition: proxy.position
0488     property int spacerGroup: -1
0489     property int spacerTrack: -1
0490     property int spacerFrame: -1
0491     property int finalSpacerFrame: -1
0492     property int spacerClickFrame: -1
0493     property bool spacerGuides: false
0494     property real timeScale: timeline.scaleFactor
0495     property int snapping: (timeline.snap && (root.timeScale < 2 * baseUnit)) ? Math.floor(baseUnit / (root.timeScale > 3 ? root.timeScale / 2 : root.timeScale)) : -1
0496     property var timelineSelection: timeline.selection
0497     property int selectedMix: timeline.selectedMix
0498     property var selectedGuides: []
0499     property int trackHeight
0500     property int copiedClip: -1
0501     property int zoomOnMouse: -1
0502     property bool zoomOnBar: false // Whether the scaling was done with the zoombar
0503     property string addedSequenceName : controller.visibleSequenceName
0504     property int viewActiveTrack: timeline.activeTrack
0505     property int wheelAccumulatedDelta: 0
0506     readonly property int defaultDeltasPerStep: 120
0507     property bool seekingFinished : proxy.seekFinished
0508     property int scrollMin: scrollView.contentX / root.timeScale
0509     property int scrollMax: scrollMin + scrollView.contentItem.width / root.timeScale
0510     property double dar: 16/9
0511     property bool paletteUnchanged: true
0512     property int maxLabelWidth: 20 * root.baseUnit * Math.sqrt(root.timeScale)
0513     property bool showSubtitles: false
0514     property bool subtitlesWarning: timeline.subtitlesWarning
0515     property bool subtitlesLocked: timeline.subtitlesLocked
0516     property bool subtitlesDisabled: timeline.subtitlesDisabled
0517     property int trackTagWidth: fontMetrics.boundingRect("M").width * ((getAudioTracksCount() > 9) || (trackHeaderRepeater.count - getAudioTracksCount() > 9)  ? 3 : 2)
0518     property bool scrollVertically: timeline.scrollVertically
0519     property int spacerMinPos: 0
0520 
0521     onAutoTrackHeightChanged: {
0522         trackHeightTimer.stop()
0523         if (root.autoTrackHeight) {
0524             timeline.autofitTrackHeight(scrollView.height - subtitleTrack.height, root.collapsedHeight)
0525         }
0526     }
0527 
0528     onSeekingFinishedChanged : {
0529         playhead.opacity = seekingFinished ? 1 : 0.5
0530     }
0531 
0532     onShowSubtitlesChanged: {
0533         subtitleTrack.height = showSubtitles? root.baseUnit * 5 : 0
0534         if (root.autoTrackHeight) {
0535             timeline.autofitTrackHeight(scrollView.height - subtitleTrack.height, root.collapsedHeight)
0536         }
0537     }
0538     Timer {
0539         id: trackHeightTimer
0540         interval: 300; running: false; repeat: false
0541         onTriggered: timeline.autofitTrackHeight(scrollView.height - subtitleTrack.height, root.collapsedHeight)
0542     }
0543 
0544     onHeightChanged: {
0545         if (root.autoTrackHeight) {
0546             trackHeightTimer.restart()
0547         }
0548     }
0549 
0550     //onCurrentTrackChanged: timeline.selection = []
0551 
0552     onTimeScaleChanged: {
0553         if (timeline.fullDuration * root.timeScale < scrollView.width) {
0554             scrollView.contentX = 0
0555             root.zoomOnMouse = -1
0556         } else if (root.zoomOnMouse >= 0) {
0557             scrollView.contentX = Math.max(0, root.zoomOnMouse * root.timeScale - getMouseX())
0558             root.zoomOnMouse = -1
0559         } else if (root.zoomOnBar) {
0560             root.zoomOnBar = false
0561         } else {
0562             scrollView.contentX = Math.max(0, root.consumerPosition * root.timeScale - (scrollView.width / 2))
0563         }
0564         ruler.adjustStepSize()
0565         if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
0566             // update dragged item pos
0567             dragProxy.masterObject.updateDrag()
0568         }
0569         root.mousePosChanged(getMousePos())
0570     }
0571 
0572     onConsumerPositionChanged: {
0573         if (root.autoScrolling && !root.blockAutoScroll) Logic.scrollIfNeeded()
0574     }
0575 
0576     onViewActiveTrackChanged: {
0577         if (controller.isSubtitleTrack(timeline.activeTrack)) {
0578             // subtitle track
0579             scrollView.contentY = 0
0580             return
0581         }
0582         var tk = Logic.getTrackById(timeline.activeTrack)
0583         if (tk.y + subtitleTrack.height < scrollView.contentY) {
0584             scrollView.contentY = Math.max(0, tk.y + subtitleTrack.height)
0585         } else if (tk.y + tk.height + subtitleTrack.height > scrollView.contentY + scrollView.height) {
0586             var newY = Math.min(trackHeaders.height + subtitleTrack.height - scrollView.height, tk.y + tk.height - scrollView.height + subtitleTrack.height)
0587             if (newY >= 0) {
0588                 scrollView.contentY = newY
0589             }
0590         }
0591     }
0592 
0593     onActiveToolChanged: {
0594         if (root.activeTool === ProjectTool.SpacerTool) {
0595             // Spacer activated
0596             endDrag()
0597         } else if (root.activeTool === ProjectTool.SelectTool) {
0598             var tk = getMouseTrack()
0599             if (tk < 0) {
0600                 return
0601             }
0602             var pos = getMousePos() * root.timeScale
0603             var sourceTrack = Logic.getTrackById(tk)
0604             var allowComposition = tracksArea.mouseY- sourceTrack.y > sourceTrack.height / 2
0605             var tentativeItem = undefined
0606             if (allowComposition) {
0607                 tentativeItem = getItemAtPos(tk, pos, true)
0608             }
0609             if (!tentativeItem) {
0610                 tentativeItem = getItemAtPos(tk, pos, false)
0611             }
0612             if (tentativeItem) {
0613                 tentativeItem.updateDrag()
0614             }
0615         }
0616     }
0617 
0618     DropArea { //Drop area for compositions
0619         id: compoArea
0620         width: root.width - headerWidth
0621         height: root.height - ruler.height
0622         y: ruler.height
0623         x: headerWidth
0624         property bool isAudioDrag
0625         property int sameCutPos: -1
0626         keys: 'kdenlive/composition'
0627         function moveDrop(offset, voffset)
0628         {
0629             if (clipBeingDroppedId >= 0) {
0630                 var track = Logic.getTrackIdFromPos(drag.y + voffset + scrollView.contentY - subtitleTrack.height)
0631                 if (track !== -1) {
0632                     var frame = Math.floor((drag.x + scrollView.contentX + offset) / root.timeScale)
0633                     if (controller.isAudioTrack(track) != isAudioDrag) {
0634                         // Don't allow moving composition to an audio track
0635                         track = controller.getCompositionTrackId(clipBeingDroppedId)
0636                     }
0637                     var moveData = controller.suggestCompositionMove(clipBeingDroppedId, track, frame, root.consumerPosition, root.snapping)
0638                     var currentFrame = moveData[0]
0639                     var currentTrack = moveData[1]
0640                     sameCutPos = timeline.isOnCut(clipBeingDroppedId)
0641                     if (sameCutPos > -1) {
0642                         var sourceTrack = Logic.getTrackById(currentTrack)
0643                         if (drag.y < sourceTrack.y + sourceTrack.height / 2 || isAudioDrag) {
0644                             sameTrackIndicator.x = sameCutPos * root.timeScale - sameTrackIndicator.width / 2
0645                             sameTrackIndicator.y = sourceTrack.y
0646                             sameTrackIndicator.height = sourceTrack.height
0647                             sameTrackIndicator.visible = true
0648                         } else {
0649                             sameTrackIndicator.visible = false
0650                         }
0651                     } else {
0652                         sameTrackIndicator.visible = false
0653                     }
0654                     continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0655                 }
0656                 if (offset != 0) {
0657                     root.mousePosChanged(Math.max(0, Math.floor((drag.x + scrollView.contentX) / root.timeScale)))
0658                 }
0659             }
0660         }
0661         onEntered: drag => {
0662             if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
0663                 var yOffset = 0
0664                 if (root.showSubtitles) {
0665                     yOffset = subtitleTrack.height
0666                 }
0667                 var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY - yOffset)
0668                 var frame = Math.round((drag.x + scrollView.contentX) / root.timeScale)
0669                 droppedPosition = frame
0670                 isAudioDrag = drag.getDataAsString('type') == "audio"
0671                 if (track >= 0 && controller.isAudioTrack(track) == isAudioDrag) {
0672                     clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
0673                     clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData, false)
0674                     continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0675                 }
0676                 drag.acceptProposedAction()
0677             }
0678         }
0679         onPositionChanged: drag => {
0680             if (clipBeingMovedId == -1) {
0681                 if (clipBeingDroppedId >= 0) {
0682                     moveDrop(0, 0)
0683                 } else {
0684                     var yOffset = 0
0685                     if (root.showSubtitles) {
0686                         yOffset = subtitleTrack.height
0687                     }
0688                     var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY - yOffset)
0689                     if (track !== -1 && controller.isAudioTrack(track) == isAudioDrag) {
0690                         var frame = Math.floor((drag.x + scrollView.contentX) / root.timeScale)
0691                         frame = controller.suggestSnapPoint(frame, root.snapping)
0692                         clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
0693                         clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData , false)
0694                         continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0695                     } else {
0696                         drag.accepted = false
0697                     }
0698                 }
0699             }
0700             root.mousePosChanged(Math.max(0, Math.floor((drag.x + scrollView.contentX) / root.timeScale)))
0701         }
0702         onExited:{
0703             if (clipBeingDroppedId != -1) {
0704                 // If we exit, remove composition
0705                 controller.requestItemDeletion(clipBeingDroppedId, false)
0706                 clearDropData()
0707             }
0708         }
0709         onDropped: drag => {
0710             if (clipBeingDroppedId != -1) {
0711                 var frame = controller.getCompositionPosition(clipBeingDroppedId)
0712                 var track = controller.getCompositionTrackId(clipBeingDroppedId)
0713                 // we simulate insertion at the final position so that stored undo has correct value
0714                 controller.requestItemDeletion(clipBeingDroppedId, false)
0715                 if (sameTrackIndicator.visible) {
0716                     // We want a same track composition
0717                     timeline.insertNewMix(track, sameCutPos, clipBeingDroppedData)
0718                 } else if (!isAudioDrag) {
0719                     timeline.insertNewCompositionAtPos(track, frame, clipBeingDroppedData)
0720                 } else {
0721                     // Cannot insert an audio mix composition
0722                 }
0723             }
0724             clearDropData()
0725             regainFocus(clipDropArea.mapToItem(root, drag.x, drag.y))
0726         }
0727     }
0728     DropArea {
0729         //Drop area for bin/clips
0730         id: clipDropArea
0731         property string lastDragUuid
0732         property var lastDragPos
0733         /** @brief local helper function to handle the insertion of multiple dragged items */
0734         function insertAndMaybeGroup(track, frame, droppedData) {
0735             var binIds = droppedData.split(";")
0736             if (binIds.length === 0) {
0737                 return -1
0738             }
0739 
0740             var id = -1
0741             if (binIds.length === 1) {
0742                 id = timeline.insertClip(timeline.activeTrack, frame, clipBeingDroppedData, false, true, false)
0743             } else {
0744                 var ids = timeline.insertClips(timeline.activeTrack, frame, binIds, false, true, false)
0745 
0746                 // if the clip insertion succeeded, request the clips to be grouped
0747                 if (ids.length > 0) {
0748                     timeline.selectItems(ids)
0749                     id = ids[0]
0750                 }
0751             }
0752             return id
0753         }
0754 
0755         property int fakeFrame: -1
0756         property int fakeTrack: -1
0757         width: root.width - headerWidth
0758         height: root.height - ruler.height
0759         y: ruler.height
0760         x: headerWidth
0761         keys: 'text/producerslist'
0762         enabled: !compoArea.containsDrag
0763         function moveDrop(offset, voffset)
0764         {
0765             if (clipBeingDroppedId > -1) {
0766                 var yOffset = 0
0767                 if (root.showSubtitles) {
0768                     yOffset = subtitleTrack.height
0769                 }
0770                 var track = Logic.getTrackIndexFromPos(drag.y + voffset + scrollView.contentY - yOffset)
0771                 if (track >= 0  && track < tracksRepeater.count) {
0772                     var targetTrack = tracksRepeater.itemAt(track).trackInternalId
0773                     var frame = Math.floor((drag.x + scrollView.contentX + offset) / root.timeScale)
0774                     var moveData = controller.suggestClipMove(clipBeingDroppedId, targetTrack, frame, root.consumerPosition, root.snapping)
0775                     fakeFrame = moveData[0]
0776                     fakeTrack = moveData[1]
0777                     timeline.activeTrack = fakeTrack
0778                     //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
0779                     continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0780                 }
0781                 if (offset != 0) {
0782                     root.mousePosChanged(Math.max(0, Math.floor((drag.x + scrollView.contentX) / root.timeScale)))
0783                 }
0784             }
0785         }
0786         function processDrop()
0787         {
0788             // Process the drop event, useful if drop event happens outside of drop area
0789             if (clipBeingDroppedId != -1) {
0790                 var frame = controller.getClipPosition(clipBeingDroppedId)
0791                 var track = controller.getClipTrackId(clipBeingDroppedId)
0792                 if (!controller.normalEdit()) {
0793                     frame = fakeFrame
0794                     track = fakeTrack
0795                 }
0796                 /* We simulate insertion at the final position so that stored undo has correct value
0797                  * NOTE: even if dropping multiple clips, requesting the deletion of the first one is
0798                  * enough as internally it will request the group deletion
0799                  */
0800                 controller.requestItemDeletion(clipBeingDroppedId, false)
0801 
0802                 var binIds = clipBeingDroppedData.split(";")
0803                 if (binIds.length == 1) {
0804                     if (controller.normalEdit()) {
0805                         timeline.insertClip(track, frame, clipBeingDroppedData, true, true, false)
0806                     } else {
0807                         timeline.insertClipZone(clipBeingDroppedData, track, frame)
0808                     }
0809                 } else {
0810                     if (controller.normalEdit()) {
0811                         timeline.insertClips(track, frame, binIds, true, true)
0812                     } else {
0813                         // TODO
0814                         console.log('multiple clips insert/overwrite not supported yet')
0815                     }
0816                 }
0817                 fakeTrack = -1
0818                 fakeFrame = -1
0819                 clearDropData()
0820                 if (clipDropArea.containsDrag) {
0821                     regainFocus(clipDropArea.mapToItem(root, drag.x, drag.y))
0822                 }
0823             }
0824         }
0825         onEntered: drag => {
0826             if (clipBeingDroppedId > -1 && lastDragUuid != drag.getDataAsString('text/dragid') && timeline.exists(clipBeingDroppedId)) {
0827                 // We are re-entering drop zone with another drag operation, ensure the previous drop operation is complete
0828                 processDrop()
0829             }
0830             lastDragPos = Qt.point(drag.x, drag.y)
0831             if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
0832                 var yOffset = 0
0833                 if (root.showSubtitles) {
0834                     yOffset = subtitleTrack.height
0835                 }
0836                 var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
0837                 if (track >= 0  && track < tracksRepeater.count) {
0838                     var frame = Math.round((drag.x + scrollView.contentX) / root.timeScale)
0839                     droppedPosition = frame
0840                     timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
0841                     clipBeingDroppedData = drag.getDataAsString('text/producerslist')
0842                     lastDragUuid = drag.getDataAsString('text/dragid')
0843                     if (controller.normalEdit()) {
0844                         clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, frame, clipBeingDroppedData)
0845                     } else {
0846                         // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
0847                         frame = controller.adjustFrame(frame, timeline.activeTrack)
0848                         clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, frame, clipBeingDroppedData)
0849                         if (clipBeingDroppedId > -1) {
0850                             var moveData = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, root.snapping)
0851                             fakeFrame = moveData[0]
0852                             fakeTrack = moveData[1]
0853                         }
0854                     }
0855                     continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0856                 }
0857                 drag.acceptProposedAction()
0858             }
0859         }
0860         onExited: {
0861             if (clipBeingDroppedId != -1 && (lastDragPos.y < lastDragPos.x || (clipDropArea.height - lastDragPos.y < lastDragPos.x))) {
0862                 // If we exit on top or bottom, remove clip
0863                 controller.requestItemDeletion(clipBeingDroppedId, false)
0864                 clearDropData()
0865             } else if (clipBeingDroppedId > -1 && fakeTrack > -1) {
0866                 // Clip is dropped
0867                 var moveData = controller.suggestClipMove(clipBeingDroppedId, fakeTrack, 0, root.consumerPosition, root.snapping)
0868                 fakeFrame = moveData[0]
0869                 fakeTrack = moveData[1]
0870                 timeline.activeTrack = fakeTrack
0871             }
0872         }
0873         onPositionChanged: drag => {
0874             lastDragPos = Qt.point(drag.x, drag.y)
0875             if (clipBeingMovedId == -1) {
0876                 if (clipBeingDroppedId > -1) {
0877                     moveDrop(0, 0)
0878                 } else {
0879                     var yOffset = 0
0880                     if (root.showSubtitles) {
0881                         yOffset = subtitleTrack.height
0882                     }
0883                     var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
0884                     if (track >= 0  && track < tracksRepeater.count) {
0885                         var targetTrack = tracksRepeater.itemAt(track).trackInternalId
0886                         var frame = Math.floor((drag.x + scrollView.contentX) / root.timeScale)
0887                         frame = controller.suggestSnapPoint(frame, root.snapping)
0888                         if (controller.normalEdit()) {
0889                             timeline.activeTrack = targetTrack
0890                             clipBeingDroppedId = insertAndMaybeGroup(targetTrack, frame, drag.getDataAsString('text/producerslist'), false, true)
0891                         } else {
0892                             // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
0893                             clipBeingDroppedId = insertAndMaybeGroup(targetTrack, timeline.fullDuration, clipBeingDroppedData)
0894                             if (clipBeingDroppedId > -1) {
0895                                 var moveData = controller.suggestClipMove(clipBeingDroppedId, targetTrack, frame, root.consumerPosition, root.snapping)
0896                                 fakeFrame = moveData[0]
0897                                 fakeTrack = moveData[1]
0898                                 timeline.activeTrack = fakeTrack
0899                             }
0900                         }
0901                         continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0902                     } else {
0903                         drag.accepted = false
0904                     }
0905                 }
0906             }
0907             root.mousePosChanged(Math.max(0, Math.floor((drag.x + scrollView.contentX) / root.timeScale)))
0908         }
0909         onDropped: {
0910             processDrop()
0911         }
0912     }
0913     DropArea { //Drop area for urls (direct drop from file manager)
0914         /** @brief local helper function to handle the insertion of multiple dragged items */
0915         property int fakeFrame: -1
0916         property int fakeTrack: -1
0917         property var droppedUrls: []
0918         enabled: !clipDropArea.containsDrag && !compoArea.containsDrag
0919         width: root.width - headerWidth
0920         height: root.height - ruler.height
0921         y: ruler.height
0922         x: headerWidth
0923         keys: 'text/uri-list'
0924         onEntered: drag => {
0925             drag.accepted = true
0926             droppedUrls.length = 0
0927             for(var i in drag.urls){
0928                 var url = drag.urls[i]
0929                 droppedUrls.push(Qt.resolvedUrl(url))
0930             }
0931         }
0932         onExited:{
0933             if (clipBeingDroppedId != -1) {
0934                 controller.requestItemDeletion(clipBeingDroppedId, false)
0935             }
0936             clearDropData()
0937         }
0938         onPositionChanged: drag => {
0939             if (clipBeingMovedId == -1) {
0940                 var yOffset = 0
0941                 if (root.showSubtitles) {
0942                     yOffset = subtitleTrack.height
0943                 }
0944                 var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
0945                 if (track >= 0  && track < tracksRepeater.count) {
0946                     timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
0947                     continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
0948                     if (clipBeingDroppedId == -1) {
0949                         if (controller.normalEdit() == false) {
0950                             // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
0951                             //clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, timeline.fullDuration, clipBeingDroppedData)
0952                             //fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, Math.floor(root.snapping))
0953                             fakeTrack = timeline.activeTrack
0954                         }
0955                     }
0956                 }
0957             }
0958             root.mousePosChanged(Math.max(0, Math.floor((drag.x + scrollView.contentX) / root.timeScale)))
0959         }
0960         onDropped: drag => {
0961             var frame = Math.floor((drag.x + scrollView.contentX) / root.timeScale)
0962             var track = timeline.activeTrack
0963             //var binIds = clipBeingDroppedData.split(";")
0964             //if (binIds.length == 1) {
0965                 if (controller.normalEdit()) {
0966                     timeline.urlDropped(droppedUrls, frame, track)
0967                 } else {
0968                     //timeline.insertClipZone(clipBeingDroppedData, track, frame)
0969                 }
0970             /*} else {
0971                 if (controller.normalEdit()) {
0972                     timeline.insertClips(track, frame, binIds, true, true)
0973                 } else {
0974                     // TODO
0975                     console.log('multiple clips insert/overwrite not supported yet')
0976                 }
0977             }*/
0978             clearDropData()
0979         }
0980     }
0981 
0982     Row {
0983         Column {
0984             id: headerContainer
0985             width: headerWidth
0986             z: 1
0987             Item {
0988                 // Padding between toolbar and track headers.
0989                 width: parent.width
0990                 height: ruler.height
0991                 Button {
0992                     text: metrics.elidedText
0993                     font: miniFont
0994                     flat: true
0995                     anchors.fill: parent
0996                     anchors.leftMargin: 2
0997                     anchors.rightMargin: 2
0998                     ToolTip.delay: 1000
0999                     ToolTip.timeout: 5000
1000                     ToolTip.visible: hovered
1001                     ToolTip.text: i18n("Show master effects")
1002                     TextMetrics {
1003                         id: metrics
1004                         font: miniFont
1005                         elide: Text.ElideRight
1006                         elideWidth: root.headerWidth * 0.8
1007                         text: root.addedSequenceName.length == 0 ? i18n("Master") : root.addedSequenceName
1008                     }
1009                     onClicked: {
1010                         timeline.showMasterEffects()
1011                     }
1012                     DropArea { //Drop area for tracks
1013                         anchors.fill: parent
1014                         keys: 'kdenlive/effect'
1015                         property string dropData
1016                         property string dropSource
1017                         property int dropRow: -1
1018                         onEntered: drag => {
1019                             dropData = drag.getDataAsString('kdenlive/effect')
1020                             dropSource = drag.getDataAsString('kdenlive/effectsource')
1021                         }
1022                         onDropped: drag => {
1023                             console.log("Add effect: ", dropData)
1024                             if (dropSource == '') {
1025                                 // drop from effects list
1026                                 controller.addTrackEffect(-1, dropData);
1027                             } else {
1028                                 controller.copyTrackEffect(-1, dropSource);
1029                             }
1030                             dropSource = ''
1031                             dropRow = -1
1032                             drag.acceptProposedAction
1033                         }
1034                     }
1035                 }
1036             }
1037             Flickable {
1038                 // Non-slider scroll area for the track headers.
1039                 id: headerFlick
1040                 contentY: scrollView.contentY
1041                 width: parent.width
1042                 y: ruler.height
1043                 height: root.height - ruler.height
1044                 interactive: false
1045                 clip: true
1046 
1047                 MouseArea {
1048                     width: trackHeaders.width
1049                     height: trackHeaders.height + subtitleTrackHeader.height
1050                     acceptedButtons: Qt.NoButton
1051                     onWheel: wheel => {
1052                         verticalScroll(wheel)
1053                         wheel.accepted = true
1054                     }
1055                 }
1056                 Rectangle {
1057                     id: subtitleTrackHeader
1058                     width: trackHeaders.width
1059                     height: subtitleTrack.height
1060                     property bool collapsed: subtitleTrack.height == root.collapsedHeight
1061                     visible: height > 0
1062                     color: (controller && controller.isSubtitleTrack(timeline.activeTrack)) ? Qt.tint(getTrackColor(false, false), selectedTrackColor) : getTrackColor(false, false)
1063                     MouseArea {
1064                         anchors.fill: parent
1065                         onClicked: {
1066                             timeline.activeTrack = -2
1067                         }
1068                     }
1069                     ToolButton {
1070                         id: expandSubButton
1071                         focusPolicy: Qt.NoFocus
1072                         property var modifier: 0
1073                         anchors.left: parent.left
1074                         anchors.leftMargin: 1.5 * root.baseUnit
1075                         width: root.collapsedHeight
1076                         height: root.collapsedHeight
1077                         contentItem: Item {
1078                             Image {
1079                                 source: subtitleTrackHeader.collapsed ? "image://icon/go-next" : "image://icon/go-down"
1080                                 anchors.centerIn: parent
1081                                 width: root.collapsedHeight - 4
1082                                 height: root.collapsedHeight - 4
1083                                 cache: root.paletteUnchanged
1084                             }
1085                         }
1086                         onClicked: {
1087                             if (subtitleTrack.height > root.collapsedHeight) {
1088                                 subtitleTrack.height = root.collapsedHeight
1089                             } else {
1090                                 subtitleTrack.height = 5 * root.baseUnit
1091                             }
1092                         }
1093                     }
1094                     ComboBox {
1095                         id: subLabel
1096                         model: timeline.subtitlesList
1097                         property int subIndex: timeline.activeSubPosition
1098                         onSubIndexChanged: {
1099                             subLabel.currentIndex = subIndex
1100                         }
1101                         anchors.right: parent.right
1102                         anchors.top: expandSubButton.bottom
1103                         flat: true
1104                         onActivated: index => {
1105                             timeline.subtitlesMenuActivatedAsync(index)
1106                         }
1107                     }
1108 
1109                     Row {
1110                         id: subButtonsRow
1111                         width: childrenRect.width
1112                         x: Math.max(2 * root.collapsedHeight + 2, parent.width - width - 4)
1113                         spacing: 0
1114                         ToolButton {
1115                             id: warningButton
1116                             visible: subtitlesWarning
1117                             focusPolicy: Qt.NoFocus
1118                             contentItem: Item {
1119                                 Image {
1120                                     source: "image://icon/data-warning"
1121                                     anchors.centerIn: parent
1122                                     width: root.collapsedHeight - 4
1123                                     height: root.collapsedHeight - 4
1124                                     cache: root.paletteUnchanged
1125                                 }
1126                             }
1127                             width: root.collapsedHeight
1128                             height: root.collapsedHeight
1129                             onClicked: timeline.subtitlesWarningDetails()
1130                             ToolTip {
1131                                 visible: warningButton.hovered
1132                                 font: miniFont
1133                                 delay: 1500
1134                                 timeout: 5000
1135                                 background: Rectangle {
1136                                     color: activePalette.alternateBase
1137                                     border.color: activePalette.light
1138                                 }
1139                                 contentItem: Label {
1140                                     color: activePalette.text
1141                                     text: i18n("Click to see details")
1142                                 }
1143                             }
1144                         }
1145                         ToolButton {
1146                             id: analyseButton
1147                             focusPolicy: Qt.NoFocus
1148                             contentItem: Item {
1149                                 Image {
1150                                     source: "image://icon/autocorrection"
1151                                     anchors.centerIn: parent
1152                                     width: root.collapsedHeight - 4
1153                                     height: root.collapsedHeight - 4
1154                                     cache: root.paletteUnchanged
1155                                 }
1156                             }
1157                             width: root.collapsedHeight
1158                             height: root.collapsedHeight
1159                             onClicked: timeline.triggerAction('audio_recognition')
1160                             ToolTip.visible: hovered
1161                             ToolTip.delay: 1500
1162                             ToolTip.timeout: 5000
1163                             ToolTip.text: i18n("Speech recognition")
1164                         }
1165                         ToolButton {
1166                             id: muteButton
1167                             focusPolicy: Qt.NoFocus
1168                             contentItem: Item {
1169                                 Image {
1170                                     source: root.subtitlesDisabled ? "image://icon/view-hidden" : "image://icon/view-visible"
1171                                     anchors.centerIn: parent
1172                                     width: root.collapsedHeight - 4
1173                                     height: root.collapsedHeight - 4
1174                                     cache: root.paletteUnchanged
1175                                 }
1176                             }
1177                             width: root.collapsedHeight
1178                             height: root.collapsedHeight
1179                             onClicked: timeline.triggerAction('disable_subtitle')
1180                             ToolTip.visible: hovered
1181                             ToolTip.delay: 1500
1182                             ToolTip.timeout: 5000
1183                             ToolTip.text: root.subtitlesDisabled? i18n("Show") : i18n("Hide")
1184                         }
1185 
1186                         ToolButton {
1187                             id: lockButton
1188                             width: root.collapsedHeight
1189                             height: root.collapsedHeight
1190                             focusPolicy: Qt.NoFocus
1191                             contentItem: Rectangle {
1192                                 id: bgRect
1193                                 anchors.fill: parent
1194                                 color: "transparent"
1195                                 Image {
1196                                     source: root.subtitlesLocked ? "image://icon/lock" : "image://icon/unlock"
1197                                     anchors.centerIn: parent
1198                                     width: root.collapsedHeight - 4
1199                                     height: root.collapsedHeight - 4
1200                                     cache: root.paletteUnchanged
1201                                 }
1202                             }
1203                             onClicked: timeline.triggerAction('lock_subtitle')
1204                             ToolTip.visible: hovered
1205                             ToolTip.delay: 1500
1206                             ToolTip.timeout: 5000
1207                             ToolTip.text: root.subtitlesLocked? i18n("Unlock track") : i18n("Lock track")
1208                             SequentialAnimation {
1209                                 id: flashLock
1210                                 loops: 3
1211                                 ParallelAnimation {
1212                                     ScaleAnimator {target: lockButton; from: 1; to: 1.2; duration: 120}
1213                                     PropertyAnimation {target: bgRect;property: "color"; from: "transparent"; to: "darkred"; duration: 100}
1214                                 }
1215                                 ParallelAnimation {
1216                                     ScaleAnimator {target: lockButton; from: 1.6; to: 1; duration: 120}
1217                                     PropertyAnimation {target: bgRect;property: "color"; from: "darkred"; to: "transparent"; duration: 120}
1218                                 }
1219                             }
1220                         }
1221                     }
1222                 }
1223                 Column {
1224                     id: trackHeaders
1225                     y: subtitleTrack.height
1226                     spacing: 0
1227                     Repeater {
1228                         id: trackHeaderRepeater
1229                         model: multitrack
1230                         property int tracksCount: count
1231                         onTracksCountChanged: {
1232                             if (root.autoTrackHeight) {
1233                                 trackHeightTimer.restart()
1234                             }
1235                         }
1236                         TrackHead {
1237                             trackName: model.name
1238                             thumbsFormat: model.thumbsFormat
1239                             trackTag: model.trackTag
1240                             isDisabled: model.disabled
1241                             isComposite: model.composite
1242                             isLocked: model.locked
1243                             isActive: model.trackActive
1244                             isAudio: model.audio
1245                             showAudioRecord: model.audioRecord
1246                             effectNames: model.effectNames
1247                             isStackEnabled: model.isStackEnabled
1248                             width: headerWidth
1249                             current: item === timeline.activeTrack
1250                             trackId: item
1251                             height: model.trackHeight
1252                             onIsLockedChanged: tracksRepeater.itemAt(index).isLocked = isLocked
1253                             collapsed: height <= root.collapsedHeight
1254                             Component.onCompleted: {
1255                                 root.collapsedHeight = collapsedHeight
1256                             }
1257                             onHeightChanged: {
1258                                 collapsed = height <= root.collapsedHeight
1259                             }
1260                         }
1261                     }
1262                 }
1263                 Column {
1264                     id: trackHeadersResizer
1265                     spacing: 0
1266                     width: Math.round(root.baseUnit/3)
1267                     Rectangle {
1268                         id: resizer
1269                         height: trackHeaders.height + subtitleTrackHeader.height
1270                         width: parent.width
1271                         x: root.headerWidth - width
1272                         color: 'red'
1273                         opacity: 0
1274                         Drag.active: headerMouseArea.drag.active
1275                         Drag.proposedAction: Qt.MoveAction
1276 
1277                         MouseArea {
1278                             id: headerMouseArea
1279                             anchors.fill: parent
1280                             hoverEnabled: true
1281                             cursorShape: Qt.SizeHorCursor
1282                             drag.target: parent
1283                             drag.axis: Drag.XAxis
1284                             drag.minimumX: root.minHeaderWidth
1285                             property double startX
1286                             property double originalX
1287                             drag.smoothed: false
1288 
1289                             onPressed: {
1290                                 root.blockAutoScroll = true
1291                             }
1292                             onReleased: {
1293                                 root.blockAutoScroll = false
1294                                 parent.opacity = 0
1295                             }
1296                             onEntered: parent.opacity = 0.5
1297                             onExited: parent.opacity = 0
1298                             onPositionChanged: mouse => {
1299                                 if (mouse.buttons === Qt.LeftButton) {
1300                                     parent.opacity = 0.5
1301                                     headerWidth = Math.max( root.minHeaderWidth, mapToItem(null, x, y).x + 2)
1302                                     timeline.setHeaderWidth(headerWidth)
1303                                 }
1304                             }
1305                         }
1306                     }
1307                 }
1308             }
1309         }
1310         MouseArea {
1311             id: tracksArea
1312             property real clickX
1313             property real clickY
1314             width: root.width - root.headerWidth
1315             height: root.height
1316             x: root.headerWidth
1317             property bool shiftPress: false
1318             // This provides continuous scrubbing and scimming at the left/right edges.
1319             hoverEnabled: true
1320             preventStealing: true
1321             acceptedButtons: Qt.AllButtons
1322             cursorShape: root.activeTool === ProjectTool.SelectTool ? Qt.ArrowCursor : root.activeTool === ProjectTool.RazorTool ? Qt.IBeamCursor : root.activeTool === ProjectTool.RippleTool ? Qt.SplitHCursor : Qt.SizeHorCursor
1323             onWheel: wheel => {
1324                 if (wheel.modifiers & Qt.AltModifier || wheel.modifiers & Qt.ControlModifier || mouseY > trackHeaders.height) {
1325                     zoomByWheel(wheel)
1326                 } else if (root.activeTool !== ProjectTool.SlipTool) {
1327                     var delta = wheel.modifiers & Qt.ShiftModifier ? timeline.fps() : 1
1328                     proxy.position = wheel.angleDelta.y > 0 ? Math.max(root.consumerPosition - delta, 0) : Math.min(root.consumerPosition + delta, timeline.fullDuration - 1)
1329                 }
1330             }
1331             onPressed: mouse => {
1332                 focus = true
1333                 shiftPress = (mouse.modifiers & Qt.ShiftModifier) && (mouse.y > ruler.height) && !(mouse.modifiers & Qt.AltModifier)
1334                 if (mouse.buttons === Qt.MidButton || ((root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool) && (mouse.modifiers & Qt.ControlModifier) && !shiftPress)) {
1335                     clickX = mouseX
1336                     clickY = mouseY
1337                     return
1338                 }
1339                 if ((root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool) && shiftPress && mouse.y > ruler.height) {
1340                         // rubber selection
1341                         rubberSelect.x = mouse.x + scrollView.contentX
1342                         rubberSelect.y = mouse.y - ruler.height + scrollView.contentY
1343                         rubberSelect.clickX = rubberSelect.x
1344                         rubberSelect.clickY = rubberSelect.y
1345                         rubberSelect.originX = rubberSelect.clickX
1346                         rubberSelect.originY = rubberSelect.clickY
1347                         rubberSelect.width = 0
1348                         rubberSelect.height = 0
1349                 } else if (mouse.button & Qt.LeftButton) {
1350                     if (root.activeTool === ProjectTool.RazorTool) {
1351                         // razor tool
1352                         var y = mouse.y - ruler.height + scrollView.contentY - subtitleTrack.height
1353                         if (y >= 0) {
1354                             timeline.cutClipUnderCursor((scrollView.contentX + mouse.x) / root.timeScale, tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId)
1355                         } else if (subtitleTrack.height > 0) {
1356                             timeline.cutClipUnderCursor((scrollView.contentX + mouse.x) / root.timeScale, -2)
1357                         }
1358                     }
1359                     if(root.activeTool === ProjectTool.SlipTool) {
1360                         //slip tool
1361                         var tk = getMouseTrack()
1362                         if (tk < 0) {
1363                             return
1364                         }
1365                         var pos = getMousePos() * root.timeScale
1366                         var sourceTrack = Logic.getTrackById(tk)
1367                         var mainClip = undefined
1368                         mainClip = getItemAtPos(tk, pos, false)
1369                         trimmingClickFrame = Math.round((scrollView.contentX + mouse.x) / root.timeScale)
1370                         timeline.requestStartTrimmingMode(mainClip.clipId, shiftPress)
1371                     }
1372                     if (dragProxy.draggedItem > -1 && mouse.y > ruler.height) {
1373                         // Check if the mouse exit event was not correctly triggered on the draggeditem
1374                         regainFocus(tracksArea.mapToItem(root,mouseX, mouseY))
1375                         if (dragProxy.draggedItem > -1) {
1376                             mouse.accepted = false
1377                             return
1378                         }
1379                     }
1380                     if (root.activeTool === ProjectTool.SpacerTool && mouse.y > ruler.height) {
1381                         // spacer tool
1382                         var y = mouse.y - ruler.height + scrollView.contentY
1383                         var frame = (scrollView.contentX + mouse.x) / root.timeScale
1384                         // Default to all tracks
1385                         spacerTrack = -1
1386                         if (mouse.modifiers & Qt.ControlModifier) {
1387                             if (subtitleTrack.height > 0) {
1388                                 if (y < subtitleTrack.height) {
1389                                     // Activate spacer on subtitle track only
1390                                     spacerTrack = -2
1391                                 } else {
1392                                     spacerTrack = tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y - subtitleTrack.height)).trackInternalId
1393                                 }
1394                             } else {
1395                                 spacerTrack = tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId
1396                             }
1397                         }
1398 
1399                         if((mouse.modifiers & Qt.ShiftModifier) || !timeline.guidesLocked) {
1400                             //spacer tool and shift modifier
1401                             spacerGuides = true;
1402                         }
1403 
1404                         spacerGroup = timeline.requestSpacerStartOperation(spacerTrack, frame)
1405                         spacerMinPos = timeline.spacerMinPos()
1406                         if (spacerGroup > -1 || spacerGuides) {
1407                             drag.axis = Drag.XAxis
1408                             Drag.active = true
1409                             Drag.proposedAction = Qt.MoveAction
1410                             spacerClickFrame = frame
1411                             spacerFrame = spacerGroup > -1 ? controller.getItemPosition(spacerGroup) : frame
1412                             finalSpacerFrame = spacerFrame
1413                             if (spacerGuides) {
1414                                 selectedGuides = timeline.spacerSelection(Math.min(spacerClickFrame, spacerFrame))
1415                                 if (selectedGuides.length > 0) {
1416                                     var firstGuidePos = timeline.getGuidePosition(selectedGuides[0])
1417                                     if (spacerGroup > -1 && firstGuidePos < spacerFrame) {
1418                                         // Don't allow moving guide below 0
1419                                         spacerMinPos = Math.max(spacerMinPos, spacerFrame - firstGuidePos + 1)
1420                                     }
1421                                 }
1422                             }
1423                         }
1424                     } else if (root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool || mouse.y <= ruler.height) {
1425                         if (mouse.y > ruler.height) {
1426                             controller.requestClearSelection();
1427                             proxy.position = Math.min((scrollView.contentX + mouse.x) / root.timeScale, timeline.fullDuration - 1)
1428                         } else if (mouse.y > ruler.guideLabelHeight) {
1429                             proxy.position = Math.min((scrollView.contentX + mouse.x) / root.timeScale, timeline.fullDuration - 1)
1430                         }
1431 
1432                     }
1433                 } else if (mouse.button & Qt.RightButton) {
1434                     if (mouse.y > ruler.height) {
1435                         if (mouse.y > ruler.height + subtitleTrack.height) {
1436                             timeline.activeTrack = tracksRepeater.itemAt(Logic.getTrackIndexFromPos(mouse.y - ruler.height + scrollView.contentY - subtitleTrack.height)).trackInternalId
1437                         } else {
1438                             timeline.activeTrack = -2
1439                         }
1440                         root.clickFrame = Math.floor((mouse.x + scrollView.contentX) / root.timeScale)
1441                         root.showTimelineMenu()
1442                     } else {
1443                         // ruler menu
1444                         proxy.position = (scrollView.contentX + mouse.x) / root.timeScale
1445                         root.showRulerMenu()
1446                     }
1447                 }
1448             }
1449             property bool scim: false
1450             onExited: {
1451                 scim = false
1452                 timeline.showTimelineToolInfo(false)
1453             }
1454             onEntered: {
1455                 timeline.showTimelineToolInfo(true)
1456             }
1457             onDoubleClicked: mouse => {
1458                 if (mouse.buttons === Qt.LeftButton && root.showSubtitles && root.activeTool === ProjectTool.SelectTool && mouse.y > ruler.height && mouse.y < (ruler.height + subtitleTrack.height)) {
1459                     subtitleModel.addSubtitle((scrollView.contentX + mouseX) / root.timeScale)
1460                     timeline.activeTrack = -2
1461                 } else if (mouse.y < ruler.guideLabelHeight) {
1462                     timeline.switchGuide((scrollView.contentX + mouseX) / root.timeScale, false)
1463                 }
1464             }
1465             onPositionChanged: mouse => {
1466                 if (pressed && ((mouse.buttons === Qt.MidButton) || (mouse.buttons === Qt.LeftButton && (root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool) && (mouse.modifiers & Qt.ControlModifier) && !shiftPress))) {
1467                     // Pan view
1468                     var newScroll = Math.min(scrollView.contentX - (mouseX - clickX), timeline.fullDuration * root.timeScale - (scrollView.width - scrollView.ScrollBar.vertical.width))
1469                     var vScroll = Math.min(scrollView.contentY - (mouseY - clickY), trackHeaders.height + subtitleTrackHeader.height - scrollView.height+ horZoomBar.height)
1470                     scrollView.contentX = Math.max(newScroll, 0)
1471                     scrollView.contentY = Math.max(vScroll, 0)
1472                     clickX = mouseX
1473                     clickY = mouseY
1474                     return
1475                 }
1476                 var mouseXPos
1477                 if (subtitleMouseArea.containsMouse) {
1478                     // Warning, subtitleMouseArea uses the full scrollview's length
1479                     mouseXPos = Math.floor(subtitleMouseArea.mouseX / root.timeScale)
1480                 } else {
1481                     mouseXPos = Math.floor((scrollView.contentX + mouseX) / root.timeScale)
1482                 }
1483                 if (root.activeTool === ProjectTool.SlipTool && pressed) {
1484                     var frame = mouseXPos
1485                     trimmingOffset = frame - trimmingClickFrame
1486                     timeline.slipPosChanged(trimmingOffset);
1487                 }
1488                 if (!pressed && !rubberSelect.visible && root.activeTool === ProjectTool.RazorTool) {
1489                     cutLine.x = mouseXPos * root.timeScale - scrollView.contentX
1490                     if (mouse.modifiers & Qt.ShiftModifier) {
1491                         // Seek
1492                         proxy.position = mouseXPos
1493                     }
1494                 }
1495                 root.mousePosChanged(Math.max(0, mouseXPos))
1496                 ruler.showZoneLabels = mouse.y < ruler.height
1497                 if (shiftPress && mouse.buttons === Qt.LeftButton && (root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool) && !rubberSelect.visible && rubberSelect.y > 0) {
1498                     // rubber selection, check if mouse move was enough
1499                     var dx = rubberSelect.originX - (mouseX + scrollView.contentX)
1500                     var dy = rubberSelect.originY - (mouseY - ruler.height + scrollView.contentY)
1501                     if ((Math.abs(dx) + Math.abs(dy)) > Qt.styleHints.startDragDistance) {
1502                         rubberSelect.visible = true
1503                     }
1504                 }
1505                 if (rubberSelect.visible) {
1506                     var newX = mouse.x + scrollView.contentX
1507                     var newY = mouse.y + scrollView.contentY - ruler.height
1508                     if (newX < rubberSelect.originX) {
1509                         rubberSelect.x = newX
1510                         rubberSelect.width = rubberSelect.originX - newX
1511                     } else {
1512                         rubberSelect.x = rubberSelect.originX
1513                         rubberSelect.width = newX - rubberSelect.originX
1514                     }
1515                     if (newY < rubberSelect.originY) {
1516                         rubberSelect.y = newY
1517                         rubberSelect.height = rubberSelect.originY - newY
1518                     } else {
1519                         rubberSelect.y = rubberSelect.originY
1520                         rubberSelect.height = newY - rubberSelect.originY
1521                     }
1522                     continuousScrolling(newX, newY)
1523                 } else if ((pressedButtons & Qt.LeftButton) && (!shiftPress || spacerGuides)) {
1524                     if (root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool || (mouse.y < ruler.height && root.activeTool !== ProjectTool.SlipTool)) {
1525                         proxy.position = Math.max(0, Math.min((scrollView.contentX + mouse.x) / root.timeScale, timeline.fullDuration - 1))
1526                     } else if (root.activeTool === ProjectTool.SpacerTool && spacerGroup > -1) {
1527                         // Spacer tool, move group
1528                         var track = controller.getItemTrackId(spacerGroup)
1529                         var lastPos = controller.getItemPosition(spacerGroup)
1530                         var frame = Math.round((mouse.x + scrollView.contentX) / root.timeScale) + spacerFrame - spacerClickFrame
1531                         frame = Math.max(spacerMinPos, frame)
1532                         finalSpacerFrame = controller.suggestItemMove(spacerGroup, track, frame, root.consumerPosition, (mouse.modifiers & Qt.ShiftModifier) ? 0 : root.snapping)[0]
1533                         if (spacerGuides) {
1534                             timeline.spacerMoveGuides(selectedGuides, finalSpacerFrame - lastPos)
1535                         }
1536                         continuousScrolling(mouse.x + scrollView.contentX, mouse.y + scrollView.contentY)
1537                     } else if (spacerGuides) {
1538                         var frame = Math.round((mouse.x + scrollView.contentX) / root.timeScale)
1539                         frame = Math.max(spacerMinPos, frame)
1540                         timeline.spacerMoveGuides(selectedGuides, frame - spacerFrame)
1541                         spacerFrame = frame;
1542                     }
1543 
1544                     scim = true
1545                 } else {
1546                     scim = false
1547                 }
1548             }
1549             onReleased: mouse => {
1550                 if((mouse.button & Qt.LeftButton) && root.activeTool === ProjectTool.SlipTool) {
1551                     // slip tool
1552                     controller.requestSlipSelection(trimmingOffset, true)
1553                     trimmingOffset = 0;
1554                     mouse.accepted = false
1555                 }
1556                 if (rubberSelect.visible) {
1557                     rubberSelect.visible = false
1558                     var y = rubberSelect.y
1559                     var selectSubs = false
1560                     var selectOnlySubs = false
1561                     var selectionHeight = rubberSelect.height
1562                     if (showSubtitles) {
1563                         selectSubs = y < subtitleTrack.height
1564                         var bottomRubber = y + rubberSelect.height
1565                         if (bottomRubber > subtitleTrack.height) {
1566                             y = Math.max(0, y - subtitleTrack.height)
1567                             if (selectSubs) {
1568                                 selectionHeight = bottomRubber - subtitleTrack.height
1569                             }
1570                         } else {
1571                             y -= subtitleTrack.height
1572                             selectOnlySubs = true
1573                         }
1574                     }
1575                     var topTrack = Logic.getTrackIndexFromPos(Math.max(0, y))
1576                     var bottomTrack = Logic.getTrackIndexFromPos(Math.max(0, y) + selectionHeight)
1577                     // Check if bottom of rubber selection covers the last track compositions
1578                     console.log('Got rubber bottom: ', y, ' - height: ', selectionHeight, ', TK y: ', Logic.getTrackYFromId(tracksRepeater.itemAt(bottomTrack).trackInternalId), ', SCROLLVIEWY: ', scrollView.contentY)
1579                     var selectBottomCompositions = ((y + selectionHeight) - Logic.getTrackYFromId(tracksRepeater.itemAt(bottomTrack).trackInternalId)) > (Logic.getTrackHeightByPos(bottomTrack) * 0.6)
1580                     if (bottomTrack >= topTrack) {
1581                         var t = []
1582                         if (!selectOnlySubs) {
1583                             for (var i = topTrack; i <= bottomTrack; i++) {
1584                                 t.push(tracksRepeater.itemAt(i).trackInternalId)
1585                             }
1586                         }
1587                         var startFrame = Math.round(rubberSelect.x / root.timeScale)
1588                         var endFrame = Math.round((rubberSelect.x + rubberSelect.width) / root.timeScale)
1589                         timeline.selectItems(t, startFrame, endFrame, mouse.modifiers & Qt.ControlModifier, selectBottomCompositions, selectSubs);
1590                     }
1591                     rubberSelect.y = -1
1592                 } else if (shiftPress && !spacerGuides) {
1593                     if (root.activeTool === ProjectTool.RazorTool) {
1594                         // Shift click, process seek
1595                         proxy.position = Math.min((scrollView.contentX + mouse.x) / root.timeScale, timeline.fullDuration - 1)
1596                     } else if (dragProxy.draggedItem > -1) {
1597                         // Select item
1598                         if (timeline.selection.indexOf(dragProxy.draggedItem) === -1) {
1599                             controller.requestAddToSelection(dragProxy.draggedItem)
1600                         } else {
1601                             controller.requestRemoveFromSelection(dragProxy.draggedItem)
1602                         }
1603                     } else if (!rubberSelect.visible) {
1604                         // Mouse release with shift press and no rubber select, seek
1605                         proxy.position = Math.min((scrollView.contentX + mouse.x) / root.timeScale, timeline.fullDuration - 1)
1606                     }
1607                     return
1608                 }
1609 
1610                 if (spacerGroup > -1 && finalSpacerFrame > -1) {
1611                     var frame = controller.getItemPosition(spacerGroup)
1612                     timeline.requestSpacerEndOperation(spacerGroup, spacerFrame, finalSpacerFrame, spacerTrack, selectedGuides, spacerGuides ? spacerClickFrame : -1);
1613                 } else if (spacerGuides) {
1614                     // Move back guides to original pos
1615                     timeline.spacerMoveGuides(selectedGuides, spacerClickFrame - spacerFrame)
1616                     timeline.moveGuidesInRange(spacerClickFrame, -1, spacerFrame - finalSpacerFrame)
1617                 }
1618 
1619                 if (spacerGroup > -1 && finalSpacerFrame > -1 || spacerGuides) {
1620                     spacerClickFrame = -1
1621                     spacerFrame = -1
1622                     spacerGroup = -1
1623                     spacerMinPos = -1
1624                     selectedGuides = []
1625                     spacerGuides = false
1626                 }
1627 
1628                 scim = false
1629             }
1630 
1631             Item {
1632                 // Guide zone delimiter
1633                 Rectangle {
1634                     width: rulercontainer.width
1635                     height: 1
1636                     anchors.top: parent.top
1637                     anchors.topMargin: ruler.guideLabelHeight
1638                     color: activePalette.dark
1639                     visible: ruler.guideLabelHeight > 0
1640                 }
1641 
1642                 // monitor zone
1643                 Rectangle {
1644                     width: rulercontainer.width
1645                     height: 1
1646                     anchors.top: parent.top
1647                     anchors.topMargin: ruler.height - ruler.zoneHeight
1648                     color: activePalette.dark
1649                     Rectangle {
1650                         width: rulercontainer.width
1651                         height: 1
1652                         anchors.top: parent.bottom
1653                         color: activePalette.light
1654                     }
1655                 }
1656                 Flickable {
1657                     // Non-slider scroll area for the Ruler.
1658                     id: rulercontainer
1659                     width: root.width - headerWidth
1660                     height: Math.round(root.baseUnit * 2.5) + ruler.guideLabelHeight
1661                     contentX: scrollView.contentX
1662                     contentWidth: Math.max(parent.width, timeline.fullDuration * timeScale)
1663                     interactive: false
1664                     clip: true
1665                     onWidthChanged: {
1666                         ruler.adjustStepSize()
1667                     }
1668                     Ruler {
1669                         id: ruler
1670                         width: rulercontainer.contentWidth
1671                         height: parent.height
1672                         TimelinePlayhead {
1673                             id: playhead
1674                             height: Math.round(root.baseUnit * .8)
1675                             width: Math.round(root.baseUnit * 1.2)
1676                             fillColor: activePalette.windowText
1677                             anchors.bottom: parent.bottom
1678                             anchors.bottomMargin: ruler.zoneHeight - 1
1679                             anchors.horizontalCenter: rulerCursor.horizontalCenter
1680                             // bottom line on zoom
1681                         }
1682                         Rectangle {
1683                             // Vertical line over ruler zone
1684                             id: rulerCursor
1685                             color: root.textColor
1686                             width: 1
1687                             height: ruler.zoneHeight - 1
1688                             x: cursor.x
1689                             anchors.bottom: parent.bottom
1690                             Rectangle {
1691                                 color: ruler.dimmedColor
1692                                 width: Math.max(1, root.timeScale)
1693                                 height: 1
1694                                 visible: width > playhead.width
1695                             }
1696                         }
1697                     }
1698                 }
1699                 MouseArea {
1700                     anchors.top: parent.top
1701                     height: rulercontainer.height
1702                     width: rulercontainer.width
1703                     acceptedButtons: Qt.NoButton
1704                     cursorShape: ruler.cursorShape
1705                 }
1706 
1707                 Item {
1708                     id: baseContainer
1709                     width: root.width - headerWidth
1710                     height: root.height - ruler.height
1711                     y: ruler.height
1712                     clip: true
1713                     // These make the striped background for the tracks.
1714                     // It is important that these are not part of the track visual hierarchy;
1715                     // otherwise, the clips will be obscured by the Track's background.
1716                     Rectangle {
1717                         width: scrollView.width
1718                         border.width: 1
1719                         border.color: root.frameColor
1720                         height: subtitleTrack.height
1721                         color: (controller && controller.isSubtitleTrack(timeline.activeTrack)) ? Qt.tint(getTrackColor(false, false), selectedTrackColor) : getTrackColor(false, false)
1722                     }
1723                     Column {
1724                         y: subtitleTrack.height
1725                         topPadding: -scrollView.contentY
1726                         Repeater {
1727                             model: multitrack
1728                             id: trackBaseRepeater
1729                             delegate: Rectangle {
1730                                 width: scrollView.width
1731                                 border.width: 1
1732                                 border.color: root.frameColor
1733                                 height: model.trackHeight
1734                                 color: (model.item === timeline.activeTrack) ? Qt.tint(getTrackColor(model.audio, false), selectedTrackColor) : getTrackColor(model.audio, false)
1735                             }
1736                         }
1737                     }
1738                     Flickable {
1739                         id: scrollView
1740                         anchors.fill: parent
1741                         anchors.rightMargin: vertScroll.visible ? vertScroll.width : 0
1742                         anchors.bottomMargin: horZoomBar.visible ? horZoomBar.height : 0
1743                         // Click and drag should seek, not scroll the timeline view
1744                         //flickableItem.interactive: false
1745                         clip: true
1746                         interactive: false
1747                         pixelAligned: true
1748                         /*
1749                          // Replaced by our custom ZoomBar
1750                          ScrollBar.horizontal: ScrollBar {
1751                             id: horScroll
1752                             parent: scrollView.parent
1753                             anchors.top: scrollView.top
1754                             anchors.left: scrollView.left
1755                             anchors.right: scrollView.right
1756                         }*/
1757                         ScrollBar.vertical: ScrollBar {
1758                             id: vertScroll
1759                             parent: scrollView.parent
1760                             anchors.top: scrollView.top
1761                             anchors.left: scrollView.right
1762                             anchors.bottom: scrollView.bottom
1763                         }
1764                         contentWidth: tracksContainerArea.width
1765                         contentHeight: tracksContainerArea.height
1766                         Item {
1767                             id: subtitleTrack
1768                             width: tracksContainerArea.width
1769                             height: 0
1770                             MouseArea {
1771                                 id: subtitleMouseArea
1772                                 anchors.fill: parent
1773                                 acceptedButtons: Qt.NoButton
1774                                 hoverEnabled: true
1775                                 onWheel: wheel => zoomByWheel(wheel)
1776                                 onEntered: {
1777                                     if (root.activeTool === ProjectTool.SelectTool) {
1778                                         timeline.showKeyBinding(i18n("<b>Double click</b> to add a subtitle"))
1779                                     }
1780                                 }
1781                                 onPositionChanged: mouse => {
1782                                     tracksArea.positionChanged(mouse)
1783                                 }
1784                                 onExited: {
1785                                     timeline.showKeyBinding()
1786                                 }
1787                             }
1788 
1789                             Repeater { id: subtitlesRepeater; model: subtitleDelegateModel }
1790                         }
1791                         Item {
1792                             id: tracksContainerArea
1793                             width: Math.max(scrollView.width - vertScroll.width, timeline.fullDuration * timeScale)
1794                             height: trackHeaders.height + subtitleTrackHeader.height
1795                             y: subtitleTrack.height
1796                             //Math.max(trackHeaders.height, scrollView.contentHeight - scrollView.__horizontalScrollBar.height)
1797                             //color: root.color
1798                             Item {
1799                                 // Drag proxy, responsible for clip / composition move
1800                                 id: dragProxy
1801                                 x: 0
1802                                 y: 0
1803                                 width: 0
1804                                 height: 0
1805                                 property int draggedItem: -1
1806                                 property int sourceTrack
1807                                 property int sourceFrame
1808                                 property bool isComposition
1809                                 property int verticalOffset
1810                                 property var masterObject
1811                                 // opacity: 0.8
1812                                 MouseArea {
1813                                     id: dragProxyArea
1814                                     anchors.fill: parent
1815                                     drag.target: parent
1816                                     drag.axis: Drag.XAxis
1817                                     drag.smoothed: false
1818                                     property int dragFrame
1819                                     property int snapping: root.snapping
1820                                     property bool moveMirrorTracks: true
1821                                     cursorShape: root.activeTool === ProjectTool.SelectTool ? dragProxyArea.drag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor : tracksArea.cursorShape
1822                                     enabled: root.activeTool === ProjectTool.SelectTool || root.activeTool === ProjectTool.RippleTool
1823                                     onPressed: mouse => {
1824                                         if (mouse.modifiers & Qt.ControlModifier || (mouse.modifiers & Qt.ShiftModifier && !(mouse.modifiers & Qt.AltModifier))) {
1825                                             mouse.accepted = false
1826                                             return
1827                                         }
1828                                         if (!timeline.exists(dragProxy.draggedItem)) {
1829                                             endDrag()
1830                                             mouse.accepted = false
1831                                             return
1832                                         }
1833                                         dragFrame = -1
1834                                         moveMirrorTracks = !(mouse.modifiers & Qt.MetaModifier) && (Qt.platform.os != "windows" || !(mouse.modifiers & Qt.AltModifier))
1835                                         timeline.activeTrack = dragProxy.sourceTrack
1836                                         var singleSelection = mouse.modifiers & Qt.AltModifier
1837                                         if (singleSelection || timeline.selection.indexOf(dragProxy.draggedItem) === -1) {
1838                                             doubleClickTimer.start()
1839                                             controller.requestAddToSelection(dragProxy.draggedItem, /*clear=*/ !(mouse.modifiers & Qt.ShiftModifier), /*single item selection */ singleSelection)
1840                                         }
1841                                         timeline.showAsset(dragProxy.draggedItem)
1842                                         root.blockAutoScroll = true
1843                                         clipBeingMovedId = dragProxy.draggedItem
1844                                         if (dragProxy.draggedItem > -1) {
1845                                             var tk = controller.getItemTrackId(dragProxy.draggedItem)
1846                                             var x = controller.getItemPosition(dragProxy.draggedItem)
1847                                             var posx = Math.round((parent.x)/ root.timeScale)
1848                                             var clickAccepted = true
1849                                             var currentMouseTrack = Logic.getTrackIdFromPos(parent.y)
1850                                             if (controller.normalEdit() && (tk !== currentMouseTrack || x !== posx)) {
1851                                                 console.log('incorrect drag, Trying to recover item', parent.y,'xpos',x,'=',posx,'track',tk)
1852                                                 // Try to find correct item
1853                                                 var tentativeClip = getItemAtPos(currentMouseTrack, mouseX + parent.x, dragProxy.isComposition)
1854                                                 if (tentativeClip && tentativeClip.clipId) {
1855                                                     console.log('missing item', tentativeClip.clipId)
1856                                                     clickAccepted = true
1857                                                     dragProxy.draggedItem = tentativeClip.clipId
1858                                                     dragProxy.x = tentativeClip.x
1859                                                     dragProxy.y = currentMouseTrack.y + tentativeClip.isComposition ? tentativeClip.displayHeight : tentativeClip.y
1860                                                     dragProxy.height = tentativeClip.itemHeight()
1861                                                     dragProxy.width = tentativeClip.width
1862                                                     dragProxy.masterObject = tentativeClip
1863                                                     dragProxy.sourceTrack = tk
1864                                                     dragProxy.isComposition = tentativeClip.isComposition
1865                                                     dragProxy.verticalOffset = tentativeClip.isComposition ? tentativeClip.displayHeight : 0
1866                                                 } else {
1867                                                     console.log('item not found')
1868                                                     clickAccepted = false
1869                                                     mouse.accepted = false
1870                                                     dragProxy.draggedItem = -1
1871                                                     dragProxy.masterObject = undefined
1872                                                     dragProxy.sourceFrame = -1
1873                                                     parent.x = 0
1874                                                     parent.y = 0
1875                                                     parent.width = 0
1876                                                     parent.height = 0
1877                                                 }
1878                                             }
1879                                             if (clickAccepted && dragProxy.draggedItem != -1) {
1880                                                 focus = true;
1881                                                 root.mainItemId = dragProxy.draggedItem
1882                                                 dragProxy.masterObject.originalX = dragProxy.masterObject.x
1883                                                 dragProxy.masterObject.originalTrackId = dragProxy.masterObject.trackId
1884                                                 dragProxy.sourceFrame = dragProxy.masterObject.modelStart
1885                                                 dragProxy.masterObject.forceActiveFocus();
1886                                             } else {
1887                                                 root.mainItemId = -1
1888                                             }
1889                                         } else {
1890                                             mouse.accepted = false
1891                                             parent.x = 0
1892                                             parent.y = 0
1893                                             parent.width = 0
1894                                             parent.height = 0
1895                                         }
1896                                     }
1897                                     onPositionChanged: mouse => {
1898                                         // we have to check item validity in the controller, because they could have been deleted since the beginning of the drag
1899                                         if (dragProxy.draggedItem > -1 && !timeline.exists(dragProxy.draggedItem)) {
1900                                             endDrag()
1901                                             return
1902                                         }
1903                                         if (dragProxy.draggedItem > -1 && mouse.buttons === Qt.LeftButton &&  (controller.isClip(dragProxy.draggedItem) || controller.isComposition(dragProxy.draggedItem))) {
1904                                             continuousScrolling(mouse.x + parent.x, dragProxyArea.mouseY + parent.y - dragProxy.verticalOffset)
1905                                             snapping = (mouse.modifiers & Qt.ShiftModifier) ? 0 : root.snapping
1906                                             moveItem()
1907                                         }
1908                                     }
1909 
1910                                     function moveItem() {
1911                                         if (dragProxy.draggedItem > -1 && !rubberSelect.visible) {
1912                                             var mapped = Math.max(0, tracksContainerArea.mapFromItem(dragProxy, dragProxyArea.mouseX, 0).x)
1913                                             root.mousePosChanged(Math.floor(mapped / root.timeScale))
1914                                             var posx = Math.round((parent.x)/ root.timeScale)
1915                                             var posy = Math.min(Math.max(0, dragProxyArea.mouseY + parent.y - dragProxy.verticalOffset), tracksContainerArea.height)
1916                                             var tId = Logic.getTrackIdFromPos(posy)
1917                                             if (dragProxy.masterObject && tId === dragProxy.masterObject.trackId) {
1918                                                 if (posx == dragProxyArea.dragFrame && controller.normalEdit()) {
1919                                                     return
1920                                                 }
1921                                             }
1922                                             if (dragProxy.isComposition) {
1923                                                 var moveData = controller.suggestCompositionMove(dragProxy.draggedItem, tId, posx, root.consumerPosition, dragProxyArea.snapping)
1924                                                 dragProxyArea.dragFrame = moveData[0]
1925                                                 timeline.activeTrack = moveData[1]
1926                                             } else {
1927                                                 if (!controller.normalEdit() && dragProxy.masterObject.parent !== dragContainer) {
1928                                                     var pos = dragProxy.masterObject.mapToGlobal(dragProxy.masterObject.x, dragProxy.masterObject.y)
1929                                                     dragProxy.masterObject.parent = dragContainer
1930                                                     pos = dragProxy.masterObject.mapFromGlobal(pos.x, pos.y)
1931                                                     dragProxy.masterObject.x = pos.x
1932                                                     dragProxy.masterObject.y = pos.y
1933                                                 }
1934                                                 var moveData = controller.suggestClipMove(dragProxy.draggedItem, tId, posx, root.consumerPosition, dragProxyArea.snapping, moveMirrorTracks)
1935                                                 dragProxyArea.dragFrame = moveData[0]
1936                                                 timeline.activeTrack = moveData[1]
1937                                                 //timeline.getItemMovingTrack(dragProxy.draggedItem)
1938                                             }
1939                                             var delta = dragProxyArea.dragFrame - dragProxy.sourceFrame
1940                                             if (delta != 0) {
1941                                                 var s = timeline.simplifiedTC(Math.abs(delta))
1942                                                 s = i18n("Offset: %1, Position: %2", (delta < 0 ? '-' : '+') + s, timeline.simplifiedTC(dragProxyArea.dragFrame))
1943                                                 timeline.showToolTip(s);
1944                                             } else {
1945                                                 timeline.showToolTip()
1946                                                 //bubbleHelp.hide()
1947                                             }
1948                                         }
1949                                     }
1950                                     onReleased: {
1951                                         clipBeingMovedId = -1
1952                                         root.blockAutoScroll = false
1953                                         if (dragProxy.draggedItem > -1 && dragFrame > -1 && (controller.isClip(dragProxy.draggedItem) || controller.isComposition(dragProxy.draggedItem))) {
1954                                             var tId = controller.getItemTrackId(dragProxy.draggedItem)
1955                                             if (dragProxy.isComposition) {
1956                                                 controller.requestCompositionMove(dragProxy.draggedItem, dragProxy.sourceTrack, dragProxy.sourceFrame, true, false)
1957                                                 controller.requestCompositionMove(dragProxy.draggedItem, tId, dragFrame , true, true)
1958                                             } else {
1959                                                 if (controller.normalEdit()) {
1960                                                     // Move clip back to original position
1961                                                     controller.requestClipMove(dragProxy.draggedItem, dragProxy.sourceTrack, dragProxy.sourceFrame, moveMirrorTracks, true, false, false, true)
1962                                                     // Move clip to final pos
1963                                                     controller.requestClipMove(dragProxy.draggedItem, tId, dragFrame , moveMirrorTracks, true, true, true)
1964                                                 } else {
1965                                                     // Fake move, only process final move
1966                                                     timeline.endFakeMove(dragProxy.draggedItem, dragFrame, true, true, true)
1967                                                 }
1968                                             }
1969                                             if (dragProxy.masterObject && dragProxy.masterObject.isGrabbed) {
1970                                                 dragProxy.masterObject.grabItem()
1971                                             }
1972                                             dragProxy.x = controller.getItemPosition(dragProxy.draggedItem) * root.timeScale
1973                                             timeline.showToolTip()
1974                                             //bubbleHelp.hide()
1975                                             tracksArea.focus = true
1976                                             if (!dragProxyArea.containsMouse) {
1977                                                 regainFocus(dragProxyArea.mapToItem(root,mouseX, mouseY))
1978                                             }
1979                                         }
1980                                     }
1981                                     onDoubleClicked: {
1982                                         if (dragProxy.masterObject.keyframeModel && dragProxy.masterObject.showKeyframes && !doubleClickTimer.running) {
1983                                             var newVal = (dragProxy.height - mouseY) / dragProxy.height
1984                                             var newPos = Math.round(mouseX / timeScale) + dragProxy.masterObject.inPoint
1985                                             timeline.addEffectKeyframe(dragProxy.draggedItem, newPos, newVal)
1986                                         } else {
1987                                             clipBeingMovedId = -1
1988                                             timeline.ungrabHack()
1989                                             if(dragProxy.masterObject.itemType === ProducerType.Timeline) {
1990                                                 timeline.focusTimelineSequence(dragProxy.draggedItem)
1991                                             } else if(dragProxy.masterObject.itemType === ProducerType.Text || dragProxy.masterObject.itemType === ProducerType.TextTemplate) {
1992                                                 timeline.editTitleClip(dragProxy.draggedItem)
1993                                             } else if (dragProxy.masterObject.itemType === ProducerType.Animation) {
1994                                                 timeline.editAnimationClip(dragProxy.draggedItem)
1995                                             } else {
1996                                                 timeline.editItemDuration(dragProxy.draggedItem)
1997                                             }
1998                                         }
1999                                     }
2000                                     onClicked: {
2001                                         if (dragProxy.masterObject.keyframeModel && dragProxy.masterObject.showKeyframes) {
2002                                             dragProxy.masterObject.resetSelection()
2003                                         }
2004                                     }
2005                                 }
2006                             }
2007                             MouseArea {
2008                                 anchors.fill: parent
2009                                 acceptedButtons: Qt.NoButton
2010                                 onWheel: wheel => zoomByWheel(wheel)
2011                                 cursorShape: dragProxyArea.drag.active ? Qt.ClosedHandCursor : tracksArea.cursorShape
2012                             }
2013                             Column {
2014                                 id: tracksContainer
2015                                 Repeater { id: tracksRepeater; model: trackDelegateModel }
2016                                 Item {
2017                                     id: dragContainer
2018                                     z: 100
2019                                 }
2020                             }
2021                             Rectangle {
2022                                 id: sameTrackIndicator
2023                                 color: 'red'
2024                                 opacity: 0.5
2025                                 visible: false
2026                                 width: root.baseUnit
2027                                 height: width
2028                             }
2029                         }
2030                         Rectangle {
2031                             id: rubberSelect
2032                             // Used to determine if drag start should trigger an event
2033                             property int originX
2034                             // Used to determine if drag start should trigger an event
2035                             property int originY
2036                             // Absolute position of the click event
2037                             property int clickX
2038                             property int clickY
2039                             y: -1
2040                             color: Qt.rgba(activePalette.highlight.r, activePalette.highlight.g, activePalette.highlight.b, 0.4)
2041                             border.color: activePalette.highlight
2042                             border.width: 1
2043                             visible: false
2044                         }
2045                         Item {
2046                             id: recordStartPlaceHolder
2047                             x: 0
2048                             width: 0
2049                         }
2050                         
2051                         Item {
2052                             id: recordPlaceHolder
2053                             // Used to determine if drag start should trigger an event
2054                             property var startTime: 0
2055                             property double currentLevel
2056                             property var recModel: []
2057                             property int channels: 1
2058                             property int maxWidth: 2048
2059                             property int totalChunks: 0
2060                             visible: false
2061                             clip: true
2062                             anchors.left: recordStartPlaceHolder.left
2063                             width: 0
2064                             Repeater {
2065                                 id: recWaveformRepeater
2066                                 model: Math.ceil(recordPlaceHolder.width / recordPlaceHolder.maxWidth)
2067                                 property bool repaintNodes: false
2068                                 anchors.fill: parent
2069                                 TimelineRecWaveform {
2070                                     id: recWave
2071                                     width: recordPlaceHolder.maxWidth < recordPlaceHolder.width ? index == recordPlaceHolder.totalChunks - 1 ? recordPlaceHolder.width % recordPlaceHolder.maxWidth : recordPlaceHolder.maxWidth : Math.round(recordPlaceHolder.width)
2072                                     height: recordPlaceHolder.height
2073                                     ix: index
2074                                     channels: recordPlaceHolder.channels
2075                                     isFirstChunk: index == 0
2076                                     isOpaque: true
2077                                     scaleFactor: root.timeScale
2078                                     format: timeline.audioThumbFormat
2079                                     waveInPoint: Math.round((index * recordPlaceHolder.maxWidth / root.timeScale) * recordPlaceHolder.channels)
2080                                     waveOutPoint: waveInPoint + Math.round(width / root.timeScale) * recordPlaceHolder.channels
2081                                     fillColor0: Qt.rgba(1, 0, 0, 0.3)
2082                                     fillColor1: Qt.rgba(1, 0, 0)
2083                                     fillColor2: Qt.rgba(1, .5, 0)
2084                                     enforceRepaint: false
2085                                 }
2086                             }
2087                             Text {
2088                                 property int recState: audiorec.recordState
2089                                 text: i18n("Recording")
2090                                 anchors.right: parent.right
2091                                 anchors.rightMargin: 2
2092                                 anchors.top: parent.top
2093                                 font: miniFont
2094                                 color: '#FFF'
2095                                 onRecStateChanged: {
2096                                     if (recState == 1) {
2097                                         // Recording
2098                                         text = i18n("Recording")
2099                                     } else if (recState == 2) {
2100                                         text = i18n("Paused")
2101                                     }
2102                                 }
2103                             }
2104                         }
2105                         Repeater { 
2106                             id: guidesRepeater
2107                             model: guidesDelegateModel
2108                         }
2109                         Rectangle {
2110                             id: cursor
2111                             visible: root.consumerPosition > -1
2112                             color: root.textColor
2113                             width: 1
2114                             opacity: 1
2115                             height: tracksContainerArea.height
2116                             x: Math.round(root.consumerPosition * root.timeScale)
2117                         }
2118                     }
2119                     Kdenlive.ZoomBar {
2120                         id: horZoomBar
2121                         visible: scrollView.visibleArea.widthRatio < 1
2122                         anchors {
2123                             left: parent.left
2124                             right: parent.right
2125                             top: scrollView.bottom
2126                         }
2127                         height: Math.round(root.baseUnit * 0.7)
2128                         barMinWidth: root.baseUnit
2129                         fitsZoom: timeline.scaleFactor === root.fitZoom() && root.scrollPos() === 0
2130                         zoomFactor: scrollView.visibleArea.widthRatio
2131                         onProposeZoomFactor: (proposedValue) => {
2132                             timeline.scaleFactor = scrollView.width / Math.round(proposedValue * scrollView.contentWidth / root.timeScale)
2133                             zoomOnBar = true
2134                         }
2135                         contentPos: scrollView.contentX / scrollView.contentWidth
2136                         onProposeContentPos: (proposedValue) => { scrollView.contentX = Math.max(0, proposedValue * scrollView.contentWidth) }
2137                         onZoomByWheel: wheel => root.zoomByWheel(wheel)
2138                         onFitZoom: {
2139                             timeline.scaleFactor = root.fitZoom()
2140                             scrollView.contentX = 0
2141                             zoomOnBar = true
2142                         }
2143                     }
2144                 }
2145             }
2146             Rectangle {
2147                 id: cutLine
2148                 visible: root.activeTool === ProjectTool.RazorTool && (tracksArea.mouseY > ruler.height || subtitleMouseArea.containsMouse)
2149                 color: 'red'
2150                 width: 1
2151                 opacity: 1
2152                 height: tracksContainerArea.height
2153                 x: 0
2154                 //x: root.consumerPosition * root.timeScale - scrollView.contentX
2155                 y: ruler.height
2156                 Rectangle {
2157                     color: 'red'
2158                     width: Math.max(0, 1 * root.timeScale - 1)
2159                     visible: width > 1
2160                     opacity: 0.2
2161                     anchors.left:parent.right
2162                     anchors.top: parent.top
2163                     anchors.bottom: parent.bottom
2164                 }
2165             }
2166             Rectangle {
2167                 id: multicamLine
2168                 visible: root.activeTool === ProjectTool.MulticamTool && timeline.multicamIn > -1
2169                 color: 'purple'
2170                 width: 3
2171                 opacity: 1
2172                 height: tracksContainerArea.height
2173                 x: timeline.multicamIn * root.timeScale - scrollView.contentX
2174                 y: ruler.height
2175                 Rectangle {
2176                     // multicam in label
2177                     width: multilabel.contentWidth + 4
2178                     height: multilabel.contentHeight + 2
2179                     radius: height / 4
2180                     color: 'purple'
2181                     anchors {
2182                         top: parent.top
2183                         left: parent.left
2184                     }
2185                     Text {
2186                         id: multilabel
2187                         text: i18n("Multicam In")
2188                         bottomPadding: 2
2189                         leftPadding: 2
2190                         rightPadding: 2
2191                         font: miniFont
2192                         color: '#FFF'
2193                     }
2194                 }
2195             }
2196         }
2197     }
2198 
2199     Rectangle {
2200         id: bubbleHelp
2201         property alias text: bubbleHelpLabel.text
2202         color: root.color //application.toolTipBaseColor
2203         width: bubbleHelpLabel.width + 6
2204         height: bubbleHelpLabel.height + 6
2205         radius: 3
2206         states: [
2207             State { name: 'invisible'; PropertyChanges { target: bubbleHelp; opacity: 0} },
2208             State { name: 'visible'; PropertyChanges { target: bubbleHelp; opacity: 0.8} }
2209         ]
2210         state: 'invisible'
2211         transitions: [
2212             Transition {
2213                 from: 'invisible'
2214                 to: 'visible'
2215                 OpacityAnimator { target: bubbleHelp; duration: 200; easing.type: Easing.InOutQuad }
2216             },
2217             Transition {
2218                 from: 'visible'
2219                 to: 'invisible'
2220                 OpacityAnimator { target: bubbleHelp; duration: 200; easing.type: Easing.InOutQuad }
2221             }
2222         ]
2223         Label {
2224             id: bubbleHelpLabel
2225             color: activePalette.text //application.toolTipTextColor
2226             anchors.centerIn: parent
2227             font: miniFont
2228         }
2229         function show(x, y, text) {
2230             bubbleHelp.text = text
2231             bubbleHelp.x = x + tracksArea.x - scrollView.contentX - bubbleHelp.width
2232             bubbleHelp.y = y + tracksArea.y - scrollView.contentY - bubbleHelp.height + ruler.height - 3
2233             if (bubbleHelp.state !== 'visible')
2234                 bubbleHelp.state = 'visible'
2235         }
2236         function hide() {
2237             bubbleHelp.state = 'invisible'
2238             bubbleHelp.opacity = 0
2239         }
2240     }
2241     /*DropShadow {
2242         source: bubbleHelp
2243         anchors.fill: bubbleHelp
2244         opacity: bubbleHelp.opacity
2245         horizontalOffset: 3
2246         verticalOffset: 3
2247         radius: 8
2248         color: '#80000000'
2249         transparentBorder: true
2250         fast: true
2251     }*/
2252 
2253     DelegateModel {
2254         id: trackDelegateModel
2255         model: multitrack
2256         delegate: Track {
2257             trackModel: multitrack
2258             rootIndex: trackDelegateModel.modelIndex(index)
2259             width: tracksContainerArea.width
2260             height: trackHeight
2261             isAudio: audio
2262             trackThumbsFormat: thumbsFormat
2263             trackInternalId: item
2264             effectZones: model.effectZones
2265             z: tracksRepeater.count - index
2266         }
2267     }
2268 
2269 
2270     DelegateModel {
2271         id: guidesDelegateModel
2272         model: guidesModel
2273         Item {
2274             id: guideRoot
2275             z: 20
2276             Rectangle {
2277                 id: guideBase
2278                 width: 1
2279                 height: tracksContainerArea.height
2280                 x: Math.round(model.frame * root.timeScale);
2281                 color: model.color
2282             }
2283         }
2284     }
2285 
2286 
2287     DelegateModel {
2288         id: subtitleDelegateModel
2289         model: subtitleModel
2290         delegate: SubTitle {
2291             subId: model.id
2292             selected: model.selected
2293             startFrame: model.startframe
2294             endFrame: model.endframe
2295             subtitle: model.subtitle
2296             isGrabbed: model.grabbed
2297         }
2298     }
2299 
2300     Connections {
2301         target: timeline
2302         function onFrameFormatChanged() {
2303             ruler.adjustFormat()
2304         }
2305 
2306         function onSelectionChanged() {
2307             if (dragProxy.draggedItem > -1 && !timeline.exists(dragProxy.draggedItem)) {
2308                 endDrag()
2309             }
2310         }
2311     }
2312 
2313     // This provides continuous scrolling at the left/right edges.
2314     Timer {
2315         id: scrollTimer
2316         interval: 80
2317         repeat: true
2318         triggeredOnStart: true
2319         property int horizontal: 0
2320         property int vertical: 0
2321         onTriggered: {
2322             if (vertical != 0) {
2323                 scrollView.contentY += vertical
2324                 if (scrollView.contentY <= 0) {
2325                     scrollView.contentY = 0
2326                     vertical = 0
2327                     stop()
2328                 } else {
2329                     if ((clipBeingMovedId == -1 && clipBeingDroppedId == -1 && !rubberSelect.visible)) {
2330                         vertical = 0
2331                         stop()
2332                     } else {
2333                         var maxScroll = trackHeaders.height - tracksArea.height + horZoomBar.height + ruler.height + subtitleTrack.height
2334                         if (scrollView.contentY > maxScroll) {
2335                             scrollView.contentY = Math.max(0, maxScroll)
2336                             vertical = 0
2337                             stop()
2338                         }
2339                     }
2340                 }
2341             }
2342             if (horizontal != 0) {
2343                 if (scrollView.contentX < -horizontal) {
2344                     horizontal = - scrollView.contentX
2345                     scrollView.contentX = 0
2346                 } else {
2347                     scrollView.contentX += horizontal
2348                 }
2349                 if (dragProxy.draggedItem > -1) {
2350                     dragProxy.x += horizontal
2351                     dragProxyArea.moveItem()
2352                 } else if (clipBeingDroppedId > -1) {
2353                     if (clipDropArea.containsDrag) {
2354                         clipDropArea.moveDrop(horizontal, vertical)
2355                     } else if (compoArea.containsDrag) {
2356                         compoArea.moveDrop(horizontal, vertical)
2357                     }
2358                 }
2359                 if (scrollView.contentX == 0 || (clipBeingMovedId == -1 && clipBeingDroppedId == -1 && !rubberSelect.visible)) {
2360                     if (root.subtitleMoving) {
2361                         root.subtitleItem.checkOffset(horizontal)
2362                     } else {
2363                         horizontal = 0
2364                         stop()
2365                     }
2366                 }
2367             }
2368             if (rubberSelect.visible) {
2369                 if (horizontal != 0) {
2370                     if (rubberSelect.x < rubberSelect.originX) {
2371                         if (horizontal < 0) {
2372                             // Expanding left
2373                             rubberSelect.x += horizontal
2374                             rubberSelect.width -= horizontal
2375                         } else if (horizontal < rubberSelect.width) {
2376                             // Expanding right
2377                             rubberSelect.x -= horizontal
2378                             rubberSelect.width -= horizontal
2379                         } else {
2380                             // Switching direction
2381                             rubberSelect.width = rubberSelect.x + rubberSelect.width + horizontal - rubberSelect.originX
2382                             rubberSelect.x = rubberSelect.originX
2383                         }
2384                     } else {
2385                         rubberSelect.x = rubberSelect.originX
2386                         rubberSelect.width += horizontal
2387                     }
2388                 }
2389                 if (vertical != 0) {
2390                     if (rubberSelect.y < rubberSelect.originY) {
2391                          if (vertical < 0) {
2392                             // Expanding up
2393                             rubberSelect.y += vertical
2394                             rubberSelect.height = rubberSelect.originY - rubberSelect.y
2395                         } else if (vertical < rubberSelect.height) {
2396                             // Expanding bottom
2397                             rubberSelect.y += vertical
2398                             rubberSelect.height = rubberSelect.originY - rubberSelect.y
2399                         } else {
2400                             // Switching direction
2401                             rubberSelect.height = rubberSelect.y + rubberSelect.height + horizontal - rubberSelect.originY
2402                             rubberSelect.y = rubberSelect.originY
2403                         }
2404                     } else {
2405                         rubberSelect.y = rubberSelect.originY
2406                         rubberSelect.height += vertical
2407                     }
2408                 }
2409             }
2410         }
2411     }
2412 }
2413