timeline.qml 84.1 KB
Newer Older
1
2
3
import QtQuick 2.11
import QtQml.Models 2.11
import QtQuick.Controls 2.4
4
import Kdenlive.Controls 1.0
5
6
7
8
9
10
11
import 'Timeline.js' as Logic

Rectangle {
    id: root
    objectName: "timelineview"
    SystemPalette { id: activePalette }
    color: activePalette.window
12
    property bool validMenu: false
13
    property color textColor: activePalette.text
14
    property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active
15

16
    signal clipClicked()
17
    signal mousePosChanged(int position)
18
    signal showClipMenu(int cid)
19
    signal showCompositionMenu()
20
    signal showTimelineMenu()
21
22
    signal showRulerMenu()
    signal showHeaderMenu()
23
    signal showTargetMenu(int ix)
24
25
    signal zoomIn(bool onMouse)
    signal zoomOut(bool onMouse)
26
    signal processingDrag(bool dragging)
Sashmita Raghav's avatar
Sashmita Raghav committed
27
    signal showSubtitleClipMenu()
28

29
30
    FontMetrics {
        id: fontMetrics
31
        font: miniFont
32
    }
33

34
35
36
37
    onDragInProgressChanged: {
        processingDrag(!root.dragInProgress)
    }

38
39
40
41
    function endBinDrag() {
        clipDropArea.processDrop()
    }

42
43
44
45
46
    function fitZoom() {
        return scrollView.width / (timeline.duration * 1.1)
    }

    function scrollPos() {
47
        return scrollView.contentX
48
49
50
    }

    function goToStart(pos) {
51
        scrollView.contentX = pos
52
53
    }

54
55
56
57
    function checkDeletion(itemId) {
        if (dragProxy.draggedItem == itemId) {
            endDrag()
        }
58
59
60
        if (itemId == mainItemId) {
            mainItemId = -1
        }
61
    }
62
63
64
65
    
    function getActiveTrackStreamPos() {
        return Logic.getTrackYFromId(timeline.activeTrack) + rulercontainer.height
    }
66

67
68
    function updatePalette() {
        root.color = activePalette.window
69
        root.textColor = activePalette.text
70
71
        playhead.fillColor = activePalette.windowText
        ruler.repaintRuler()
72
73
        // Disable caching fot track header icons
        root.paletteUnchanged = false
74
    }
75

76
    function moveSelectedTrack(offset) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
77
78
        var cTrack = Logic.getTrackIndexFromId(timeline.activeTrack)
        var newTrack = cTrack + offset
79
80
81
82
83
84
        var max = tracksRepeater.count;
        if (newTrack < 0) {
            newTrack = max - 1;
        } else if (newTrack >= max) {
            newTrack = 0;
        }
85
        timeline.activeTrack = tracksRepeater.itemAt(newTrack).trackInternalId
86
87
    }

88
    function zoomByWheel(wheel) {
89
        if (wheel.modifiers & Qt.AltModifier) {
90
            // Seek to next snap
91
92
93
94
95
96
            if (wheel.angleDelta.x > 0) {
                timeline.triggerAction('monitor_seek_snap_backward')
            } else {
                timeline.triggerAction('monitor_seek_snap_forward')
            }
        } else if (wheel.modifiers & Qt.ControlModifier) {
97
            root.wheelAccumulatedDelta += wheel.angleDelta.y;
98
            // Zoom
99
            if (root.wheelAccumulatedDelta >= defaultDeltasPerStep) {
100
                root.zoomIn(true);
101
102
                root.wheelAccumulatedDelta = 0;
            } else if (root.wheelAccumulatedDelta <= -defaultDeltasPerStep) {
103
                root.zoomOut(true);
104
                root.wheelAccumulatedDelta = 0;
105
            }
106
107
        } else if (wheel.modifiers & Qt.ShiftModifier) {
            // Vertical scroll
108
            var newScroll = Math.min(scrollView.contentY - wheel.angleDelta.y, trackHeaders.height - tracksArea.height + horScroll.height + ruler.height)
109
            scrollView.contentY = Math.max(newScroll, 0)
110
        } else {
111
            // Horizontal scroll
112
            var newScroll = Math.min(scrollView.contentX - wheel.angleDelta.y, timeline.fullDuration * root.timeScale - scrollView.width)
113
            scrollView.contentX = Math.max(newScroll, 0)
114
        }
115
        wheel.accepted = true
116
117
    }

