timeline.qml 71.9 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
24
    signal zoomIn(bool onMouse)
    signal zoomOut(bool onMouse)
25
    signal processingDrag(bool dragging)
26

27
28
    FontMetrics {
        id: fontMetrics
29
        font: miniFont
30
    }
31

32
33
34
35
    onDragInProgressChanged: {
        processingDrag(!root.dragInProgress)
    }

36
37
38
39
    function endBinDrag() {
        clipDropArea.processDrop()
    }

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

    function scrollPos() {
45
        return scrollView.contentX
46
47
48
    }

    function goToStart(pos) {
49
        scrollView.contentX = pos
50
51
    }

52
53
54
55
    function checkDeletion(itemId) {
        if (dragProxy.draggedItem == itemId) {
            endDrag()
        }
56
57
58
        if (itemId == mainItemId) {
            mainItemId = -1
        }
59
60
    }

61
62
    function updatePalette() {
        root.color = activePalette.window
63
        root.textColor = activePalette.text
64
65
        playhead.fillColor = activePalette.windowText
        ruler.repaintRuler()
66
67
        // Disable caching fot track header icons
        root.paletteUnchanged = false
68
    }
69

70
    function moveSelectedTrack(offset) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
71
72
        var cTrack = Logic.getTrackIndexFromId(timeline.activeTrack)
        var newTrack = cTrack + offset
73
74
75
76
77
78
        var max = tracksRepeater.count;
        if (newTrack < 0) {
            newTrack = max - 1;
        } else if (newTrack >= max) {
            newTrack = 0;
        }
79
        timeline.activeTrack = tracksRepeater.itemAt(newTrack).trackInternalId
80
81
    }

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

112
    function continuousScrolling(x, y) {
113
        // This provides continuous scrolling at the left/right edges.
114
        if (x > scrollView.contentX + scrollView.width - root.baseUnit * 3) {
115
            scrollTimer.horizontal = 10
116
117
            scrollTimer.start()
        } else if (x < 50) {
118
            scrollView.contentX = 0;
119
            scrollTimer.horizontal = 0
120
            scrollTimer.stop()
121
        } else if (x < scrollView.contentX + root.baseUnit * 3) {
122
            scrollTimer.horizontal = -10
123
124
            scrollTimer.start()
        } else {
125
126
            if (y > scrollView.contentY + scrollView.height + ruler.height - root.baseUnit * 3) {
                scrollTimer.vertical = root.baseUnit / 3
127
                scrollTimer.horizontal = 0
128
                scrollTimer.start()
129
130
            } else if (y - scrollView.contentY - ruler.height < root.baseUnit * 3) {
                scrollTimer.vertical = -root.baseUnit / 3
131
                scrollTimer.horizontal = 0
132
133
134
135
136
137
                scrollTimer.start()
            } else {
                scrollTimer.vertical = 0
                scrollTimer.horizontal = 0
                scrollTimer.stop()
            }
138
139
        }
    }
140
141
142
    function getTrackYFromId(a_track) {
        return Logic.getTrackYFromId(a_track)
    }
143

144
145
146
147
    function getTrackYFromMltIndex(a_track) {
        return Logic.getTrackYFromMltIndex(a_track)
    }

148
149
150
151
    function getTracksCount() {
        return Logic.getTracksList()
    }

152
    function getMousePos() {
153
154
155
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            return (dragProxy.masterObject.x + dragProxy.masterObject.mouseXPos) / timeline.scaleFactor
        }
156
157
158
159
160
        if (tracksArea.containsMouse) {
            return (scrollView.contentX + tracksArea.mouseX) / timeline.scaleFactor
        } else {
            return -1;
        }
161
    }
162
163
164
165
166
167
168
169
170
171
    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;
        }
    }
172

173
    function getScrollPos() {
174
        return scrollView.contentX
175
176
177
    }

    function setScrollPos(pos) {
178
        return scrollView.contentX = pos
179
180
    }

181
182
183
184
    function getCopiedItemId() {
        return copiedClip
    }

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

192
193
194
    function getTrackColor(audio, header) {
        var col = activePalette.alternateBase
        if (audio) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
195
            col = Qt.tint(col, "#06FF00CC")
196
197
        }
        if (header) {
198
            col = Qt.darker(col, 1.05)
199
        }
200
        return col
201
202
    }

