timeline.qml 70.9 KB
Newer Older
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1
import QtQuick 2.6
2
import QtQml.Models 2.2
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
3
import QtQuick.Controls 1.4 as OLD
4
import QtQuick.Controls 2.2
5
import QtQuick.Layouts 1.3
6
import QtQuick.Dialogs 1.2
7
import Kdenlive.Controls 1.0
8
9
10
11
12
13
import QtQuick.Window 2.2
import 'Timeline.js' as Logic

Rectangle {
    id: root
    objectName: "timelineview"
14

15
16
    SystemPalette { id: activePalette }
    color: activePalette.window
17
    property bool validMenu: false
18
    property color textColor: activePalette.text
19

20
    signal clipClicked()
21
    signal mousePosChanged(int position)
22
23
    signal zoomIn(bool onMouse)
    signal zoomOut(bool onMouse)
24

25
26
27
28
    FontMetrics {
        id: fontMetrics
        font.family: "Arial"
    }
29
    ClipMenu {
30
31
        id: clipMenu
    }
32
    CompositionMenu {
33
34
        id: compositionMenu
    }
35

36
37
38
39
40
41
42
43
44
45
46
47
    function fitZoom() {
        return scrollView.width / (timeline.duration * 1.1)
    }

    function scrollPos() {
        return scrollView.flickableItem.contentX
    }

    function goToStart(pos) {
        scrollView.flickableItem.contentX = pos
    }

48
49
    function updatePalette() {
        root.color = activePalette.window
50
        root.textColor = activePalette.text
51
52
53
54
        playhead.fillColor = activePalette.windowText
        ruler.repaintRuler()
    }

55
    function moveSelectedTrack(offset) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
56
57
        var cTrack = Logic.getTrackIndexFromId(timeline.activeTrack)
        var newTrack = cTrack + offset
58
59
60
61
62
63
64
        var max = tracksRepeater.count;
        if (newTrack < 0) {
            newTrack = max - 1;
        } else if (newTrack >= max) {
            newTrack = 0;
        }
        console.log('Setting curr tk: ', newTrack, 'MAX: ',max)
65
        timeline.activeTrack = tracksRepeater.itemAt(newTrack).trackInternalId
66
67
    }

68
    function zoomByWheel(wheel) {
69
        if (wheel.modifiers & Qt.AltModifier) {
70
            // Seek to next snap
71
72
73
74
75
76
            if (wheel.angleDelta.x > 0) {
                timeline.triggerAction('monitor_seek_snap_backward')
            } else {
                timeline.triggerAction('monitor_seek_snap_forward')
            }
        } else if (wheel.modifiers & Qt.ControlModifier) {
77
            root.wheelAccumulatedDelta += wheel.angleDelta.y;
78
            // Zoom
79
            if (root.wheelAccumulatedDelta >= defaultDeltasPerStep) {
80
                root.zoomIn(true);
81
82
                root.wheelAccumulatedDelta = 0;
            } else if (root.wheelAccumulatedDelta <= -defaultDeltasPerStep) {
83
                root.zoomOut(true);
84
                root.wheelAccumulatedDelta = 0;
85
            }
86
87
88
89
        } else if (wheel.modifiers & Qt.ShiftModifier) {
            // Vertical scroll
            var newScroll = Math.min(scrollView.flickableItem.contentY - wheel.angleDelta.y, trackHeaders.height - tracksArea.height + scrollView.__horizontalScrollBar.height + cornerstone.height)
            scrollView.flickableItem.contentY = Math.max(newScroll, 0)
90
        } else {
91
            // Horizontal scroll
92
            var newScroll = Math.min(scrollView.flickableItem.contentX - wheel.angleDelta.y, timeline.fullDuration * root.timeScale - (scrollView.width - scrollView.__verticalScrollBar.width))
93
            scrollView.flickableItem.contentX = Math.max(newScroll, 0)
94
        }
95
        wheel.accepted = true
96
97
    }

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    function continuousScrolling(x) {
        // This provides continuous scrolling at the left/right edges.
        if (x > scrollView.flickableItem.contentX + scrollView.width - 50) {
            scrollTimer.item = clip
            scrollTimer.backwards = false
            scrollTimer.start()
        } else if (x < 50) {
            scrollView.flickableItem.contentX = 0;
            scrollTimer.stop()
        } else if (x < scrollView.flickableItem.contentX + 50) {
            scrollTimer.item = clip
            scrollTimer.backwards = true
            scrollTimer.start()
        } else {
            scrollTimer.stop()
        }
    }