118
    function continuousScrolling(x, y) {
119
        // This provides continuous scrolling at the left/right edges.
120
        if (x > scrollView.contentX + scrollView.width - root.baseUnit * 3) {
121
            scrollTimer.horizontal = root.baseUnit
122
123
            scrollTimer.start()
        } else if (x < 50) {
124
            scrollView.contentX = 0;
125
            scrollTimer.horizontal = 0
126
            scrollTimer.stop()
127
        } else if (x < scrollView.contentX + root.baseUnit * 3) {
128
            scrollTimer.horizontal = -root.baseUnit
129
130
            scrollTimer.start()
        } else {
131
132
            if (y > scrollView.contentY + scrollView.height + ruler.height - root.baseUnit) {
                scrollTimer.vertical = root.baseUnit
133
                scrollTimer.horizontal = 0
134
                scrollTimer.start()
135
136
            } else if (scrollView.contentY > 0 && (y - (scrollView.contentY + ruler.height ) < root.baseUnit)) {
                scrollTimer.vertical = -root.baseUnit
137
                scrollTimer.horizontal = 0
138
139
                scrollTimer.start()
            } else {
140
                console.log('TRIGGERING VERTICAL STOP: ', y, ', SCROLL: ',scrollView.contentY, 'SCH:', scrollView.height)
141
142
143
144
                scrollTimer.vertical = 0
                scrollTimer.horizontal = 0
                scrollTimer.stop()
            }
145
146
        }
    }
147
148
149
    function getTrackYFromId(a_track) {
        return Logic.getTrackYFromId(a_track)
    }
150

151
152
153
154
    function getTrackYFromMltIndex(a_track) {
        return Logic.getTrackYFromMltIndex(a_track)
    }

155
    function getMousePos() {
156
157
158
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            return (dragProxy.masterObject.x + dragProxy.masterObject.mouseXPos) / timeline.scaleFactor
        }
159
160
161
162
163
        if (tracksArea.containsMouse) {
            return (scrollView.contentX + tracksArea.mouseX) / timeline.scaleFactor
        } else {
            return -1;
        }
164
    }
165
166
167
168
169
170
171
172
173
174
    function getMouseX() {
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            return (dragProxy.masterObject.x + dragProxy.masterObject.mouseXPos) - scrollView.contentX
        }
        if (tracksArea.containsMouse) {
            return tracksArea.mouseX
        } else {
            return -1;
        }
    }
175

176
    function getScrollPos() {
177
        return scrollView.contentX
178
179
180
    }

    function setScrollPos(pos) {
181
        return scrollView.contentX = pos
182
183
    }

184
185
186
187
    function getCopiedItemId() {
        return copiedClip
    }

188
    function getMouseTrack() {
189
190
191
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            return dragProxy.masterObject.trackId
        }
192
        return Logic.getTrackIdFromPos(tracksArea.mouseY - ruler.height + scrollView.contentY - subtitleTrack.height)
193
194
    }

195
196
197
    function getTrackColor(audio, header) {
        var col = activePalette.alternateBase
        if (audio) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
198
            col = Qt.tint(col, "#06FF00CC")
199
200
        }
        if (header) {
201
            col = Qt.darker(col, 1.05)
202
        }
203
        return col
204
    }
205
206
207
208
    
    function centerViewOnCursor() {
        scrollView.contentX = Math.max(0, root.consumerPosition * timeline.scaleFactor - (scrollView.width / 2))
    }
209

210
211
212
213
214
    function clearDropData() {
        clipBeingDroppedId = -1
        droppedPosition = -1
        droppedTrack = -1
        scrollTimer.running = false
215
        scrollTimer.stop()
216
    }
217

218
    function isDragging() {
219
        return dragInProgress
220
    }
221

222
    function initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) {
223
        dragProxy.x = itemObject.modelStart * timeScale
224
        dragProxy.y = itemCoord.y
225
        dragProxy.width = itemObject.clipDuration * timeScale
226
227
228
229
230
231
        dragProxy.height = itemCoord.height
        dragProxy.masterObject = itemObject
        dragProxy.draggedItem = itemId
        dragProxy.sourceTrack = itemTrack
        dragProxy.sourceFrame = itemPos
        dragProxy.isComposition = isComposition
232
        dragProxy.verticalOffset = isComposition ? itemObject.displayHeight : 0
233
234
235
236
237
238
239
    }
    function endDrag() {
        dragProxy.draggedItem = -1
        dragProxy.x = 0
        dragProxy.y = 0
        dragProxy.width = 0
        dragProxy.height = 0
240
        dragProxy.verticalOffset = 0
241
242
    }

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    function getItemAtPos(tk, posx, isComposition) {
        var track = Logic.getTrackById(tk)
        var container = track.children[0]
        var tentativeClip = undefined
        for (var i = 0 ; i < container.children.length; i++) {
            if (container.children[i].children.length == 0 || container.children[i].children[0].children.length == 0) {
                continue
            }
            tentativeClip = container.children[i].children[0].childAt(posx, 1)
            if (tentativeClip && tentativeClip.clipId && (tentativeClip.isComposition == isComposition)) {
                break
            }
        }
        return tentativeClip
    }
