timeline.qml 71.6 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
    property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active
20

21
    signal clipClicked()
22
    signal mousePosChanged(int position)
23
24
    signal zoomIn(bool onMouse)
    signal zoomOut(bool onMouse)
25
    signal processingDrag(bool dragging)
26

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

38
39
40
41
    onDragInProgressChanged: {
        processingDrag(!root.dragInProgress)
    }

42
43
44
45
46
47
48
49
50
51
52
53
    function fitZoom() {
        return scrollView.width / (timeline.duration * 1.1)
    }

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

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

54
55
    function updatePalette() {
        root.color = activePalette.window
56
        root.textColor = activePalette.text
57
58
59
        playhead.fillColor = activePalette.windowText
        ruler.repaintRuler()
    }
60
    
61
    function moveSelectedTrack(offset) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
62
63
        var cTrack = Logic.getTrackIndexFromId(timeline.activeTrack)
        var newTrack = cTrack + offset
64
65
66
67
68
69
70
        var max = tracksRepeater.count;
        if (newTrack < 0) {
            newTrack = max - 1;
        } else if (newTrack >= max) {
            newTrack = 0;
        }
        console.log('Setting curr tk: ', newTrack, 'MAX: ',max)
71
        timeline.activeTrack = tracksRepeater.itemAt(newTrack).trackInternalId
72
73
    }

74
    function zoomByWheel(wheel) {
75
        if (wheel.modifiers & Qt.AltModifier) {
76
            // Seek to next snap
77
78
79
80
81
82
            if (wheel.angleDelta.x > 0) {
                timeline.triggerAction('monitor_seek_snap_backward')
            } else {
                timeline.triggerAction('monitor_seek_snap_forward')
            }
        } else if (wheel.modifiers & Qt.ControlModifier) {
83
            root.wheelAccumulatedDelta += wheel.angleDelta.y;
84
            // Zoom
85
            if (root.wheelAccumulatedDelta >= defaultDeltasPerStep) {
86
                root.zoomIn(true);
87
88
                root.wheelAccumulatedDelta = 0;
            } else if (root.wheelAccumulatedDelta <= -defaultDeltasPerStep) {
89
                root.zoomOut(true);
90
                root.wheelAccumulatedDelta = 0;
91
            }
92
93
94
95
        } 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)
96
        } else {
97
            // Horizontal scroll
98
            var newScroll = Math.min(scrollView.flickableItem.contentX - wheel.angleDelta.y, timeline.fullDuration * root.timeScale - (scrollView.width - scrollView.__verticalScrollBar.width))
99
            scrollView.flickableItem.contentX = Math.max(newScroll, 0)
100
        }
101
        wheel.accepted = true
102
103
    }

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    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()
        }
    }
121
122
123
    function getTrackYFromId(a_track) {
        return Logic.getTrackYFromId(a_track)
    }
124

125
126
127
128
    function getTrackYFromMltIndex(a_track) {
        return Logic.getTrackYFromMltIndex(a_track)
    }

129
130
131
132
    function getTracksCount() {
        return Logic.getTracksList()
    }

133
    function getMousePos() {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
134
        return (scrollView.flickableItem.contentX + tracksArea.mouseX) / timeline.scaleFactor
135
136
    }

137
138
139
140
141
142
143
144
    function getScrollPos() {
        return scrollView.flickableItem.contentX
    }

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

145
146
147
148
    function getCopiedItemId() {
        return copiedClip
    }

149
    function getMouseTrack() {
150
        return Logic.getTrackIdFromPos(tracksArea.mouseY - ruler.height + scrollView.flickableItem.contentY)
151
152
    }

153
154
155
    function getTrackColor(audio, header) {
        var col = activePalette.alternateBase
        if (audio) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
156
            col = Qt.tint(col, "#06FF00CC")
157
158
        }
        if (header) {
159
            col = Qt.darker(col, 1.05)
160
        }
161
        return col
162
163
    }

164
165
166
167
168
    function clearDropData() {
        clipBeingDroppedId = -1
        droppedPosition = -1
        droppedTrack = -1
        scrollTimer.running = false
169
        scrollTimer.stop()
170
    }
171

172
    function isDragging() {
173
        return dragInProgress
174
    }
175

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

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
    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
    }
214
215
216
217
218
219
    Keys.onDownPressed: {
        root.moveSelectedTrack(1)
    }
    Keys.onUpPressed: {
        root.moveSelectedTrack(-1)
    }
220

221
    property int headerWidth: timeline.headerWidth()
222
    property int activeTool: 0
223
    property real baseUnit: fontMetrics.font.pointSize
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
224
225
    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)
226
    property bool autoScrolling: timeline.autoScroll
227
    property int duration: timeline.duration
228
229
    property color audioColor: timeline.audioColor
    property color videoColor: timeline.videoColor
230
    property color lockedColor: timeline.lockedColor
Vincent Pinon's avatar
Vincent Pinon committed
231
    property color selectionColor: timeline.selectionColor
232
    property color groupColor: timeline.groupColor
Nicolas Carion's avatar
Nicolas Carion committed
233
    property int clipBeingDroppedId: -1
234
    property string clipBeingDroppedData