115
116
117
    function getTrackYFromId(a_track) {
        return Logic.getTrackYFromId(a_track)
    }
118

119
120
121
122
    function getTrackYFromMltIndex(a_track) {
        return Logic.getTrackYFromMltIndex(a_track)
    }

123
124
125
126
    function getTracksCount() {
        return Logic.getTracksList()
    }

127
    function getMousePos() {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
128
        return (scrollView.flickableItem.contentX + tracksArea.mouseX) / timeline.scaleFactor
129
130
    }

131
132
133
134
135
136
137
138
    function getScrollPos() {
        return scrollView.flickableItem.contentX
    }

    function setScrollPos(pos) {
        return scrollView.flickableItem.contentX = pos
    }

139
140
141
142
    function getCopiedItemId() {
        return copiedClip
    }

143
    function getMouseTrack() {
144
        return Logic.getTrackIdFromPos(tracksArea.mouseY - ruler.height + scrollView.flickableItem.contentY)
145
146
    }

147
148
149
    function getTrackColor(audio, header) {
        var col = activePalette.alternateBase
        if (audio) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
150
            col = Qt.tint(col, "#06FF00CC")
151
152
        }
        if (header) {
153
            col = Qt.darker(col, 1.05)
154
        }
155
        return col
156
157
    }

158
159
160
161
162
    function clearDropData() {
        clipBeingDroppedId = -1
        droppedPosition = -1
        droppedTrack = -1
        scrollTimer.running = false
163
        scrollTimer.stop()
164
    }
165

166
167
168
    function isDragging() {
        return dragProxy.draggedItem > -1 && dragProxyArea.pressed
    }
169

170
    function initDrag(itemObject, itemCoord, itemId, itemPos, itemTrack, isComposition) {
171
        dragProxy.x = itemObject.modelStart * timeScale
172
        dragProxy.y = itemCoord.y
173
        dragProxy.width = itemObject.clipDuration * timeScale
174
175
176
177
178
179
        dragProxy.height = itemCoord.height
        dragProxy.masterObject = itemObject
        dragProxy.draggedItem = itemId
        dragProxy.sourceTrack = itemTrack
        dragProxy.sourceFrame = itemPos
        dragProxy.isComposition = isComposition
180
        dragProxy.verticalOffset = isComposition ? itemObject.displayHeight : 0
181
182
183
184
185
186
187
    }
    function endDrag() {
        dragProxy.draggedItem = -1
        dragProxy.x = 0
        dragProxy.y = 0
        dragProxy.width = 0
        dragProxy.height = 0
188
        dragProxy.verticalOffset = 0
189
190
    }

191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
    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
    }

209
    property int headerWidth: timeline.headerWidth()
210
    property int activeTool: 0
211
    property real baseUnit: fontMetrics.font.pointSize
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
212
213
    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)
214
    property bool stopScrolling: false
215
    property int duration: timeline.duration
216
217
    property color audioColor: timeline.audioColor
    property color videoColor: timeline.videoColor
218
    property color lockedColor: timeline.lockedColor
Vincent Pinon's avatar
Vincent Pinon committed
219
    property color selectionColor: timeline.selectionColor
220
    property color groupColor: timeline.groupColor
Nicolas Carion's avatar
Nicolas Carion committed
221
    property int clipBeingDroppedId: -1
222
    property string clipBeingDroppedData
223
224
    property int droppedPosition: -1
    property int droppedTrack: -1
225
    property int clipBeingMovedId: -1
226
    property int spacerGroup: -1
227
228
    property int spacerFrame: -1
    property int spacerClickFrame: -1
229
    property real timeScale: timeline.scaleFactor
230
    property real snapping: (timeline.snap && (timeScale < 2 * baseUnit)) ? 10 / Math.sqrt(timeScale) - 0.5 : -1
231
    property var timelineSelection: timeline.selection
232
    property int trackHeight
233
    property int copiedClip: -1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
234
    property int zoomOnMouse: -1
235
    property int viewActiveTrack: timeline.activeTrack
236
237
    property int wheelAccumulatedDelta: 0
    readonly property int defaultDeltasPerStep: 120
238

239
    //onCurrentTrackChanged: timeline.selection = []
240
    onTimeScaleChanged: {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
241
        if (root.zoomOnMouse >= 0) {
242
            scrollView.flickableItem.contentX = Math.max(0, root.zoomOnMouse * timeline.scaleFactor - tracksArea.mouseX)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
243
244
245
246
            root.zoomOnMouse = -1
        } else {
            scrollView.flickableItem.contentX = Math.max(0, (timeline.seekPosition > -1 ? timeline.seekPosition : timeline.position) * timeline.scaleFactor - (scrollView.width / 2))
        }
247
        //root.snapping = timeline.snap ? 10 / Math.sqrt(root.timeScale) : -1
248
        ruler.adjustStepSize()
249
250
251
252
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            // update dragged item pos
            dragProxy.masterObject.updateDrag()
        }