203
204
205
206
207
    function clearDropData() {
        clipBeingDroppedId = -1
        droppedPosition = -1
        droppedTrack = -1
        scrollTimer.running = false
208
        scrollTimer.stop()
209
    }
210

211
    function isDragging() {
212
        return dragInProgress
213
    }
214

215
    function initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) {
216
        dragProxy.x = itemObject.modelStart * timeScale
217
        dragProxy.y = itemCoord.y
218
        dragProxy.width = itemObject.clipDuration * timeScale
219
220
221
222
223
224
        dragProxy.height = itemCoord.height
        dragProxy.masterObject = itemObject
        dragProxy.draggedItem = itemId
        dragProxy.sourceTrack = itemTrack
        dragProxy.sourceFrame = itemPos
        dragProxy.isComposition = isComposition
225
        dragProxy.verticalOffset = isComposition ? itemObject.displayHeight : 0
226
227
228
229
230
231
232
    }
    function endDrag() {
        dragProxy.draggedItem = -1
        dragProxy.x = 0
        dragProxy.y = 0
        dragProxy.width = 0
        dragProxy.height = 0
233
        dragProxy.verticalOffset = 0
234
235
    }

236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
    function getItemAtPos(tk, posx, isComposition) {
        var track = Logic.getTrackById(tk)
        var container = track.children[0]
        var tentativeClip = undefined
        //console.log('TESTING ITMES OK TK: ', tk, ', POS: ', posx, ', CHILREN: ', container.children.length, ', COMPO: ', isComposition)
        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)) {
                //console.log('found item with id: ', tentativeClip.clipId, ' IS COMPO: ', tentativeClip.isComposition)
                break
            }
        }
        return tentativeClip
    }
253
254
255
256
257
258
    Keys.onDownPressed: {
        root.moveSelectedTrack(1)
    }
    Keys.onUpPressed: {
        root.moveSelectedTrack(-1)
    }
259

260
    property int headerWidth: timeline.headerWidth()
261
    property int activeTool: 0
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
262
    property real baseUnit: fontMetrics.font.pixelSize
263
    property real fontUnit: fontMetrics.font.pointSize
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
264
265
    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)
266
    property bool autoScrolling: timeline.autoScroll
267
    property int duration: timeline.duration
268
269
    property color audioColor: timeline.audioColor
    property color videoColor: timeline.videoColor
270
    property color titleColor: timeline.titleColor
Sashmita Raghav's avatar
Sashmita Raghav committed
271
    property color imageColor: timeline.imageColor
272
    property color slideshowColor: timeline.slideshowColor
273
    property color lockedColor: timeline.lockedColor
Vincent Pinon's avatar
Vincent Pinon committed
274
    property color selectionColor: timeline.selectionColor
275
    property color groupColor: timeline.groupColor
276
    property int mainItemId: -1
277
    property int mainFrame: 0
Nicolas Carion's avatar
Nicolas Carion committed
278
    property int clipBeingDroppedId: -1
279
    property string clipBeingDroppedData
280
281
    property int droppedPosition: -1
    property int droppedTrack: -1
282
    property int clipBeingMovedId: -1
283
    property int consumerPosition: proxy.position
284
    property int spacerGroup: -1
285
286
    property int spacerFrame: -1
    property int spacerClickFrame: -1
287
    property real timeScale: timeline.scaleFactor
288
    property int snapping: (timeline.snap && (timeline.scaleFactor < 2 * baseUnit)) ? Math.floor(baseUnit / (timeline.scaleFactor > 3 ? timeline.scaleFactor / 2 : timeline.scaleFactor)) : -1
289
    property var timelineSelection: timeline.selection
290
    property int trackHeight
291
    property int copiedClip: -1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
292
    property int zoomOnMouse: -1
293
    property int viewActiveTrack: timeline.activeTrack
294
295
    property int wheelAccumulatedDelta: 0
    readonly property int defaultDeltasPerStep: 120
296
    property bool seekingFinished : proxy.seekFinished
297
    property int scrollMin: scrollView.contentX / timeline.scaleFactor
298
    property int scrollMax: scrollMin + scrollView.contentItem.width / timeline.scaleFactor
299
    property double dar: 16/9
300
    property int collapsedHeight: Math.max(28, baseUnit * 1.8)
301
    property bool paletteUnchanged: true
302
303
304
305

    onSeekingFinishedChanged : {
        playhead.opacity = seekingFinished ? 1 : 0.5
    }
306

307
    //onCurrentTrackChanged: timeline.selection = []