235
236
    property int droppedPosition: -1
    property int droppedTrack: -1
237
    property int clipBeingMovedId: -1
238
    property int spacerGroup: -1
239
240
    property int spacerFrame: -1
    property int spacerClickFrame: -1
241
    property real timeScale: timeline.scaleFactor
242
    property real snapping: (timeline.snap && (timeScale < 2 * baseUnit)) ? 10 / Math.sqrt(timeScale) - 0.5 : -1
243
    property var timelineSelection: timeline.selection
244
    property int trackHeight
245
    property int copiedClip: -1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
246
    property int zoomOnMouse: -1
247
    property int viewActiveTrack: timeline.activeTrack
248
249
    property int wheelAccumulatedDelta: 0
    readonly property int defaultDeltasPerStep: 120
250

251
    //onCurrentTrackChanged: timeline.selection = []
252
    onTimeScaleChanged: {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
253
        if (root.zoomOnMouse >= 0) {
254
            scrollView.flickableItem.contentX = Math.max(0, root.zoomOnMouse * timeline.scaleFactor - tracksArea.mouseX)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
255
256
257
258
            root.zoomOnMouse = -1
        } else {
            scrollView.flickableItem.contentX = Math.max(0, (timeline.seekPosition > -1 ? timeline.seekPosition : timeline.position) * timeline.scaleFactor - (scrollView.width / 2))
        }
259
        //root.snapping = timeline.snap ? 10 / Math.sqrt(root.timeScale) : -1
260
        ruler.adjustStepSize()
261
262
263
264
        if (dragProxy.draggedItem > -1 && dragProxy.masterObject) {
            // update dragged item pos
            dragProxy.masterObject.updateDrag()
        }
265
    }
266

267
268
269
270
271
    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) {
272
            scrollView.flickableItem.contentY = Math.min(trackHeaders.height - scrollView.height + scrollView.__horizontalScrollBar.height, tk.y - scrollView.height / 3)
273
274
275
        }
    }

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    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()
            }
        }
    }

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

                // if the clip insertion succeeded, request the clips to be grouped
                if (ids.length > 0) {
381
                    timeline.selectItems(ids)
382
383
384
385
386
387
                    id = ids[0]
                }
            }
            return id
        }

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

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

729
730
    Row {
        Column {
731
            id: headerContainer
732
733
734
735
736
737
738
            z: 1
            Rectangle {
                id: cornerstone
                property bool selected: false
                // Padding between toolbar and track headers.
                width: headerWidth
                height: ruler.height
739
                color: 'transparent' //selected? shotcutBlue : activePalette.window
740
741
742
743
744
745
                border.color: selected? 'red' : 'transparent'
                border.width: selected? 1 : 0
                z: 1
            }
            Flickable {
                // Non-slider scroll area for the track headers.
746
                id: headerFlick
747
                contentY: scrollView.flickableItem.contentY
748
                width: headerWidth
749
                height: 100
750
751
                interactive: false

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

                            onPressed: {
830
                                root.autoScrolling = false
831
832
                            }
                            onReleased: {
833
                                root.autoScrolling = timeline.autoScroll
834
835
836
837
838
839
840
841
842
843
844
845
                                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)
                                }
                            }
                        }
846
847
848
849
850
851
                    }
                }
            }
        }
        MouseArea {
            id: tracksArea
852
853
            property real clickX
            property real clickY
854
855
856
857
            width: root.width - headerWidth
            height: root.height
            // This provides continuous scrubbing and scimming at the left/right edges.
            hoverEnabled: true
858
            acceptedButtons: Qt.RightButton | Qt.LeftButton | Qt.MidButton
859
            cursorShape: tracksArea.mouseY < ruler.height || root.activeTool === 0 ? Qt.ArrowCursor : root.activeTool === 1 ? Qt.IBeamCursor : Qt.SplitHCursor
860
            onWheel: {
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
                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
                }
877
            }
878
            onPressed: {
879
                focus = true
880
                if (mouse.buttons === Qt.MidButton || (root.activeTool == 0 && mouse.modifiers & Qt.ControlModifier && !(mouse.modifiers & Qt.ShiftModifier))) {
881
882
883
884
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
885
                if (root.activeTool === 0 && mouse.modifiers & Qt.ShiftModifier && mouse.y > ruler.height) {
886
                        console.log('1111111111111\nREAL SHIFT PRESSED\n111111111111\n')
887
888
889
890
891
892
893
894
                        // 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) {
895
896
897
898
899
                    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)
                    }
900
901
902
903
                    if (dragProxy.draggedItem > -1) {
                        mouse.accepted = false
                        return
                    }
904
                    if (root.activeTool === 2 && mouse.y > ruler.height) {
905
                        // spacer tool
906
                        var y = mouse.y - ruler.height + scrollView.flickableItem.contentY
907
                        var frame = (scrollView.flickableItem.contentX + mouse.x) / timeline.scaleFactor
908
                        var track = (mouse.modifiers & Qt.ControlModifier) ? tracksRepeater.itemAt(Logic.getTrackIndexFromPos(y)).trackInternalId : -1
Jean-Baptiste Mardelle's avatar