253
    }
254

255
256
257
258
259
    onViewActiveTrackChanged: {
        var tk = Logic.getTrackById(timeline.activeTrack)
        if (tk.y < scrollView.flickableItem.contentY) {
            scrollView.flickableItem.contentY = Math.max(0, tk.y - scrollView.height / 3)
        } else if (tk.y + tk.height > scrollView.flickableItem.contentY + scrollView.viewport.height) {
260
            scrollView.flickableItem.contentY = Math.min(trackHeaders.height - scrollView.height + scrollView.__horizontalScrollBar.height, tk.y - scrollView.height / 3)
261
262
263
        }
    }

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
    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()
            }
        }
    }

290
291
292
293
294
295
296
    DropArea { //Drop area for compositions
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
        keys: 'kdenlive/composition'
        onEntered: {
297
            console.log("Trying to drop composition")
298
            if (clipBeingMovedId == -1) {
299
                console.log("No clip being moved")
300
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.flickableItem.contentY)
301
302
                var frame = Math.round((drag.x + scrollView.flickableItem.contentX) / timeline.scaleFactor)
                droppedPosition = frame
303
                if (track >= 0 && !controller.isAudioTrack(track)) {
304
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
305
                    console.log("Trying to insert",track, frame, clipBeingDroppedData)
306
                    clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData, false)
307
                    console.log("id",clipBeingDroppedId)
308
309
310
311
312
313
314
                    continuousScrolling(drag.x + scrollView.flickableItem.contentX)
                    drag.acceptProposedAction()
                } else {
                    drag.accepted = false
                }
            }
        }
315
316
        onPositionChanged: {
            if (clipBeingMovedId == -1) {
317
                var track = Logic.getTrackIdFromPos(drag.y + scrollView.flickableItem.contentY)
318
319
                if (track !=-1) {
                    var frame = Math.round((drag.x + scrollView.flickableItem.contentX) / timeline.scaleFactor)
320
                    frame = controller.suggestSnapPoint(frame, Math.floor(root.snapping))
321
                    if (clipBeingDroppedId >= 0){
322
323
324
325
                        if (controller.isAudioTrack(track)) {
                            // Don't allow moving composition to an audio track
                            track = controller.getCompositionTrackId(clipBeingDroppedId)
                        }
326
327
                        controller.requestCompositionMove(clipBeingDroppedId, track, frame, true, false)
                        continuousScrolling(drag.x + scrollView.flickableItem.contentX)
328
                    } else if (!controller.isAudioTrack(track)) {
329
330
331
332
                        clipBeingDroppedData = drag.getDataAsString('kdenlive/composition')
                        clipBeingDroppedId = timeline.insertComposition(track, frame, clipBeingDroppedData , false)
                        continuousScrolling(drag.x + scrollView.flickableItem.contentX)
                    }
333
334
335
                }
            }
        }
336
337
        onExited:{
            if (clipBeingDroppedId != -1) {
338
                controller.requestItemDeletion(clipBeingDroppedId, false)
339
            }
340
            clearDropData()
341
        }
342
343
344
345
346
        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
347
                controller.requestItemDeletion(clipBeingDroppedId, false)
348
                timeline.insertNewComposition(track, frame, clipBeingDroppedData, true)
349
            }
350
            clearDropData()
351
        }