258
259
260
261
262
263
    Keys.onDownPressed: {
        root.moveSelectedTrack(1)
    }
    Keys.onUpPressed: {
        root.moveSelectedTrack(-1)
    }
264

265
    property int activeTool: 0
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
266
    property real baseUnit: fontMetrics.font.pixelSize
267
    property real fontUnit: fontMetrics.font.pointSize
268
269
270
    property int collapsedHeight: Math.max(28, baseUnit * 1.8)
    property int minHeaderWidth: 6 * collapsedHeight
    property int headerWidth: Math.max(minHeaderWidth, timeline.headerWidth())
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
271
272
    property color selectedTrackColor: Qt.rgba(activePalette.highlight.r, activePalette.highlight.g, activePalette.highlight.b, 0.2)
    property color frameColor: Qt.rgba(activePalette.shadow.r, activePalette.shadow.g, activePalette.shadow.b, 0.3)
273
    property bool autoScrolling: timeline.autoScroll
274
    property int duration: timeline.duration
275
276
    property color audioColor: timeline.audioColor
    property color videoColor: timeline.videoColor
277
    property color titleColor: timeline.titleColor
Sashmita Raghav's avatar
Sashmita Raghav committed
278
    property color imageColor: timeline.imageColor
279
    property color slideshowColor: timeline.slideshowColor
280
    property color lockedColor: timeline.lockedColor
Vincent Pinon's avatar
Vincent Pinon committed
281
    property color selectionColor: timeline.selectionColor
282
    property color groupColor: timeline.groupColor
283
284
    property color thumbColor1: timeline.thumbColor1
    property color thumbColor2: timeline.thumbColor2
285
    property int mainItemId: -1
286
    property int mainFrame: -1
Nicolas Carion's avatar
Nicolas Carion committed
287
    property int clipBeingDroppedId: -1
288
    property string clipBeingDroppedData
289
290
    property int droppedPosition: -1
    property int droppedTrack: -1
291
    property int clipBeingMovedId: -1
292
    property int consumerPosition: proxy.position
293
    property int spacerGroup: -1
294
    property int spacerTrack: -1
295
    property int spacerFrame: -1
296
    property int finalSpacerFrame: -1
297
    property int spacerClickFrame: -1
298
    property real timeScale: timeline.scaleFactor
299
    property int snapping: (timeline.snap && (timeline.scaleFactor < 2 * baseUnit)) ? Math.floor(baseUnit / (timeline.scaleFactor > 3 ? timeline.scaleFactor / 2 : timeline.scaleFactor)) : -1
300
    property var timelineSelection: timeline.selection
301
    property int selectedMix: timeline.selectedMix
302
    property int trackHeight
303
    property int copiedClip: -1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
304
    property int zoomOnMouse: -1
305
    property int viewActiveTrack: timeline.activeTrack
306
307
    property int wheelAccumulatedDelta: 0
    readonly property int defaultDeltasPerStep: 120
308
    property bool seekingFinished : proxy.seekFinished
309
    property int scrollMin: scrollView.contentX / timeline.scaleFactor
310
    property int scrollMax: scrollMin + scrollView.contentItem.width / timeline.scaleFactor
311
    property double dar: 16/9
312
    property bool paletteUnchanged: true
313
    property int maxLabelWidth: 20 * root.baseUnit * Math.sqrt(root.timeScale)
314
    property bool showSubtitles: false
315
316
    property bool subtitlesLocked: timeline.subtitlesLocked
    property bool subtitlesDisabled: timeline.subtitlesDisabled
317
    property int trackTagWidth: fontMetrics.boundingRect("M").width
318
319
320
321

    onSeekingFinishedChanged : {
        playhead.opacity = seekingFinished ? 1 : 0.5
    }
322
323
324
325
    
    onShowSubtitlesChanged: {
        subtitleTrack.height = showSubtitles? root.baseUnit * 5 : 0
    }
