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