352
353
    }
    DropArea { //Drop area for bin/clips
354
355
356
357
358
359
360
361
362
        /** @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) {
363
                id = timeline.insertClip(timeline.activeTrack, frame, clipBeingDroppedData, false, true, false)
364
            } else {
365
                var ids = timeline.insertClips(timeline.activeTrack, frame, binIds, false, true, false)
366
367
368

                // if the clip insertion succeeded, request the clips to be grouped
                if (ids.length > 0) {
369
                    timeline.selectItems(ids)
370
371
372
373
374
375
                    id = ids[0]
                }
            }
            return id
        }

376
377
        property int fakeFrame: -1
        property int fakeTrack: -1
378
379
380
381
        width: root.width - headerWidth
        height: root.height - ruler.height
        y: ruler.height
        x: headerWidth
382
        keys: 'kdenlive/producerslist'
383
        onEntered: {
384
            if (clipBeingMovedId == -1) {
385
                //var track = Logic.getTrackIdFromPos(drag.y)
386
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.flickableItem.contentY)
387
                if (track >= 0  && track < tracksRepeater.count) {
388
389
                    var frame = Math.round((drag.x + scrollView.flickableItem.contentX) / timeline.scaleFactor)
                    droppedPosition = frame
390
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
Nicolas Carion's avatar
Nicolas Carion committed
391
                    //drag.acceptProposedAction()
392
                    clipBeingDroppedData = drag.getDataAsString('kdenlive/producerslist')
393
                    console.log('dropped data: ', clipBeingDroppedData)
394
395
396
397
398
399
400
401
                    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)
                        fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, timeline.position, Math.floor(root.snapping))
                        fakeTrack = timeline.activeTrack
                    }
402
                    continuousScrolling(drag.x + scrollView.flickableItem.contentX)
403
404
405
406
                } else {
                    drag.accepted = false
                }
            }
407
        }
Nicolas Carion's avatar
Nicolas Carion committed
408
409
        onExited:{
            if (clipBeingDroppedId != -1) {
410
                controller.requestItemDeletion(clipBeingDroppedId, false)
Nicolas Carion's avatar
Nicolas Carion committed
411
            }
412
            clearDropData()
Nicolas Carion's avatar
Nicolas Carion committed
413
        }
414
        onPositionChanged: {
415
            if (clipBeingMovedId == -1) {
416
                var track = Logic.getTrackIndexFromPos(drag.y + scrollView.flickableItem.contentY)
417
                if (track >= 0  && track < tracksRepeater.count) {
418
                    timeline.activeTrack = tracksRepeater.itemAt(track).trackInternalId
419
420
                    var frame = Math.round((drag.x + scrollView.flickableItem.contentX) / timeline.scaleFactor)
                    if (clipBeingDroppedId >= 0){
421
422
423
                        fakeFrame = controller.suggestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, timeline.position, Math.floor(root.snapping))
                        fakeTrack = timeline.activeTrack
                        //controller.requestClipMove(clipBeingDroppedId, timeline.activeTrack, frame, true, false, false)
424
425
                        continuousScrolling(drag.x + scrollView.flickableItem.contentX)
                    } else {
426
427
428
429
430
431
432
433
434
                        frame = controller.suggestSnapPoint(frame, Math.floor(root.snapping))
                        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, timeline.position, Math.floor(root.snapping))
                            fakeTrack = timeline.activeTrack
                        }
435
436
                        continuousScrolling(drag.x + scrollView.flickableItem.contentX)
                    }
437
                }
Nicolas Carion's avatar
Nicolas Carion committed
438
            }
439
440
        }
        onDropped: {
441
442
443
            if (clipBeingDroppedId != -1) {
                var frame = controller.getClipPosition(clipBeingDroppedId)
                var track = controller.getClipTrackId(clipBeingDroppedId)
444
445
446
447
                if (!controller.normalEdit()) {
                    frame = fakeFrame
                    track = fakeTrack
                }
448
449
450
451
                /* 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
                 */
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
452
                controller.requestItemDeletion(clipBeingDroppedId, false)
453
454
455

                var binIds = clipBeingDroppedData.split(";")
                if (binIds.length == 1) {
456
457
458
459
460
                    if (controller.normalEdit()) {
                        timeline.insertClip(track, frame, clipBeingDroppedData, true, true, false)
                    } else {
                        timeline.insertClipZone(clipBeingDroppedData, track, frame)
                    }
461
                } else {
462
463
464
465
466
467
                    if (controller.normalEdit()) {
                        timeline.insertClips(track, frame, binIds, true, true)
                    } else {
                        // TODO
                        console.log('multiple clips insert/overwrite not supported yet')
                    }
468
                }
469
470
                fakeTrack = -1
                fakeFrame = -1
471
            }
472
            clearDropData()
473
474
        }
    }