326

327
    //onCurrentTrackChanged: timeline.selection = []
328
    onTimeScaleChanged: {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
329
        if (root.zoomOnMouse >= 0) {
330
            scrollView.contentX = Math.max(0, root.zoomOnMouse * timeline.scaleFactor - getMouseX())
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
331
332
            root.zoomOnMouse = -1
        } else {
333
            scrollView.contentX = Math.max(0, root.consumerPosition * timeline.scaleFactor - (scrollView.width / 2))
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
334
        }
335
        //root.snapping = timeline.snap ? 10 / Math.sqrt(root.timeScale) : -1
336
        ruler.adjustStepSize()
337
338
339
340
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            // update dragged item pos
            dragProxy.masterObject.updateDrag()
        }
341
    }
342

343
    onConsumerPositionChanged: {
344
        if (root.autoScrolling) Logic.scrollIfNeeded()
345
346
    }

347
348
    onViewActiveTrackChanged: {
        var tk = Logic.getTrackById(timeline.activeTrack)
349
350
351
352
        if (tk.y + subtitleTrack.height < scrollView.contentY) {
            scrollView.contentY = Math.max(0, tk.y + subtitleTrack.height)
        } else if (tk.y + tk.height + subtitleTrack.height > scrollView.contentY + scrollView.height) {
            var newY = Math.min(trackHeaders.height + subtitleTrack.height - scrollView.height + scrollView.ScrollBar.horizontal.height, tk.y + tk.height - scrollView.height + subtitleTrack.height)
353
354
355
            if (newY >= 0) {
                scrollView.contentY = newY
            }
356
357
358
        }
    }

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    onActiveToolChanged: {
        if (root.activeTool == 2) {
            // Spacer activated
            endDrag()
        } else if (root.activeTool == 0) {
            var tk = getMouseTrack()
            if (tk < 0) {
                return
            }
            var pos = getMousePos() * timeline.scaleFactor
            var sourceTrack = Logic.getTrackById(tk)
            var allowComposition = tracksArea.mouseY- sourceTrack.y > sourceTrack.height / 2
            var tentativeItem = undefined
            if (allowComposition) {
                tentativeItem = getItemAtPos(tk, pos, true)
            }
            if (!tentativeItem) {
                tentativeItem = getItemAtPos(tk, pos, false)
            }
            if (tentativeItem) {
                tentativeItem.updateDrag()
            }
        }
    }

384
385
386
387
388
389
390
    DropArea { //Drop area for compositions
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
        keys: 'kdenlive/composition'
        onEntered: {
391
            if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
392
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY - subtitleTrack.height)
393
                var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
394
                droppedPosition = frame
395
                if (track >= 0 && !controller.isAudioTrack(track)) {
396
397
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
                    clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData, false)
398
                    continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
399
400
401
402
403
404
                    drag.acceptProposedAction()
                } else {
                    drag.accepted = false
                }
            }
        }
405
406
        onPositionChanged: {
            if (clipBeingMovedId == -1) {
407
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY - subtitleTrack.height)
408
                if (track !=-1) {
409
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
410
                    if (clipBeingDroppedId >= 0){
411
412
413
414
                        if (controller.isAudioTrack(track)) {
                            // Don't allow moving composition to an audio track
                            track = controller.getCompositionTrackId(clipBeingDroppedId)
                        }
415
                        controller.suggestCompositionMove(clipBeingDroppedId, track, frame, root.consumerPosition, root.snapping)
416
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
417
                    } else if (!controller.isAudioTrack(track)) {
418
                        frame = controller.suggestSnapPoint(frame, root.snapping)
419
420
                        clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
                        clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData , false)
421
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
422
                    }
423
424
425
                }
            }
        }
426
        onExited:{
427
428
            if (clipBeingDroppedId != -1) {
                // If we exit, remove composition
429
                controller.requestItemDeletion(clipBeingDroppedId, false)
430
                clearDropData()
431
432
            }
        }
433
434
435
436
437
        onDropped: {
            if (clipBeingDroppedId != -1) {
                var frame = controller.getCompositionPosition(clipBeingDroppedId)
                var track = controller.getCompositionTrackId(clipBeingDroppedId)
                // we simulate insertion at the final position so that stored undo has correct value
438
                controller.requestItemDeletion(clipBeingDroppedId, false)
439
                timeline.insertNewComposition(track, frame, clipBeingDroppedData, true)
440
            }
441
            clearDropData()
442
        }
