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

Rectangle {
    id: root
    objectName: "timelineview"
    SystemPalette { id: activePalette }
    color: activePalette.window
16
    property bool validMenu: false
17
    property color textColor: activePalette.text
18
    property bool dragInProgress: dragProxyArea.pressed || dragProxyArea.drag.active
19

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

726
727
    Row {
        Column {
728
            id: headerContainer
729
730
731
732
733
734
735
            z: 1
            Rectangle {
                id: cornerstone
                property bool selected: false
                // Padding between toolbar and track headers.
                width: headerWidth
                height: ruler.height
736
                color: 'transparent' //selected? shotcutBlue : activePalette.window
737
738
739
                border.color: selected? 'red' : 'transparent'
                border.width: selected? 1 : 0
                z: 1
740
741
742
743
744
                Button {
                    text: parent.width > metrics.boundingRect.width * 1.4 ? metrics.text : "M"
                    anchors.fill: parent
                    anchors.leftMargin: 2
                    anchors.rightMargin: 2
745
746
747
748
                    ToolTip.delay: 1000
                    ToolTip.timeout: 5000
                    ToolTip.visible: hovered
                    ToolTip.text: i18n("Show master effects")
749
750
751
752
                    TextMetrics {
                        id: metrics
                        text: i18n("Master")
                    }
753
754
755
756
                    onClicked: {
                        timeline.showMasterEffects()
                    }
                }
757
758
759
            }
            Flickable {
                // Non-slider scroll area for the track headers.
760
                id: headerFlick
761
                contentY: scrollView.flickableItem.contentY
762
                width: headerWidth
763
                height: 100
764
765
                interactive: false

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

                            onPressed: {
844
                                root.autoScrolling = false
845
846
                            }
                            onReleased: {
847
                                root.autoScrolling = timeline.autoScroll
848
849
850
851
852
853
854
855
856
857
858
859
                                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)
                                }
                            }
                        }
860
861
862
863
864
865
                    }
                }
            }
        }
        MouseArea {
            id: tracksArea
866
867
            property real clickX
            property real clickY
868
869
870
871
            width: root.width - headerWidth
            height: root.height
            // This provides continuous scrubbing and scimming at the left/right edges.
            hoverEnabled: true
872
            acceptedButtons: Qt.RightButton | Qt.LeftButton | Qt.MidButton
873
            cursorShape: root.activeTool === 0 ? Qt.ArrowCursor : root.activeTool === 1 ? Qt.IBeamCursor : Qt.SplitHCursor
874
            onWheel: {
875
876
877
878
879
880
881
882
883
884
                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) {
885
                        timeline.position = Math.min(timeline.seekPosition - (wheel.angleDelta.y > 0 ? delta : -delta), timeline.fullDuration - 1)
886
                    } else {
887
                        timeline.position = Math.min(timeline.position - (wheel.angleDelta.y > 0 ? delta : -delta), timeline.fullDuration - 1)
888
889
                    }
                }
890
            }
891
            onPressed: {
892
                focus = true
893
                if (mouse.buttons === Qt.MidButton || (root.activeTool == 0 && mouse.modifiers & Qt.ControlModifier && !(mouse.modifiers & Qt.ShiftModifier))) {
894
895
896
897
                    clickX = mouseX
                    clickY = mouseY
                    return
                }
898
                if (root.activeTool === 0 && mouse.modifiers & Qt.ShiftModifier && mouse.y > ruler.height) {
899
                        console.log('1111111111111\nREAL SHIFT PRESSED\n111111111111\n')
900
901
902
903
904
905
906
907
                        // 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