475
    OLD.Menu {
476
        id: menu
477
478
        property int clickedX
        property int clickedY
479
480
        onAboutToHide: {
            timeline.ungrabHack()
481
            editGuideMenu.visible = false
482
        }
483
        OLD.MenuItem {
484
            text: i18n('Paste')
485
            iconName: 'edit-paste'
486
487
            visible: copiedClip != -1
            onTriggered: {
488
489
490
                var track = Logic.getTrackIdFromPos(menu.clickedY - ruler.height + scrollView.flickableItem.contentY)
                var frame = Math.floor((menu.clickedX + scrollView.flickableItem.contentX) / timeline.scaleFactor)
                timeline.pasteItem(frame, track)
491
492
            }
        }
493
        OLD.MenuItem {
494
495
            text: i18n('Insert Space')
            onTriggered: {
496
                var track = Logic.getTrackIdFromPos(menu.clickedY - ruler.height + scrollView.flickableItem.contentY)
497
                var frame = Math.floor((menu.clickedX + scrollView.flickableItem.contentX) / timeline.scaleFactor)
498
499
500
                timeline.insertSpace(track, frame);
            }
        }
501
        OLD.MenuItem {
502
503
            text: i18n('Remove Space On Active Track')
            onTriggered: {
504
                var track = Logic.getTrackIdFromPos(menu.clickedY - ruler.height + scrollView.flickableItem.contentY)
505
                var frame = Math.floor((menu.clickedX + scrollView.flickableItem.contentX) / timeline.scaleFactor)
506
507
508
                timeline.removeSpace(track, frame);
            }
        }
509
        OLD.MenuItem {
510
511
            text: i18n('Remove Space')
            onTriggered: {
512
                var track = Logic.getTrackIdFromPos(menu.clickedY - ruler.height + scrollView.flickableItem.contentY)
513
                var frame = Math.floor((menu.clickedX + scrollView.flickableItem.contentX) / timeline.scaleFactor)
514
515
516
                timeline.removeSpace(track, frame, true);
            }
        }
517
        OLD.MenuItem {
518
            id: addGuideMenu
519
520
            text: i18n('Add Guide')
            onTriggered: {
521
                timeline.switchGuide(timeline.position);
522
523
            }
        }
524
525
526
527
528
529
530
531
532
        GuidesMenu {
            title: i18n('Go to guide...')
            menuModel: guidesModel
            enabled: guidesDelegateModel.count > 0
            onGuideSelected: {
                timeline.seekPosition = assetFrame
                timeline.position = timeline.seekPosition
            }
        }
533
        OLD.MenuItem {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
534
535
536
537
538
539
540
            id: editGuideMenu
            text: i18n('Edit Guide')
            visible: false
            onTriggered: {
                timeline.editGuide(timeline.position);
            }
        }
541
542
543
        AssetMenu {
            title: i18n('Insert a composition...')
            menuModel: transitionModel
544
            isTransition: true
545
            onAssetSelected: {
546
                var track = Logic.getTrackIdFromPos(menu.clickedY - ruler.height + scrollView.flickableItem.contentY)
547
548
549
550
551
552
553
                var frame = Math.round((menu.clickedX + scrollView.flickableItem.contentX) / timeline.scaleFactor)
                var id = timeline.insertComposition(track, frame, assetId, true)
                if (id == -1) {
                    compositionFail.open()
                }
            }
        }
554
        onAboutToShow: {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
555
556
557
558
559
560
561
            if (guidesModel.hasMarker(timeline.position)) {
                // marker at timeline position
                addGuideMenu.text = i18n('Remove Guide')
                editGuideMenu.visible = true
            } else {
                addGuideMenu.text = i18n('Add Guide')
            }
562
563
            console.log("pop menu")
        }
564
    }
565
566
567
568
    OLD.Menu {
        id: rulermenu
        property int clickedX
        property int clickedY
569
570
571
572
        onAboutToHide: {
            timeline.ungrabHack()
            editGuideMenu2.visible = false
        }
573
574
575
576
577
578
579
        OLD.MenuItem {
            id: addGuideMenu2
            text: i18n('Add Guide')
            onTriggered: {
                timeline.switchGuide(timeline.position);
            }
        }
580
581
582
583
584
585
586
587
588
        GuidesMenu {
            title: i18n('Go to guide...')
            menuModel: guidesModel
            enabled: guidesDelegateModel.count > 0
            onGuideSelected: {
                timeline.seekPosition = assetFrame
                timeline.position = timeline.seekPosition
            }
        }
589
590
591
592
593
594
595
596
        OLD.MenuItem {
            id: editGuideMenu2
            text: i18n('Edit Guide')
            visible: false
            onTriggered: {
                timeline.editGuide(timeline.position);
            }
        }
597
598
599
600
601
602
603
        OLD.MenuItem {
            id: addProjectNote
            text: i18n('Add Project Note')
            onTriggered: {
                timeline.triggerAction('add_project_note')
            }
        }
604
605
606
607
608
609
610
611
612
613
614
        onAboutToShow: {
            if (guidesModel.hasMarker(timeline.position)) {
                // marker at timeline position
                addGuideMenu2.text = i18n('Remove Guide')
                editGuideMenu2.visible = true
            } else {
                addGuideMenu2.text = i18n('Add Guide')
            }
            console.log("pop menu")
        }
    }