443
    }
444
445
446
    DropArea {
        //Drop area for bin/clips
        id: clipDropArea
447
448
449
450
451
452
453
454
455
        /** @brief local helper function to handle the insertion of multiple dragged items */
        function insertAndMaybeGroup(track, frame, droppedData) {
            var binIds = droppedData.split(";")
            if (binIds.length == 0) {
                return -1
            }

            var id = -1
            if (binIds.length == 1) {
456
                id = timeline.insertClip(timeline.activeTrack, frame, clipBeingDroppedData, false, true, false)
457
            } else {
458
                var ids = timeline.insertClips(timeline.activeTrack, frame, binIds, false, true, false)
459
460
461

                // if the clip insertion succeeded, request the clips to be grouped
                if (ids.length > 0) {
462
                    timeline.selectItems(ids)
463
464
465
466
467
468
                    id = ids[0]
                }
            }
            return id
        }

469
470
        property int fakeFrame: -1
        property int fakeTrack: -1
471
472
473
474
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
475
        keys: 'kdenlive/producerslist'
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
        function processDrop()
        {
            // Process the drop event, useful if drop event happens outside of drop area
            if (clipBeingDroppedId != -1) {
                var frame = controller.getClipPosition(clipBeingDroppedId)
                var track = controller.getClipTrackId(clipBeingDroppedId)
                if (!controller.normalEdit()) {
                    frame = fakeFrame
                    track = fakeTrack
                }
                /* We simulate insertion at the final position so that stored undo has correct value
                 * NOTE: even if dropping multiple clips, requesting the deletion of the first one is
                 * enough as internally it will request the group deletion
                 */
                controller.requestItemDeletion(clipBeingDroppedId, false)

                var binIds = clipBeingDroppedData.split(";")
                if (binIds.length == 1) {
                    if (controller.normalEdit()) {
                        timeline.insertClip(track, frame, clipBeingDroppedData, true, true, false)
                    } else {
                        timeline.insertClipZone(clipBeingDroppedData, track, frame)
                    }
                } else {
                    if (controller.normalEdit()) {
                        timeline.insertClips(track, frame, binIds, true, true)
                    } else {
                        // TODO
                        console.log('multiple clips insert/overwrite not supported yet')
                    }
                }
                fakeTrack = -1
                fakeFrame = -1
            }
            clearDropData()
        }
512
        onEntered: {
513
            if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
514
                //var track = Logic.getTrackIdFromPos(drag.y)
515
516
517
518
519
                var yOffset = 0
                if (root.showSubtitles) {
                    yOffset = subtitleTrack.height
                }
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
520
                if (track >= 0  && track < tracksRepeater.count) {
521
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
522
                    droppedPosition = frame
523
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
Nicolas Carion's avatar
Nicolas Carion committed
524
                    //drag.acceptProposedAction()
525
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/producerslist')
526
527
528
529
                    if (controller.normalEdit()) {
                        clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, frame, clipBeingDroppedData)
                    } else {
                        // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
530
531
                        frame = controller.adjustFrame(frame, timeline.activeTrack)
                        clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, frame, clipBeingDroppedData)
532
                        if (clipBeingDroppedId > -1) {
533
534
535
                            var moveData = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, root.snapping)
                            fakeFrame = moveData[0]
                            fakeTrack = moveData[1]
536
537
538
                        } else {
                            drag.accepted = false
                        }
539
                    }
540
                    continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
541
542
543
544
                } else {
                    drag.accepted = false
                }
            }
545
        }
Nicolas Carion's avatar
Nicolas Carion committed
546
        onExited:{
547
548
            if (clipBeingDroppedId != -1 && drag.y < drag.x) {
                // If we exit on top, remove clip
549
                controller.requestItemDeletion(clipBeingDroppedId, false)
550
551
552
                clearDropData()
            } else {
                // Clip is dropped
Nicolas Carion's avatar
Nicolas Carion committed
553
554
            }
        }
