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