615
616
617
618
619
620
    MessageDialog {
        id: compositionFail
        title: i18n("Timeline error")
        icon: StandardIcon.Warning
        text: i18n("Impossible to add a composition at that position. There might not be enough space")
        standardButtons: StandardButton.Ok
621
    }
622
    OLD.Menu {
623
        id: headerMenu
624
625
626
        property int trackId: -1
        property int thumbsFormat: 0
        property bool audioTrack: false
627
        property bool recEnabled: false
628
629
630
        onAboutToHide: {
            timeline.ungrabHack()
        }
631
        OLD.MenuItem {
632
            text: i18n('Add Track')
633
634
635
            onTriggered: {
                timeline.addTrack(timeline.activeTrack)
            }
636
        }
637
        OLD.MenuItem {
638
            text: i18n('Delete Track')
639
640
641
            onTriggered: {
                timeline.deleteTrack(timeline.activeTrack)
            }
642
        }
643
644
645
646
647
648
649
650
651
652
        OLD.MenuItem {
            visible: headerMenu.audioTrack
            id: showRec
            text: "Show Record Controls"
            onTriggered: {
                controller.setTrackProperty(headerMenu.trackId, "kdenlive:audio_rec", showRec.checked ? '1' : '0')
            }
            checkable: true
            checked: headerMenu.recEnabled
        }
653
654
655
656
657
658
659
660
        OLD.MenuItem {
            visible: headerMenu.audioTrack
            id: configRec
            text: "Configure Recording"
            onTriggered: {
                timeline.showConfig(4,2)
            }
        }
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
        OLD.Menu {
            title: i18n('Track thumbnails')
            visible: !headerMenu.audioTrack
                    OLD.ExclusiveGroup { id: thumbStyle }
                    OLD.MenuItem {
                        text: "In frame"
                        id: inFrame
                        onTriggered:controller.setTrackProperty(headerMenu.trackId, "kdenlive:thumbs_format", 2)
                        checkable: true
                        exclusiveGroup: thumbStyle
                    }
                    OLD.MenuItem {
                        text: "In / out frames"
                        id: inOutFrame
                        onTriggered:controller.setTrackProperty(headerMenu.trackId, "kdenlive:thumbs_format", 0)
                        checkable: true
                        checked: true
                        exclusiveGroup: thumbStyle
                    }
                    OLD.MenuItem {
                        text: "All frames"
                        id: allFrame
                        onTriggered:controller.setTrackProperty(headerMenu.trackId, "kdenlive:thumbs_format", 1)
                        checkable: true
                        exclusiveGroup: thumbStyle
                    }
                    OLD.MenuItem {
                        text: "No thumbnails"
                        id: noFrame
                        onTriggered:controller.setTrackProperty(headerMenu.trackId, "kdenlive:thumbs_format", 3)
                        checkable: true
                        exclusiveGroup: thumbStyle
                    }
                onAboutToShow: {
                        switch(headerMenu.thumbsFormat) {
                            case 3:
                                noFrame.checked = true
                                break
                            case 2:
                                inFrame.checked = true
                                break
                            case 1:
                                allFrame.checked = true
                                break
                            default:
                                inOutFrame.checked = true
                                break
                        }
                }
        }
711
    }
712