308
    onTimeScaleChanged: {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
309
        if (root.zoomOnMouse >= 0) {
310
            scrollView.contentX = Math.max(0, root.zoomOnMouse * timeline.scaleFactor - getMouseX())
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
311
312
            root.zoomOnMouse = -1
        } else {
313
            scrollView.contentX = Math.max(0, root.consumerPosition * timeline.scaleFactor - (scrollView.width / 2))
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
314
        }
315
        //root.snapping = timeline.snap ? 10 / Math.sqrt(root.timeScale) : -1
316
        ruler.adjustStepSize()
317
318
319
320
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            // update dragged item pos
            dragProxy.masterObject.updateDrag()
        }
321
        console.log('GOT SCALE: ', timeScale, ', BASE: ', baseUnit, ' - SNAPPING: ', snapping)
322
    }
323

324
    onConsumerPositionChanged: {
325
        if (root.autoScrolling) Logic.scrollIfNeeded()
326
327
    }

328
329
    onViewActiveTrackChanged: {
        var tk = Logic.getTrackById(timeline.activeTrack)
330
331
        if (tk.y < scrollView.contentY) {
            scrollView.contentY = Math.max(0, tk.y - scrollView.height / 3)
332
        } else if (tk.y + tk.height > scrollView.contentY + scrollView.height) {
333
334
335
336
            var newY = Math.min(trackHeaders.height - scrollView.height + scrollView.ScrollBar.horizontal.height, tk.y - scrollView.height / 3)
            if (newY >= 0) {
                scrollView.contentY = newY
            }
337
338
339
        }
    }

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
    onActiveToolChanged: {
        if (root.activeTool == 2) {
            // Spacer activated
            endDrag()
        } else if (root.activeTool == 0) {
            var tk = getMouseTrack()
            if (tk < 0) {
                console.log('........ MOUSE OUTSIDE TRAKS\n\n.........')
                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()
            }
        }
    }

366
367
368
369
370
371
372
    DropArea { //Drop area for compositions
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
        keys: 'kdenlive/composition'
        onEntered: {
373
            console.log("Trying to drop composition")
374
            if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
375
                console.log("No clip being moved")
376
377
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY)
                var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
378
                droppedPosition = frame
379
                if (track >= 0 && !controller.isAudioTrack(track)) {
380
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
381
                    console.log("Trying to insert",track, frame, clipBeingDroppedData)
382
                    clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData, false)
383
                    console.log("id",clipBeingDroppedId)
384
                    continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
385
386
387
388
389
390
                    drag.acceptProposedAction()
                } else {
                    drag.accepted = false
                }
            }
        }
391
392
        onPositionChanged: {
            if (clipBeingMovedId == -1) {
393
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.contentY)
394
                if (track !=-1) {
395
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
396
                    if (clipBeingDroppedId >= 0){
397
398
399
400
                        if (controller.isAudioTrack(track)) {
                            // Don't allow moving composition to an audio track
                            track = controller.getCompositionTrackId(clipBeingDroppedId)
                        }
401
                        controller.suggestCompositionMove(clipBeingDroppedId, track, frame, root.consumerPosition, root.snapping)
402
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
403
                    } else if (!controller.isAudioTrack(track)) {
404
                        frame = controller.suggestSnapPoint(frame, root.snapping)
405
406
                        clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
                        clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData , false)
407
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
408
                    }
409
410
411
                }
            }
        }
412
        onExited:{
413
414
            if (clipBeingDroppedId != -1) {
                // If we exit, remove composition
415
                controller.requestItemDeletion(clipBeingDroppedId, false)
416
                clearDropData()
417
418
            }
        }
419
420
421
422
423
        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
424
                controller.requestItemDeletion(clipBeingDroppedId, false)
425
                timeline.insertNewComposition(track, frame, clipBeingDroppedData, true)
426
            }
427
            clearDropData()
428
        }
429
    }