555
        onPositionChanged: {
556
            if (clipBeingMovedId == -1) {
557
558
559
560
561
                var yOffset = 0
                if (root.showSubtitles) {
                    yOffset = subtitleTrack.height
                }
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
562
                if (track >= 0  && track < tracksRepeater.count) {
563
564
                    //timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
                    var targetTrack = tracksRepeater.itemAt(track).trackInternalId
565
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
566
567
568
569
570
                    if (clipBeingDroppedId > -1) {
                        var moveData = controller.suggestClipMove(clipBeingDroppedId, targetTrack, frame, root.consumerPosition, root.snapping)
                        fakeFrame = moveData[0]
                        fakeTrack = moveData[1]
                        timeline.activeTrack = fakeTrack
571
                        //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
572
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
573
                    } else {
574
                        frame = controller.suggestSnapPoint(frame, root.snapping)
575
                        if (controller.normalEdit()) {
576
577
                            timeline.activeTrack = targetTrack
                            clipBeingDroppedId = insertAndMaybeGroup(targetTrack, frame, drag.getDataAsString('kdenlive/producerslist'), false, true)
578
579
                        } else {
                            // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
580
581
582
583
584
585
586
                            clipBeingDroppedId = insertAndMaybeGroup(targetTrack, timeline.fullDuration, clipBeingDroppedData)
                            if (clipBeingDroppedId > -1) {
                                var moveData = controller.suggestClipMove(clipBeingDroppedId, targetTrack, frame, root.consumerPosition, root.snapping)
                                fakeFrame = moveData[0]
                                fakeTrack = moveData[1]
                                timeline.activeTrack = fakeTrack
                            }
587
                        }
588
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
589
                    }
590
                }
Nicolas Carion's avatar
Nicolas Carion committed
591
            }
592
593
        }
        onDropped: {
594
            processDrop()
595
596
        }
    }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
    DropArea { //Drop area for urls (direct drop from file manager)
        /** @brief local helper function to handle the insertion of multiple dragged items */
        property int fakeFrame: -1
        property int fakeTrack: -1
        property var droppedUrls: []
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
        keys: 'text/uri-list'
        onEntered: {
            drag.accepted = true
            droppedUrls.length = 0
            for(var i in drag.urls){
                var url = drag.urls[i]
                droppedUrls.push(Qt.resolvedUrl(url))
            }
        }
        onExited:{
            if (clipBeingDroppedId != -1) {
                controller.requestItemDeletion(clipBeingDroppedId, false)
            }
            clearDropData()
        }
        onPositionChanged: {
            if (clipBeingMovedId == -1) {
623
624
625
626
627
                var yOffset = 0
                if (root.showSubtitles) {
                    yOffset = subtitleTrack.height
                }
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY - yOffset)
628
629
                if (track >= 0  && track < tracksRepeater.count) {
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
630
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
631
632
                    if (clipBeingDroppedId >= 0) {
                        //fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, Math.floor(root.snapping))
633
                        //fakeTrack = timeline.activeTrack
634
                        //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
635
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
636
                    } else {
637
                        frame = controller.suggestSnapPoint(frame, root.snapping)
638
639
640
641
642
643
644
645
                        if (controller.normalEdit()) {
                            //clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, frame, drag.getDataAsString('kdenlive/producerslist'), false, true)
                        } else {
                            // we want insert/overwrite mode, make a fake insert at end of timeline, then move to position
                            //clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, timeline.fullDuration, clipBeingDroppedData)
                            //fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, Math.floor(root.snapping))
                            fakeTrack = timeline.activeTrack
                        }
646
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
647
648
649
650
651
                    }
                }
            }
        }
        onDropped: {
652
            var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
            var track = timeline.activeTrack
            //var binIds = clipBeingDroppedData.split(";")
            //if (binIds.length == 1) {
                if (controller.normalEdit()) {
                    timeline.urlDropped(droppedUrls, frame, track)
                } else {
                    //timeline.insertClipZone(clipBeingDroppedData, track, frame)
                }
            /*} else {
                if (controller.normalEdit()) {
                    timeline.insertClips(track, frame, binIds, true, true)
                } else {
                    // TODO
                    console.log('multiple clips insert/overwrite not supported yet')
                }
            }*/
            clearDropData()
        }
    }
672