713
714
    Row {
        Column {
715
            id: headerContainer
716
717
718
719
720
721
722
            z: 1
            Rectangle {
                id: cornerstone
                property bool selected: false
                // Padding between toolbar and track headers.
                width: headerWidth
                height: ruler.height
723
                color: 'transparent' //selected? shotcutBlue : activePalette.window
724
725
726
727
728
729
                border.color: selected? 'red' : 'transparent'
                border.width: selected? 1 : 0
                z: 1
            }
            Flickable {
                // Non-slider scroll area for the track headers.
730
                id: headerFlick
731
                contentY: scrollView.flickableItem.contentY
732
                width: headerWidth
733
                height: 100
734
735
                interactive: false

736
737
738
739
740
                MouseArea {
                    width: trackHeaders.width
                    height: trackHeaders.height
                    acceptedButtons: Qt.NoButton
                    onWheel: {
741
                        var newScroll = Math.min(scrollView.flickableItem.contentY - wheel.angleDelta.y, height - tracksArea.height + scrollView.__horizontalScrollBar.height + cornerstone.height)
742
743
744
                        scrollView.flickableItem.contentY = Math.max(newScroll, 0)
                    }
                }
745
746
                Column {
                    id: trackHeaders
747
                    spacing: 0
748
749
750
751
752
                    Repeater {
                        id: trackHeaderRepeater
                        model: multitrack
                        TrackHead {
                            trackName: model.name
753
                            thumbsFormat: model.thumbsFormat
754
                            trackTag: model.trackTag
755
                            isDisabled: model.disabled
756
757
                            isComposite: model.composite
                            isLocked: model.locked
758
                            isActive: model.trackActive
759
                            isAudio: model.audio
760
                            showAudioRecord: model.audioRecord
761
762
                            effectNames: model.effectNames
                            isStackEnabled: model.isStackEnabled
763
                            width: headerWidth
764
                            current: item === timeline.activeTrack
765
                            trackId: item
766
                            height: model.trackHeight
767
                            onIsLockedChanged: tracksRepeater.itemAt(index).isLocked = isLocked
768
                            collapsed: height <= collapsedHeight
769
                            onMyTrackHeightChanged: {
770
                                collapsed = myTrackHeight <= collapsedHeight
771
772
                                if (!collapsed) {
                                    controller.setTrackProperty(trackId, "kdenlive:trackheight", myTrackHeight)
773
774
775
                                    controller.setTrackProperty(trackId, "kdenlive:collapsed", "0")
                                } else {
                                    controller.setTrackProperty(trackId, "kdenlive:collapsed", collapsedHeight)
776
                                }
777
778
                                // hack: change property to trigger transition adjustment
                                root.trackHeight = root.trackHeight === 1 ? 0 : 1
779
                            }
780
                            onClicked: {
781
782
                                timeline.activeTrack = tracksRepeater.itemAt(index).trackInternalId
                                console.log('track name: ',index, ' = ', model.name,'/',tracksRepeater.itemAt(index).trackInternalId)
783
                                //timeline.selectTrackHead(currentTrack)
784
                            }
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
                        }
                    }
                }
                Column {
                    id: trackHeadersResizer
                    spacing: 0
                    width: 5
                    Rectangle {
                        id: resizer
                        height: trackHeaders.height
                        width: 3
                        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
809
                            drag.minimumX: 2 * baseUnit
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
                            property double startX
                            property double originalX
                            drag.smoothed: false

                            onPressed: {
                                root.stopScrolling = true
                            }
                            onReleased: {
                                root.stopScrolling = false
                                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)
                                }
                            }
                        }
831
832
833
834
835
836
                    }
                }
            }
        }
        MouseArea {
            id: tracksArea
837
838
            property real clickX
            property real clickY
839
840
            width: root.width - headerWidth
            height: root.height
841
842
843
844
845
846
            Keys.onDownPressed: {
                root.moveSelectedTrack(1)
            }
            Keys.onUpPressed: {
                root.moveSelectedTrack(-1)
            }
847
848
            // This provides continuous scrubbing and scimming at the left/right edges.
            hoverEnabled: true
849
            acceptedButtons: Qt.RightButton | Qt.LeftButton | Qt.MidButton
850
            cursorShape: tracksArea.mouseY < ruler.height || root.activeTool === 0 ? Qt.ArrowCursor : root.activeTool === 1 ? Qt.IBeamCursor : Qt.SplitHCursor
851
            onWheel: {
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
                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
                    if (timeline.seekPosition > -1) {
                        timeline.seekPosition = Math.min(timeline.seekPosition - (wheel.angleDelta.y > 0 ? delta : -delta), timeline.fullDuration - 1)
                    } else {
                        timeline.seekPosition = Math.min(timeline.position - (wheel.angleDelta.y > 0 ? delta : -delta), timeline.fullDuration - 1)
                    }
                    timeline.position = timeline.seekPosition
                }
868
            }