430
431
432
    DropArea {
        //Drop area for bin/clips
        id: clipDropArea
433
434
435
436
437
438
439
440
441
        /** @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) {
442
                id = timeline.insertClip(timeline.activeTrack, frame, clipBeingDroppedData, false, true, false)
443
            } else {
444
                var ids = timeline.insertClips(timeline.activeTrack, frame, binIds, false, true, false)
445
446
447

                // if the clip insertion succeeded, request the clips to be grouped
                if (ids.length > 0) {
448
                    timeline.selectItems(ids)
449
450
451
452
453
454
                    id = ids[0]
                }
            }
            return id
        }

455
456
        property int fakeFrame: -1
        property int fakeTrack: -1
457
458
459
460
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
461
        keys: 'kdenlive/producerslist'
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
        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()
        }
498
        onEntered: {
499
            if (clipBeingMovedId == -1 && clipBeingDroppedId == -1) {
500
                //var track = Logic.getTrackIdFromPos(drag.y)
501
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY)
502
                if (track >= 0  && track < tracksRepeater.count) {
503
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
504
                    droppedPosition = frame
505
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
Nicolas Carion's avatar
Nicolas Carion committed
506
                    //drag.acceptProposedAction()
507
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/producerslist')
508
                    console.log('dropped data: ', clipBeingDroppedData)
509
510
511
512
513
                    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
                        clipBeingDroppedId = insertAndMaybeGroup(timeline.activeTrack, timeline.fullDuration, clipBeingDroppedData)
514
                        if (clipBeingDroppedId > -1) {
515
                            fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, root.snapping)
516
517
518
519
                            fakeTrack = timeline.activeTrack
                        } else {
                            drag.accepted = false
                        }
520
                    }
521
                    continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
522
523
524
525
                } else {
                    drag.accepted = false
                }
            }
526
        }
Nicolas Carion's avatar
Nicolas Carion committed
527
        onExited:{
528
529
            if (clipBeingDroppedId != -1 && drag.y < drag.x) {
                // If we exit on top, remove clip
530
                controller.requestItemDeletion(clipBeingDroppedId, false)
531
532
533
                clearDropData()
            } else {
                // Clip is dropped
Nicolas Carion's avatar
Nicolas Carion committed
534
535
            }
        }
536
        onPositionChanged: {
537
            if (clipBeingMovedId == -1) {
538
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY)
539
                if (track >= 0  && track < tracksRepeater.count) {
540
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
541
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
542
                    if (clipBeingDroppedId >= 0) {
543
                        fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, root.snapping)
544
545
                        fakeTrack = timeline.activeTrack
                        //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
546
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
547
                    } else {
548
                        frame = controller.suggestSnapPoint(frame, root.snapping)
549
550
551
552
553
                        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)
554
                            fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, root.snapping)
555
556
                            fakeTrack = timeline.activeTrack
                        }
557
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
558
                    }
559
                }
Nicolas Carion's avatar
Nicolas Carion committed
560
            }
561
562
        }
        onDropped: {
563
            processDrop()
564
565
        }
    }
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
    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) {
592
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.contentY)
593
594
                if (track >= 0  && track < tracksRepeater.count) {
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
595
                    var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
596
597
598
599
                    if (clipBeingDroppedId >= 0) {
                        //fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, root.consumerPosition, Math.floor(root.snapping))
                        fakeTrack = timeline.activeTrack
                        //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
600
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
601
                    } else {
602
                        frame = controller.suggestSnapPoint(frame, root.snapping)
603
604
605
606
607
608
609
610
                        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
                        }
611
                        continuousScrolling(drag.x + scrollView.contentX, drag.y + scrollView.contentY)
612
613
614
615
616
                    }
                }
            }
        }
        onDropped: {
617
            var frame = Math.round((drag.x + scrollView.contentX) / timeline.scaleFactor)
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
            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()
        }
    }
637

638
639
    Row {
        Column {
640
            id: headerContainer
641
            width: headerWidth
642
            z: 1
643
            Item {
644
                // Padding between toolbar and track headers.
645
                width: parent.width
646
                height: ruler.height
647
                Button {
648
                    text: parent.width > metrics.boundingRect.width * 1.4 ? metrics.text : i18nc("Initial for Master", "M")
649
650
651
                    anchors.fill: parent
                    anchors.leftMargin: 2
                    anchors.rightMargin: 2
652
653
654
655
                    ToolTip.delay: 1000
                    ToolTip.timeout: 5000
                    ToolTip.visible: hovered
                    ToolTip.text: i18n("Show master effects")
656
657
658
659
                    TextMetrics {
                        id: metrics
                        text: i18n("Master")
                    }
660
661
662
663
                    onClicked: {
                        timeline.showMasterEffects()
                    }
                }
664
665
666
            }
            Flickable {
                // Non-slider scroll area for the track headers.
667
                id: headerFlick
668
                contentY: scrollView.contentY
669
670
671
                width: parent.width
                y: ruler.height
                height: root.height - ruler.height
672
                interactive: false
673
                clip: true
674

675
676
677
678
679
                MouseArea {
                    width: trackHeaders.width
                    height: trackHeaders.height
                    acceptedButtons: Qt.NoButton
                    onWheel: {
680
681
                        var newScroll = Math.min(scrollView.contentY - wheel.angleDelta.y, height - tracksArea.height + scrollView.ScrollBar.horizontal.height + ruler.height)
                        scrollView.contentY = Math.max(newScroll, 0)
682
683
                    }
                }
684
685
                Column {
                    id: trackHeaders
686
                    spacing: 0
687
688
689
690
691
                    Repeater {
                        id: trackHeaderRepeater
                        model: multitrack
                        TrackHead {
                            trackName: model.name
692
                            thumbsFormat: model.thumbsFormat
693
                            trackTag: model.trackTag
694
                            isDisabled: model.disabled
695
696
                            isComposite: model.composite
                            isLocked: model.locked
697
                            isActive: model.trackActive
698
                            isAudio: model.audio
699
                            showAudioRecord: model.audioRecord
700
701
                            effectNames: model.effectNames
                            isStackEnabled: model.isStackEnabled
702
                            width: headerWidth
703
                            current: item === timeline.activeTrack
704
                            trackId: item
705
                            height: model.trackHeight
706
                            onIsLockedChanged: tracksRepeater.itemAt(index).isLocked = isLocked
707
                            collapsed: height <= root.collapsedHeight
708
709
710
                            Component.onCompleted: {
                                root.collapsedHeight = collapsedHeight
                            }
711
712
                            onHeightChanged: {
                                collapsed = height <= root.collapsedHeight
713
                            }
714
715
716
717
718
719
                        }
                    }
                }
                Column {
                    id: trackHeadersResizer
                    spacing: 0
720
                    width: root.baseUnit / 2
721
722
723
                    Rectangle {
                        id: resizer
                        height: trackHeaders.height
724
                        width: root.baseUnit / 2
725
726
727
728
729
730
731
732
733
734
735
736
737
                        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
738
                            drag.minimumX: 2 * baseUnit
739
740
741
742
743
                            property double startX
                            property double originalX
                            drag.smoothed: false

                            onPressed: {
744
                                root.autoScrolling = false
745
746
                            }
                            onReleased: {
747
                                root.autoScrolling = timeline.autoScroll
748
749
750
751
752
753
754
755
756
757
758
759
                                parent.opacity = 0
                            }
                            onEntered: parent.opacity = 0.5
                            onExited: parent.opacity = 0
                            onPositionChanged: {
                                if (mouse.buttons === Qt.LeftButton) {
                                    parent.opacity = 0.5
                                    headerWidth = Math.max(10, mapToItem(null, x, y).x + 2)
                                    timeline.setHeaderWidth(headerWidth)
                                }
                            }
                        }
760
761
762
763
764
765
                    }
                }
            }
        }
        MouseArea {
            id: tracksArea
766
767
            property real clickX
            property real clickY
768
            width: root.width - root.headerWidth
769
            height: root.height
770
            x: root.headerWidth
771
            property bool shiftPress: false
772
773
            // This provides continuous scrubbing and scimming at the left/right edges.
            hoverEnabled: true
774
775
            preventStealing: true
            acceptedButtons: Qt.AllButtons
776
            cursorShape: root.activeTool === 0 ? Qt.ArrowCursor : root.activeTool === 1 ? Qt.IBeamCursor : Qt.SplitHCursor
777
            onWheel: {
778
779
780
781
782
783
784
785
786
                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
787
                    proxy.position = wheel.angleDelta.y > 0 ? Math.max(root.consumerPosition - delta, 0) : Math.min(root.consumerPosition + delta, timeline.fullDuration - 1)
788
                }
789
            }
790
            onPressed: {
791
                focus = true
792
                shiftPress = (mouse.modifiers & Qt.ShiftModifier) && (mouse.y > ruler.height) && !(mouse.modifiers & Qt.AltModifier)
793
                if (mouse.buttons === Qt.MidButton || (root.activeTool == 0 && (mouse.modifiers & Qt.ControlModifier) && !shiftPress)) {
794
795
796
797
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
798
                if (root.activeTool === 0 && shiftPress && mouse.y > ruler.height) {
799
                        // rubber selection
800
                        rubberSelect.clickX = mouse.x + scrollView.contentX
801
                        rubberSelect.clickY = mouse.y + scrollView.contentY
802
803
                        rubberSelect.x = mouse.x + tracksArea.x
                        rubberSelect.y = mouse.y
804
                        rubberSelect.originX = rubberSelect.clickX
805
                        rubberSelect.originY = rubberSelect.clickY
806
807
808
                        rubberSelect.width = 0
                        rubberSelect.height = 0
                } else if (mouse.button & Qt.LeftButton) {
809
810
                    if (root.activeTool === 1) {
                        // razor tool
811
                        var y = mouse.y - ruler.height + scrollView.contentY
812
                        if (y >= 0) {
813
                            timeline.cutClipUnderCursor((scrollView.contentX + mouse.x) / timeline.scaleFactor, tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId)
814
                        }
815
                    }
816
817
818
819
                    if (dragProxy.draggedItem > -1) {
                        mouse.accepted = false
                        return
                    }
820
                    if (root.activeTool === 2 && mouse.y > ruler.height) {
821
                        // spacer tool
822
823
                        var y = mouse.y - ruler.height + scrollView.contentY
                        var frame = (scrollView.contentX + mouse.x) / timeline.scaleFactor
824
                        var track = (mouse.modifiers & Qt.ControlModifier) ? tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId : -1
825
826
827
828
829
830
                        spacerGroup = timeline.requestSpacerStartOperation(track, frame)
                        if (spacerGroup > -1) {
                            drag.axis = Drag.XAxis
                            Drag.active = true
                            Drag.proposedAction = Qt.MoveAction
                            spacerClickFrame = frame
831
                            spacerFrame = controller.getItemPosition(spacerGroup)
832
                        }
833
                    } else if (root.activeTool === 0 || mouse.y <= ruler.height) {
834
                        if (mouse.y > ruler.height) {
835
                            controller.requestClearSelection();
836
                        }
837
                        proxy.position = Math.min((scrollView.contentX + mouse.x) / timeline.scaleFactor, timeline.fullDuration - 1)
838
                    }
839
                } else if (mouse.button & Qt.RightButton) {
840
                    if (mouse.y > ruler.height) {
841
842
                        timeline.activeTrack = tracksRepeater.itemAt(Logic.getTrackIndexFromPos(mouse.y - ruler.height + scrollView.contentY)).trackInternalId
                        root.mainFrame = Math.floor((mouse.x + scrollView.contentX) / timeline.scaleFactor)
843
                        root.showTimelineMenu()
844
845
                    } else {
                        // ruler menu
846
                        root.showRulerMenu()
847
                    }
848
                }
849
            }
850
            property bool scim: false
851
852
853
            onExited: {
                scim = false
            }
854
            onPositionChanged: {
855
                if (pressed && ((mouse.buttons === Qt.MidButton) || (mouse.buttons === Qt.LeftButton && root.activeTool == 0 && (mouse.modifiers & Qt.ControlModifier) && !shiftPress))) {
856
                    // Pan view
857
858
859
860
                    var newScroll = Math.min(scrollView.contentX - (mouseX - clickX), timeline.fullDuration * root.timeScale - (scrollView.width - scrollView.ScrollBar.vertical.width))
                    var vertScroll = Math.min(scrollView.contentY - (mouseY - clickY), trackHeaders.height - scrollView.height + scrollView.ScrollBar.horizontal.height)
                    scrollView.contentX = Math.max(newScroll, 0)
                    scrollView.contentY = Math.max(vertScroll, 0)
861
862
863
864
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
865
                if (!pressed && !rubberSelect.visible && root.activeTool === 1) {
866
                    cutLine.x = Math.floor((scrollView.contentX + mouse.x) / timeline.scaleFactor) * timeline.scaleFactor - scrollView.contentX
867
                    if (mouse.modifiers & Qt.ShiftModifier) {
868
                        // Seek
869
                        proxy.position = Math.floor((scrollView.contentX + mouse.x) / timeline.scaleFactor)
870
871
                    }
                }
872
                var mousePos = Math.max(0, Math.round((mouse.x + scrollView.contentX) / timeline.scaleFactor))
873
                root.mousePosChanged(mousePos)
874
                ruler.showZoneLabels = mouse.y < ruler.height
Jean-Baptiste Mardelle's avatar