673
674
    Row {
        Column {
675
            id: headerContainer
676
            width: headerWidth
677
            z: 1
678
            Item {
679
                // Padding between toolbar and track headers.
680
                width: parent.width
681
                height: ruler.height
682
                Button {
683
                    text: parent.width > metrics.boundingRect.width * 1.4 ? metrics.text : i18nc("Initial for Master", "M")
684
685
686
                    anchors.fill: parent
                    anchors.leftMargin: 2
                    anchors.rightMargin: 2
687
688
689
690
                    ToolTip.delay: 1000
                    ToolTip.timeout: 5000
                    ToolTip.visible: hovered
                    ToolTip.text: i18n("Show master effects")
691
692
693
694
                    TextMetrics {
                        id: metrics
                        text: i18n("Master")
                    }
695
696
697
698
                    onClicked: {
                        timeline.showMasterEffects()
                    }
                }
699
700
701
            }
            Flickable {
                // Non-slider scroll area for the track headers.
702
                id: headerFlick
703
                contentY: scrollView.contentY
704
705
706
                width: parent.width
                y: ruler.height
                height: root.height - ruler.height
707
                interactive: false
708
                clip: true
709

710
711
712
713
714
                MouseArea {
                    width: trackHeaders.width
                    height: trackHeaders.height
                    acceptedButtons: Qt.NoButton
                    onWheel: {
715
716
                        var newScroll = Math.min(scrollView.contentY - wheel.angleDelta.y, height - tracksArea.height + scrollView.ScrollBar.horizontal.height + ruler.height)
                        scrollView.contentY = Math.max(newScroll, 0)
717
718
                    }
                }
719
720
                Item {
                    id: subtitleTrackHeader
721
                    width: trackHeaders.width
722
723
                    height: subtitleTrack.height
                    property bool collapsed: subtitleTrack.height == root.collapsedHeight
724
                    visible: height > 0
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
                    ToolButton {
                        id: expandSubButton
                        focusPolicy: Qt.NoFocus
                        property var modifier: 0
                        anchors.left: parent.left
                        anchors.leftMargin: 2 * root.trackTagWidth
                        width: root.collapsedHeight
                        height: root.collapsedHeight   
                        contentItem: Item {
                            Image {
                                source: subtitleTrackHeader.collapsed ? "image://icon/go-next" : "image://icon/go-down"
                                anchors.centerIn: parent
                                width: root.collapsedHeight - 4
                                height: root.collapsedHeight - 4
                                cache: root.paletteUnchanged
                            }
                        }
                        onClicked: {
                            if (subtitleTrack.height > root.collapsedHeight) {
                                subtitleTrack.height = root.collapsedHeight
                            } else {
                                subtitleTrack.height = 5 * root.baseUnit
                            }
                        }
                    }
                    Label {
751
                        id: subLabel
752
753
754
755
                        anchors.left: expandSubButton.left
                        anchors.top: expandSubButton.bottom
                        font: miniFont
                        text: i18n("Subtitles")
756
                        visible: (subtitleTrackHeader.height > root.collapsedHeight + subLabel.height)
757
                    }
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
                    
                    Row {
                        id: subButtonsRow
                        width: childrenRect.width
                        x: Math.max(2 * root.collapsedHeight + 2, parent.width - width - 4)
                        spacing: 0
                        ToolButton {
                            id: muteButton
                            focusPolicy: Qt.NoFocus
                            contentItem: Item {
                                Image {
                                    source: root.subtitlesDisabled ? "image://icon/view-hidden" : "image://icon/view-visible"
                                    anchors.centerIn: parent
                                    width: root.collapsedHeight - 4
                                    height: root.collapsedHeight - 4
                                    cache: root.paletteUnchanged
                                }
                            }
                            width: root.collapsedHeight
                            height: root.collapsedHeight
                            onClicked: timeline.triggerAction('disable_subtitle')
                            ToolTip {
                                visible: muteButton.hovered
                                font: miniFont
                                delay: 1500
                                timeout: 5000
                                background: Rectangle {
                                    color: activePalette.alternateBase
                                    border.color: activePalette.light
                                }
                                contentItem: Label {
                                    color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
790
                                    text: root.subtitlesDisabled? i18n("Show") : i18n("Hide")
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
                                }
                            }
                        }

                        ToolButton {
                            id: lockButton
                            width: root.collapsedHeight
                            height: root.collapsedHeight
                            focusPolicy: Qt.NoFocus
                            contentItem: Item {
                                Image {
                                    source: root.subtitlesLocked ? "image://icon/kdenlive-lock" : "image://icon/kdenlive-unlock"
                                    anchors.centerIn: parent
                                    width: root.collapsedHeight - 4
                                    height: root.collapsedHeight - 4
                                    cache: root.paletteUnchanged
                                }
                            }
                            onClicked: timeline.triggerAction('lock_subtitle')
                            ToolTip {
                                visible: lockButton.hovered
                                font: miniFont
                                delay: 1500
                                timeout: 5000
                                background: Rectangle {
                                    color: activePalette.alternateBase
                                    border.color: activePalette.light
                                }
                                contentItem: Label {
                                    color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
821
                                    text: root.subtitlesLocked? i18n("Unlock track") : i18n("Lock track")
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
                                }
                            }
                            SequentialAnimation {
                                id: flashLock
                                loops: 1
                                ScaleAnimator {
                                    target: lockButton
                                    from: 1
                                    to: 1.6
                                    duration: 200
                                }
                                ScaleAnimator {
                                    target: lockButton
                                    from: 1.6
                                    to: 1
                                    duration: 200
                                }
                            }
                        }
                    }
842
                }
843
844
                Column {
                    id: trackHeaders
845
                    y: subtitleTrack.height
846
                    spacing: 0
847
848
849
850
851
                    Repeater {
                        id: trackHeaderRepeater
                        model: multitrack
                        TrackHead {
                            trackName: model.name
852
                            thumbsFormat: model.thumbsFormat
853
                            trackTag: model.trackTag
854
                            isDisabled: model.disabled
855
856
                            isComposite: model.composite
                            isLocked: model.locked
857
                            isActive: model.trackActive
858
                            isAudio: model.audio
859
                            showAudioRecord: model.audioRecord
860
861
                            effectNames: model.effectNames
                            isStackEnabled: model.isStackEnabled
862
                            width: headerWidth
863
                            current: item === timeline.activeTrack
864
                            trackId: item
865
                            height: model.trackHeight
866
                            onIsLockedChanged: tracksRepeater.itemAt(index).isLocked = isLocked
867
                            collapsed: height <= root.collapsedHeight
868
869
870
                            Component.onCompleted: {
                                root.collapsedHeight = collapsedHeight
                            }
871
872
                            onHeightChanged: {
                                collapsed = height <= root.collapsedHeight
873
                            }
874
875
876
877
878
879
                        }
                    }
                }
                Column {
                    id: trackHeadersResizer
                    spacing: 0
880
                    width: root.baseUnit / 2
881
882
883
                    Rectangle {
                        id: resizer
                        height: trackHeaders.height
884
                        width: root.baseUnit / 2
885
886
887
888
889
890
891
892
893
894
895
896
897
                        x: root.headerWidth - 2
                        color: 'red'
                        opacity: 0
                        Drag.active: headerMouseArea.drag.active
                        Drag.proposedAction: Qt.MoveAction

                        MouseArea {
                            id: headerMouseArea
                            anchors.fill: parent
                            hoverEnabled: true
                            cursorShape: Qt.SizeHorCursor
                            drag.target: parent
                            drag.axis: Drag.XAxis
898
                            drag.minimumX: root.minHeaderWidth
899
900
901
902
903
                            property double startX
                            property double originalX
                            drag.smoothed: false

                            onPressed: {
904
                                root.autoScrolling = false
905
906
                            }
                            onReleased: {
907
                                root.autoScrolling = timeline.autoScroll
908
909
910
911
912
913
914
                                parent.opacity = 0
                            }
                            onEntered: parent.opacity = 0.5
                            onExited: parent.opacity = 0
                            onPositionChanged: {
                                if (mouse.buttons === Qt.LeftButton) {
                                    parent.opacity = 0.5
915
                                    headerWidth = Math.max( root.minHeaderWidth, mapToItem(null, x, y).x + 2)
916
917
918
919
                                    timeline.setHeaderWidth(headerWidth)
                                }
                            }
                        }
920
921
922
923
924
925
                    }
                }
            }
        }
        MouseArea {
            id: tracksArea
926
927
            property real clickX
            property real clickY
928
            width: root.width - root.headerWidth
929
            height: root.height
930
            x: root.headerWidth
931
            property bool shiftPress: false
932
933
            // This provides continuous scrubbing and scimming at the left/right edges.
            hoverEnabled: true
934
935
            preventStealing: true
            acceptedButtons: Qt.AllButtons
936
            cursorShape: root.activeTool === 0 ? Qt.ArrowCursor : root.activeTool === 1 ? Qt.IBeamCursor : Qt.SplitHCursor
937
            onWheel: {
938
939
940
941
942
943
944
945
946
                if (wheel.modifiers & Qt.AltModifier) {
                    // Alt + wheel = seek to next snap point
                    if (wheel.angleDelta.x > 0) {
                        timeline.triggerAction('monitor_seek_snap_backward')
                    } else {
                        timeline.triggerAction('monitor_seek_snap_forward')
                    }
                } else {
                    var delta = wheel.modifiers & Qt.ShiftModifier ? timeline.fps() : 1
947
                    proxy.position = wheel.angleDelta.y > 0 ? <