869
            onPressed: {
870
                focus = true
871
                if (mouse.buttons === Qt.MidButton || (root.activeTool == 0 && mouse.modifiers & Qt.ControlModifier)) {
872
873
874
875
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
876
                if (root.activeTool === 0 && mouse.modifiers & Qt.ShiftModifier && mouse.y > ruler.height) {
877
878
879
880
881
882
883
884
                        // rubber selection
                        rubberSelect.x = mouse.x + tracksArea.x
                        rubberSelect.y = mouse.y
                        rubberSelect.originX = mouse.x
                        rubberSelect.originY = rubberSelect.y
                        rubberSelect.width = 0
                        rubberSelect.height = 0
                } else if (mouse.button & Qt.LeftButton) {
885
886
887
888
889
                    if (root.activeTool === 1) {
                        // razor tool
                        var y = mouse.y - ruler.height + scrollView.flickableItem.contentY
                        timeline.cutClipUnderCursor((scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor, tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId)
                    }
890
891
892
893
                    if (dragProxy.draggedItem > -1) {
                        mouse.accepted = false
                        return
                    }
894
                    if (root.activeTool === 2 && mouse.y > ruler.height) {
895
                        // spacer tool
896
                        var y = mouse.y - ruler.height + scrollView.flickableItem.contentY
897
                        var frame = (scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor
898
                        var track = (mouse.modifiers & Qt.ControlModifier) ? tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId : -1
899
900
901
902
903
904
                        spacerGroup = timeline.requestSpacerStartOperation(track, frame)
                        if (spacerGroup > -1) {
                            drag.axis = Drag.XAxis
                            Drag.active = true
                            Drag.proposedAction = Qt.MoveAction
                            spacerClickFrame = frame
905
                            spacerFrame = controller.getItemPosition(spacerGroup)
906
                        }
907
                    } else if (root.activeTool === 0 || mouse.y <= ruler.height) {
908
                        if (mouse.y > ruler.height) {
909
                            controller.requestClearSelection();
910
                        }
911
                        timeline.seekPosition = Math.min((scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor, timeline.fullDuration - 1)
912
                        timeline.position = timeline.seekPosition
913
                    }
914
                } else if (mouse.button & Qt.RightButton) {
915
916
                    menu.clickedX = mouse.x
                    menu.clickedY = mouse.y
917
                    if (mouse.y > ruler.height) {
918
                        timeline.activeTrack = tracksRepeater.itemAt(Logic.getTrackIndexFromPos(mouse.y - ruler.height + scrollView.flickableItem.contentY)).trackInternalId
919
920
921
922
923
                        menu.popup()
                    } else {
                        // ruler menu
                        rulermenu.popup()
                    }
924
                }
925
            }
926
            property bool scim: false
927
928
929
            onExited: {
                scim = false
            }
930
            onPositionChanged: {
931
                if (pressed && ((mouse.buttons === Qt.MidButton) || (mouse.buttons === Qt.LeftButton && root.activeTool == 0 && mouse.modifiers & Qt.ControlModifier))) {
932
                    var newScroll = Math.min(scrollView.flickableItem.contentX - (mouseX - clickX), timeline.fullDuration * root.timeScale - (scrollView.width - scrollView.__verticalScrollBar.width))
933
                    var vertScroll = Math.min(scrollView.flickableItem.contentY - (mouseY - clickY), trackHeaders.height - scrollView.height + scrollView.__horizontalScrollBar.height)
934
935
936
937
938
939
                    scrollView.flickableItem.contentX = Math.max(newScroll, 0)
                    scrollView.flickableItem.contentY = Math.max(vertScroll, 0)
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
940
941
942
943
944
945
                if (!pressed && !rubberSelect.visible && root.activeTool === 1) {
                    cutLine.x = Math.floor((scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor) * timeline.scaleFactor - scrollView.flickableItem.contentX
                    if (mouse.modifiers & Qt.ShiftModifier) {
                        timeline.position = Math.floor((scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor)
                    }
                }
946
947
948
949
                if (dragProxy.draggedItem > -1) {
                    mouse.accepted = false
                    return
                }
950
951
                var mousePos = Math.max(0, Math.round((mouse.x + scrollView.flickableItem.contentX) / timeline.scaleFactor))
                root.mousePosChanged(mousePos)
952
                ruler.showZoneLabels = mouse.y < ruler.height
953
954
955
                if (mouse.modifiers & Qt.ShiftModifier && mouse.buttons === Qt.LeftButton && root.activeTool === 0 && !rubberSelect.visible && rubberSelect.y > 0) {
                    // rubber selection
                    rubberSelect.visible = true
956
                }
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
                if (rubberSelect.visible) {
                    var newX = mouse.x
                    var newY = mouse.y
                    if (newX < rubberSelect.originX) {
                        rubberSelect.x = newX + tracksArea.x
                        rubberSelect.width = rubberSelect.originX - newX
                    } else {
                        rubberSelect.x = rubberSelect.originX + tracksArea.x
                        rubberSelect.width = newX - rubberSelect.originX
                    }
                    if (newY < rubberSelect.originY) {
                        rubberSelect.y = newY
                        rubberSelect.height = rubberSelect.originY - newY
                    } else {
                        rubberSelect.y = rubberSelect.originY
                        rubberSelect.height= newY - rubberSelect.originY
                    }
                } else if (mouse.buttons === Qt.LeftButton) {
975
                    if (root.activeTool === 0 || mouse.y < ruler.height) {
976