Clip.qml 50 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
 * Copyright (c) 2013-2016 Meltytech, LLC
 * Author: Dan Dennedy <dan@dennedy.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

19
20
import QtQuick 2.11
import QtQuick.Controls 2.4
21
import Kdenlive.Controls 1.0
22
import QtQml.Models 2.11
23
import QtQuick.Window 2.2
24
import 'Timeline.js' as Logic
25
import com.enums 1.0
26
27
28

Rectangle {
    id: clipRoot
29
    property real timeScale: 1
30
    property string clipName: ''
31
    property string tagColor: ''
32
33
    property string clipResource: ''
    property string mltService: ''
34
    property string effectNames
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
35
    property int modelStart
36
    property int mixDuration: 0
37
    property int mixCut: 0
38
    property real scrollX: 0
39
40
41
    property int inPoint: 0
    property int outPoint: 0
    property int clipDuration: 0
42
    property int maxDuration: 0
43
    property bool isAudio: false
44
    property int audioChannels
45
    property int audioStream: -1
46
    property bool multiStream: false
47
    property int aStreamIndex: 0
48
    property bool showKeyframes: false
49
    property bool isGrabbed: false
50
    property bool grouped: false
51
    property var markers
52
    property var keyframeModel
53
    property int clipState: 0
54
55
    property int clipStatus: 0
    property int itemType: 0
56
57
    property int fadeIn: 0
    property int fadeOut: 0
58
    property int binId: 0
59
    property int positionOffset: 0
60
    property var parentTrack
61
62
    property int trackIndex //Index in track repeater
    property int clipId     //Id of the clip in the model
63
64
65
    property int trackId: -1 // Id of the parent track in the model
    property int fakeTid: -1
    property int fakePosition: 0
66
    property int originalTrackId: -1
67
    property int originalX: x
68
69
    property int originalDuration: clipDuration
    property int lastValidDuration: clipDuration
70
    property int draggedX: x
71
    property bool selected: false
72
    property bool isLocked: parentTrack && parentTrack.isLocked == true
73
    property bool hasAudio
74
75
    property bool canBeAudio
    property bool canBeVideo
76
    property double speed: 1.0
77
    property color borderColor: 'black'
78
    property bool forceReloadThumb
79
    property bool isComposition: false
80
    property bool hideClipViews: false
81
    property var groupTrimData
82
    property int scrollStart: scrollView.contentX - (clipRoot.modelStart * timeline.scaleFactor)
83
    property int mouseXPos: mouseArea.mouseX
84
    width : clipDuration * timeScale
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
85
    opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
86

87
88
    signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
    signal trimmedIn(var clip, bool shiftTrim, bool controlTrim)
89
    signal initGroupTrim(var clip)
90
91
    signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
    signal trimmedOut(var clip, bool shiftTrim, bool controlTrim)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
92

93
    onScrollStartChanged: {
94
        clipRoot.hideClipViews = scrollStart > (clipDuration * timeline.scaleFactor) || scrollStart + scrollView.width < 0
95
    }
96

97
98
    onIsGrabbedChanged: {
        if (clipRoot.isGrabbed) {
99
            grabItem()
100
101
        } else {
            mouseArea.focus = false
102
103
104
        }
    }

105
106
107
108
109
    function grabItem() {
        clipRoot.forceActiveFocus()
        mouseArea.focus = true
    }

110
111
    function clearAndMove(offset) {
        controller.requestClearSelection()
112
113
        controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true)
        controller.requestAddToSelection(clipRoot.clipId)
114
115
    }

116
    onClipResourceChanged: {
117
        if (itemType == ProducerType.Color) {
118
            color: Qt.darker(getColor(), 1.5)
119
120
        }
    }
121
    ToolTip {
122
        visible: mouseArea.containsMouse && !dragProxyArea.pressed && !trimInMouseArea.containsMouse && !trimOutMouseArea.containsMouse && !fadeInMouseArea.containsMouse && !fadeOutMouseArea.containsMouse && !fadeInMouseArea.drag.active && !fadeOutMouseArea.drag.active && !trimInMouseArea.drag.active && !trimOutMouseArea.drag.active
123
124
125
126
127
128
129
130
        delay: 1000
        timeout: 5000
        background: Rectangle {
            color: activePalette.alternateBase
            border.color: activePalette.light
        }
        contentItem: Label {
            color: activePalette.text
131
            font: miniFont
132
133
134
135
136
            text: '%1 (%2-%3)\n%4: %5'.arg(label.text)
                        .arg(timeline.simplifiedTC(clipRoot.inPoint))
                        .arg(timeline.simplifiedTC(clipRoot.outPoint))
                        .arg(i18n("Duration"))
                        .arg(timeline.simplifiedTC(clipRoot.clipDuration))
137
138
139
        }
    }

140
    onKeyframeModelChanged: {
141
        if (effectRow.keyframecanvas) {
142
            console.log('keyframe model changed............')
143
144
            effectRow.keyframecanvas.requestPaint()
        }
145
146
    }

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
147
148
    onClipDurationChanged: {
        width = clipDuration * timeScale;
149
150
        if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
            // Duration changed, we may need a different number of repeaters
151
            thumbsLoader.item.reload(1)
152
        }
153
    }
154

155
156
157
    onModelStartChanged: {
        x = modelStart * timeScale;
    }
158

159
160
161
162
163
    onFakePositionChanged: {
        x = fakePosition * timeScale;
    }
    onFakeTidChanged: {
        if (clipRoot.fakeTid > -1 && parentTrack) {
164
165
166
167
168
169
170
            if (clipRoot.parent != dragContainer) {
                var pos = clipRoot.mapToGlobal(clipRoot.x, clipRoot.y);
                clipRoot.parent = dragContainer
                pos = clipRoot.mapFromGlobal(pos.x, pos.y)
                clipRoot.x = pos.x
                clipRoot.y = pos.y
            }
171
            clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y
172
            clipRoot.height = Logic.getTrackById(clipRoot.fakeTid).height
173
        }
174
175
    }

176
    onForceReloadThumbChanged: {
177
        // TODO: find a way to force reload of clip thumbs
178
        if (thumbsLoader.item) {
179
            thumbsLoader.item.reload(0)
180
        }
181
182
    }

183
    onTimeScaleChanged: {
184
        x = modelStart * timeScale;
185
        width = clipDuration * timeScale;
186
        updateLabelOffset()
187
188
    }
    onScrollXChanged: {
189
190
191
192
193
194
        updateLabelOffset()
    }
    
    function updateLabelOffset()
    {
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : clipRoot.border.width
195
196
    }

197
    border.color: (clipStatus == ClipStatus.StatusMissing || ClipStatus == ClipStatus.StatusWaiting || clipStatus == ClipStatus.StatusDeleting) ? "#ff0000" : selected ? root.selectionColor : grouped ? root.groupColor : borderColor
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
198
    border.width: isGrabbed ? 8 : 2
199

200
201
202
203
204
    function updateDrag() {
        var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height)
        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
    }

205
    function getColor() {
206
        if (clipState == ClipState.Disabled) {
207
208
            return 'grey'
        }
209
210
211
        if (clipRoot.tagColor) {
            return clipRoot.tagColor
        }
212
213
214
        if (itemType == ProducerType.Text) {
            return titleColor
        }
215
        if (itemType == ProducerType.Image) {
216
217
            return imageColor
        }
218
219
220
        if (itemType == ProducerType.SlideShow) {
            return slideshowColor
        }
221
        if (itemType == ProducerType.Color) {
222
223
224
            var color = clipResource.substring(clipResource.length - 9)
            if (color[0] == '#') {
                return color
225
            }
226
            return '#' + color.substring(color.length - 8, color.length - 2)
227
        }
228
        return isAudio? root.audioColor : root.videoColor
229
230
    }

231
/*    function reparent(track) {
232
        console.log('TrackId: ',trackId)
233
234
        parent = track
        height = track.height
235
        parentTrack = track
236
        trackId = parentTrack.trackId
237
        console.log('Reparenting clip to Track: ', trackId)
238
        //generateWaveform()
239
    }
240
*/
241
    property bool noThumbs: (isAudio || itemType == ProducerType.Color || mltService === '')
242
    property string baseThumbPath: noThumbs ? '' : 'image://thumbnail/' + binId + '/' + documentId + '/#'
243

244
    DropArea { //Drop area for clips
245
246
247
        anchors.fill: clipRoot
        keys: 'kdenlive/effect'
        property string dropData
248
        property string dropSource
249
        property int dropRow: -1
250
251
        onEntered: {
            dropData = drag.getDataAsString('kdenlive/effect')
252
            dropSource = drag.getDataAsString('kdenlive/effectsource')
253
254
255
        }
        onDropped: {
            console.log("Add effect: ", dropData)
256
            if (dropSource == '') {
257
258
259
                // drop from effects list
                controller.addClipEffect(clipRoot.clipId, dropData);
            } else {
260
                controller.copyClipEffect(clipRoot.clipId, dropSource);
261
            }
262
            dropSource = ''
263
            dropRow = -1
264
            drag.acceptProposedAction
265
266
        }
    }
267
268
    MouseArea {
        id: mouseArea
269
        enabled: root.activeTool === 0
270
        anchors.fill: clipRoot
271
        acceptedButtons: Qt.RightButton
272
        hoverEnabled: root.activeTool === 0
273
        cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape
274
        onPressed: {
275
            root.autoScrolling = false
276
            root.mainItemId = clipRoot.clipId
277
            if (mouse.button == Qt.RightButton) {
278
                if (timeline.selection.indexOf(clipRoot.clipId) == -1) {
279
                    controller.requestAddToSelection(clipRoot.clipId, true)
280
                }
281
                root.mainFrame = Math.round(mouse.x / timeline.scaleFactor)
282
                root.showClipMenu(clipRoot.clipId)
283
                root.autoScrolling = timeline.autoScroll
284
            }
285
        }
286
287
288
        onReleased: {
            root.autoScrolling = timeline.autoScroll
        }
289
        Keys.onShortcutOverride: event.accepted = clipRoot.isGrabbed && (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down || event.key === Qt.Key_Escape)
290
        Keys.onLeftPressed: {
291
292
            var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true);
293
294
        }
        Keys.onRightPressed: {
295
296
            var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart + offset, true, true, true);
297
        }
298
299
300
301
302
303
        Keys.onUpPressed: {
            controller.requestClipMove(clipRoot.clipId, controller.getNextTrackId(clipRoot.trackId), clipRoot.modelStart, true, true, true);
        }
        Keys.onDownPressed: {
            controller.requestClipMove(clipRoot.clipId, controller.getPreviousTrackId(clipRoot.trackId), clipRoot.modelStart, true, true, true);
        }
304
305
        Keys.onEscapePressed: {
            timeline.grabCurrent()
306
            //focus = false
307
        }
308
        onPositionChanged: {
309
310
            var mapped = parentTrack.mapFromItem(clipRoot, mouse.x, mouse.y).x
            root.mousePosChanged(Math.round(mapped / timeline.scaleFactor))
311
        }
312
313
314
315
        onEntered: {
            var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height)
            initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
        }
316

317
318
        onExited: {
            endDrag()
319
        }
320
        onWheel: zoomByWheel(wheel)
321

322
        Loader {
323
            // Thumbs container
324
            id: thumbsLoader
325
            anchors.fill: parent
326
            anchors.leftMargin: parentTrack.isAudio ? 0 : clipRoot.border.width + mixContainer.width
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
327
            anchors.rightMargin: parentTrack.isAudio ? 0 : clipRoot.border.width
328
329
330
            anchors.topMargin: clipRoot.border.width
            anchors.bottomMargin: clipRoot.border.width
            clip: true
331
332
            asynchronous: true
            visible: status == Loader.Ready
333
            source: clipRoot.hideClipViews || clipRoot.itemType == 0 || clipRoot.itemType === ProducerType.Color ? "" : parentTrack.isAudio ? (timeline.showAudioThumbnails ? "ClipAudioThumbs.qml" : "") : timeline.showThumbnails ? "ClipThumbs.qml" : ""
334
        }
335

336
337
338
339
        Item {
            // Clipping container
            id: container
            anchors.fill: parent
340
341
            anchors.margins: clipRoot.border.width
            //clip: true
342
            property bool showDetails: (!clipRoot.selected || !effectRow.visible) && container.height > 2.2 * labelRect.height
343
            
344
            Item {
345
                // Mix indicator
346
                id: mixContainer
347
348
349
                anchors.left: parent.left
                anchors.top: parent.top
                anchors.bottom: parent.bottom
350
351
352
                width: clipRoot.mixDuration * clipRoot.timeScale
                
                Rectangle {
353
                    id: mixBackground
354
                    property double mixPos: mixBackground.width - clipRoot.mixCut * clipRoot.timeScale
355
356
357
358
359
                    anchors.top: parent.top
                    anchors.bottom: parent.bottom
                    anchors.left: parent.left
                    anchors.right: parent.right
                    visible: clipRoot.mixDuration > 0
360
                    color: "mediumpurple"
361
362
                    Loader {
                        id: shapeLoader
363
                        source: clipRoot.mixDuration > 0 ? "MixShape.qml" : ""
364
                        property bool valid: item !== null
365
                    }
366
367

                    opacity: mixArea.containsMouse || trimInMixArea.pressed || trimInMixArea.containsMouse || root.selectedMix == clipRoot.clipId ? 1 : 0.7
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
                    border.color: root.selectedMix == clipRoot.clipId ? root.selectionColor : "transparent"
                    border.width: 2
                    MouseArea {
                        // Mix click mouse area
                        id: mixArea
                        anchors.fill: parent
                        hoverEnabled: true
                        cursorShape: Qt.PointingHandCursor
                        onPressed: {
                            controller.requestMixSelection(clipRoot.clipId);
                        }
                    }
                    Rectangle {
                        id: mixCutPos
                        anchors.right: parent.right
                        anchors.rightMargin: clipRoot.mixCut * clipRoot.timeScale
                        anchors.top: parent.top
                        anchors.bottom: parent.bottom
                        width: 2
387
                        color: "navy"
388
                    }
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
                    MouseArea {
                        // Left resize handle
                        id: trimInMixArea
                        anchors.left: parent.left
                        anchors.leftMargin: clipRoot.mixDuration * clipRoot.timeScale
                        height: parent.height
                        width: root.baseUnit / 2
                        visible: root.activeTool === 0
                        property int previousMix
                        enabled: !isLocked && (pressed || clipRoot.width > 3 * width)
                        hoverEnabled: true
                        drag.target: trimInMixArea
                        drag.axis: Drag.XAxis
                        drag.smoothed: false
                        property bool sizeChanged: false
                        cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
                        onPressed: {
                            previousMix = clipRoot.mixDuration
                            root.autoScrolling = false
408
                            mixOut.color = 'darkorchid'
409
410
                            anchors.left = undefined
                            parent.anchors.right = undefined
411
                            mixCutPos.anchors.right = undefined
412
413
                        }
                        onReleased: {
414
                            controller.resizeStartMix(clipRoot.clipId, Math.round(Math.max(0, x) / clipRoot.timeScale), mouse.modifiers & Qt.ShiftModifier)
415
416
417
418
419
420
                            root.autoScrolling = timeline.autoScroll
                            if (sizeChanged) {
                                sizeChanged = false
                            }
                            anchors.left = parent.left
                            parent.anchors.right = mixContainer.right
421
                            mixBackground.anchors.bottom = mixContainer.bottom
422
                            mixOut.color = clipRoot.border.color
423
                            mixCutPos.anchors.right = mixCutPos.parent.right
424
425
426
427
428
429
430
431
432
433
                        }
                        onPositionChanged: {
                            if (mouse.buttons === Qt.LeftButton) {
                                var currentFrame = Math.round(x / clipRoot.timeScale)
                                if (currentFrame != previousMix) {
                                    parent.width = currentFrame * clipRoot.timeScale
                                    sizeChanged = true
                                    //TODO: resize mix's other clip
                                    //clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
                                }
434
435
436
437
438
439
                                if (x < mixCutPos.x) {
                                    // This will delete the mix
                                    mixBackground.anchors.bottom = mixContainer.top
                                } else {
                                    mixBackground.anchors.bottom = mixContainer.bottom
                                }
440
441
442
443
                            }
                        }
                        onEntered: {
                            if (!pressed) {
444
                                mixOut.color = 'darkorchid'
445
446
447
                            }
                        }
                        onExited: {
448
                            if (!pressed) {
449
                                mixOut.color = clipRoot.border.color
450
                            }
451
452
453
454
                        }
                        Rectangle {
                            id: mixOut
                            width: clipRoot.border.width
455
                            height: mixContainer.height
456
                            color: clipRoot.border.color
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
                            Drag.active: trimInMixArea.drag.active
                            Drag.proposedAction: Qt.MoveAction
                            visible: trimInMixArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)

                            ToolTip {
                                visible: trimInMixArea.containsMouse && !trimInMixArea.pressed
                                delay: 1000
                                timeout: 5000
                                background: Rectangle {
                                    color: activePalette.alternateBase
                                    border.color: activePalette.light
                                }
                                contentItem: Label {
                                    color: activePalette.text
                                    font: miniFont
                                    text: i18n("Mix:%1", timeline.simplifiedTC(clipRoot.mixDuration))
                                }
                            }
                        }
476
477
                    }
                }
478
                
479
            }
480
481

            Repeater {
482
                // Clip markers
483
484
485
486
                model: markers
                delegate:
                Item {
                    anchors.fill: parent
487
                    visible: markerBase.x >= 0 && markerBase.x < clipRoot.width
488
489
490
491
                    Rectangle {
                        id: markerBase
                        width: 1
                        height: parent.height
492
                        x: clipRoot.speed < 0 ? (clipRoot.maxDuration - clipRoot.inPoint) * timeScale + (Math.round(model.frame / clipRoot.speed)) * timeScale - clipRoot.border.width : (Math.round(model.frame / clipRoot.speed) - clipRoot.inPoint) * timeScale - clipRoot.border.width;
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
                        color: model.color
                    }
                    Rectangle {
                        visible: mlabel.visible
                        opacity: 0.7
                        x: markerBase.x
                        radius: 2
                        width: mlabel.width + 4
                        height: mlabel.height
                        anchors {
                            bottom: parent.verticalCenter
                        }
                        color: model.color
                        MouseArea {
                            z: 10
                            anchors.fill: parent
                            acceptedButtons: Qt.LeftButton
                            cursorShape: Qt.PointingHandCursor
                            hoverEnabled: true
512
                            onDoubleClicked: timeline.editMarker(clipRoot.clipId, model.frame)
513
                            onClicked: proxy.position = clipRoot.modelStart + (clipRoot.speed < 0 ? (clipRoot.maxDuration - clipRoot.inPoint) * timeScale + (Math.round(model.frame / clipRoot.speed)) : (Math.round(model.frame / clipRoot.speed) - clipRoot.inPoint))
514
515
                        }
                    }
516
517
518
519
520
521
522
                    TextMetrics {
                        id: textMetrics
                        font: miniFont
                        text: model.comment
                        elide: root.timeScale > 1 ? Text.ElideNone : Text.ElideRight
                        elideWidth: root.maxLabelWidth
                    }
523
524
                    Text {
                        id: mlabel
525
526
                        visible: timeline.showMarkers && textMetrics.elideWidth > root.baseUnit
                        text: textMetrics.elidedText
527
                        font: miniFont
528
529
530
531
532
533
534
535
                        x: markerBase.x
                        anchors {
                            bottom: parent.verticalCenter
                            topMargin: 2
                            leftMargin: 2
                        }
                        color: 'white'
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
536
                }
537
538
            }

539
540
541
            MouseArea {
                // Left resize handle
                id: trimInMouseArea
542
                x: -clipRoot.border.width
543
544
                height: parent.height
                width: root.baseUnit / 2
545
                visible: root.activeTool === 0
546
                enabled: !isLocked && (pressed || clipRoot.width > 3 * width)
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
                hoverEnabled: true
                drag.target: trimInMouseArea
                drag.axis: Drag.XAxis
                drag.smoothed: false
                property bool shiftTrim: false
                property bool controlTrim: false
                property bool sizeChanged: false
                cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
                onPressed: {
                    root.autoScrolling = false
                    clipRoot.originalX = clipRoot.x
                    clipRoot.originalDuration = clipDuration
                    shiftTrim = mouse.modifiers & Qt.ShiftModifier
                    controlTrim = mouse.modifiers & Qt.ControlModifier
                    if (!shiftTrim && clipRoot.grouped) {
                        clipRoot.initGroupTrim(clipRoot)
                    }
                    trimIn.opacity = 0
                }
                onReleased: {
                    root.autoScrolling = timeline.autoScroll
568
                    x = -clipRoot.border.width
569
570
571
                    if (sizeChanged) {
                        clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim)
                        sizeChanged = false
572
                        updateDrag()
573
574
                    }
                }
575
576
577
                onDoubleClicked: {
                    timeline.mixClip(clipRoot.clipId, -1)
                }
578
579
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
580
581
582
                        var currentFrame = Math.round((clipRoot.x + (x + clipRoot.border.width)) / timeScale)
                        var currentClipPos = clipRoot.modelStart
                        var delta = currentFrame - currentClipPos
583
                        if (delta !== 0) {
584
                            if (maxDuration > 0 && delta < -inPoint && !(mouse.modifiers & Qt.ControlModifier)) {
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
                                delta = -inPoint
                            }
                            var newDuration =  clipDuration - delta
                            sizeChanged = true
                            clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
                        }
                    }
                }
                onEntered: {
                    if (!pressed) {
                        trimIn.opacity = 1
                    }
                }
                onExited: {
                    trimIn.opacity = 0
                }
                Rectangle {
                    id: trimIn
                    anchors.left: parent.left
                    width: clipRoot.border.width
                    height: parent.height
                    color: 'lawngreen'
                    opacity: 0
                    Drag.active: trimInMouseArea.drag.active
                    Drag.proposedAction: Qt.MoveAction
610
                    visible: trimInMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)
611
612
613
614
615
616
617
618
619
620
621

                    ToolTip {
                        visible: trimInMouseArea.containsMouse && !trimInMouseArea.pressed
                        delay: 1000
                        timeout: 5000
                        background: Rectangle {
                            color: activePalette.alternateBase
                            border.color: activePalette.light
                        }
                        contentItem: Label {
                            color: activePalette.text
622
                            font: miniFont
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
                            text: i18n("In:%1\nPosition:%2", timeline.simplifiedTC(clipRoot.inPoint),timeline.simplifiedTC(clipRoot.modelStart))
                        }
                    }
                }
            }

            MouseArea {
                // Right resize handle
                id: trimOutMouseArea
                anchors.right: parent.right
                anchors.rightMargin: -clipRoot.border.width
                anchors.top: parent.top
                height: parent.height
                width: root.baseUnit / 2
                hoverEnabled: true
638
                visible: root.activeTool === 0
639
                enabled: !isLocked && (pressed || clipRoot.width > 3 * width)
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
                property bool shiftTrim: false
                property bool controlTrim: false
                property bool sizeChanged: false
                cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
                drag.target: trimOutMouseArea
                drag.axis: Drag.XAxis
                drag.smoothed: false

                onPressed: {
                    root.autoScrolling = false
                    clipRoot.originalDuration = clipDuration
                    anchors.right = undefined
                    shiftTrim = mouse.modifiers & Qt.ShiftModifier
                    controlTrim = mouse.modifiers & Qt.ControlModifier
                    if (!shiftTrim && clipRoot.grouped) {
                        clipRoot.initGroupTrim(clipRoot)
                    }
                    trimOut.opacity = 0
                }
                onReleased: {
                    root.autoScrolling = timeline.autoScroll
                    anchors.right = parent.right
                    if (sizeChanged) {
                        clipRoot.trimmedOut(clipRoot, shiftTrim, controlTrim)
                        sizeChanged = false
665
                        updateDrag()
666
667
                    }
                }
668
669
670
                onDoubleClicked: {
                    timeline.mixClip(clipRoot.clipId, 1)
                }
671
672
673
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
                        var newDuration = Math.round((x + width) / timeScale)
674
                        if (maxDuration > 0 && (newDuration > maxDuration - inPoint) && !(mouse.modifiers & Qt.ControlModifier)) {
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
                            newDuration = maxDuration - inPoint
                        }
                        if (newDuration != clipDuration) {
                            sizeChanged = true
                            clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
                        }
                    }
                }
                onEntered: {
                    if (!pressed) {
                        trimOut.opacity = 1
                    }
                }
                onExited: trimOut.opacity = 0
                ToolTip {
                    visible: trimOutMouseArea.containsMouse && !trimOutMouseArea.pressed
                    delay: 1000
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
699
                        font: miniFont
700
701
702
703
704
705
706
707
708
709
710
711
                        text: i18n("Out: ") + timeline.simplifiedTC(clipRoot.outPoint)
                    }
                }
                Rectangle {
                    id: trimOut
                    anchors.right: parent.right
                    width: clipRoot.border.width
                    height: parent.height
                    color: 'red'
                    opacity: 0
                    Drag.active: trimOutMouseArea.drag.active
                    Drag.proposedAction: Qt.MoveAction
712
                    visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
                }
            }

            TimelineTriangle {
                // Green fade in triangle
                id: fadeInTriangle
                fillColor: 'green'
                width: Math.min(clipRoot.fadeIn * timeScale, container.width)
                height: parent.height
                anchors.left: parent.left
                anchors.top: parent.top
                opacity: 0.4
            }

            TimelineTriangle {
                // Red fade out triangle
                id: fadeOutCanvas
                fillColor: 'red'
                width: Math.min(clipRoot.fadeOut * timeScale, container.width)
                height: parent.height
                anchors.right: parent.right
                anchors.top: parent.top
                opacity: 0.4
                transform: Scale { xScale: -1; origin.x: fadeOutCanvas.width / 2}
            }

            Item {
                // Clipping container for clip names
                anchors.fill: parent
742
                anchors.leftMargin: mixContainer.width
743
744
745
746
747
                clip: true
                Rectangle {
                    // Clip name background
                    id: labelRect
                    color: clipRoot.selected ? 'darkred' : '#66000000'
748
                    x: clipRoot.border.width
749
750
751
752
753
754
755
                    y: 0
                    width: label.width + 2 * clipRoot.border.width
                    height: label.height
                    visible: clipRoot.width > width / 2
                    Text {
                        // Clip name text
                        id: label
756
                        property string clipNameString: (clipRoot.isAudio && clipRoot.multiStream) ? ((clipRoot.audioStream > 10000 ? 'Merged' : clipRoot.aStreamIndex) + '|' + clipName ) : clipName
757
                        text: (clipRoot.speed != 1.0 ? ('[' + Math.round(clipRoot.speed*100) + '%] ') : '') + clipNameString
758
                        font: miniFont
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
                        anchors {
                            top: labelRect.top
                            left: labelRect.left
                            leftMargin: clipRoot.border.width
                        }
                        color: 'white'
                        //style: Text.Outline
                        //styleColor: 'black'
                    }
                }

                Rectangle {
                    // Offset info
                    id: offsetRect
                    color: 'darkgreen'
                    width: offsetLabel.width + radius
                    height: offsetLabel.height
                    radius: height/3
                    x: labelRect.width + 4
                    y: 2
                    visible: labelRect.visible && positionOffset != 0
                    MouseArea {
                        id: offsetArea
                        hoverEnabled: true
                        cursorShape: Qt.PointingHandCursor
                        anchors.fill: parent
                        onClicked: {
                            clearAndMove(positionOffset)
                        }
                        ToolTip {
                            visible: offsetArea.containsMouse
                            delay: 1000
                            timeout: 5000
                            background: Rectangle {
                                color: activePalette.alternateBase
                                border.color: activePalette.light
                            }
                            contentItem: Label {
                                color: activePalette.text
798
                                font: miniFont
799
800
801
802
803
804
                                text: positionOffset < 0 ? i18n("Offset: -%1", timeline.simplifiedTC(-positionOffset)) : i18n("Offset: %1", timeline.simplifiedTC(positionOffset))
                            }
                        }
                        Text {
                            id: offsetLabel
                            text: positionOffset
805
                            font: miniFont
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
                            anchors {
                                horizontalCenter: parent.horizontalCenter
                                topMargin: 1
                                leftMargin: 1
                            }
                            color: 'white'
                            style: Text.Outline
                            styleColor: 'black'
                        }
                    }
                }

                Rectangle {
                    // effect names background
                    id: effectsRect
                    color: '#555555'
                    width: effectLabel.width + 2
                    height: effectLabel.height
                    x: labelRect.x
                    anchors.top: labelRect.bottom
                    visible: labelRect.visible && clipRoot.effectNames != '' && container.showDetails
                    Text {
                        // Effect names text
                        id: effectLabel
                        text: clipRoot.effectNames
831
                        font: miniFont
832
833
834
835
836
837
838
839
840
841
842
                        visible: effectsRect.visible
                        anchors {
                            top: effectsRect.top
                            left: effectsRect.left
                            leftMargin: 1
                            // + ((isAudio || !settings.timelineShowThumbnails) ? 0 : inThumbnail.width) + 1
                        }
                        color: 'white'
                        //style: Text.Outline
                        styleColor: 'black'
                    }
843
               }
844
845
846
               Rectangle{
                    //proxy 
                    id:proxyRect
847
                    color: '#fdbc4b'
848
849
850
851
852
                    width: labelRect.height
                    height: labelRect.height
                    x: labelRect.x
                    anchors.top: labelRect.top
                    anchors.left: labelRect.right
853
                    visible: !clipRoot.isAudio && clipRoot.clipStatus == ClipStatus.StatusProxy || clipRoot.clipStatus == ClipStatus.StatusProxyOnly
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
                    Text {
                        // Proxy P
                        id: proxyLabel
                        text: "P"
                        font.pointSize: root.fontUnit +1
                        visible: proxyRect.visible
                        anchors {
                            top: proxyRect.top
                            left: proxyRect.left
                            leftMargin: (labelRect.height-proxyLabel.width)/2
                            topMargin: (labelRect.height-proxyLabel.height)/2
                        }
                        color: 'black'
                        styleColor: 'black'
                    }
                }
870
            }
871
872
873
874
875
876
877
878
879
880
881
882

            KeyframeView {
                id: effectRow
                clip: true
                anchors.fill: parent
                visible: clipRoot.showKeyframes && clipRoot.keyframeModel
                selected: clipRoot.selected
                inPoint: clipRoot.inPoint
                outPoint: clipRoot.outPoint
                masterObject: clipRoot
                kfrModel: clipRoot.hideClipViews ? 0 : clipRoot.keyframeModel
            }
883
        }
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915

        states: [
            State {
                name: 'locked'
                when: isLocked
                PropertyChanges {
                    target: clipRoot
                    color: root.lockedColor
                    opacity: 0.8
                    z: 0
                }
            },
            State {
                name: 'normal'
                when: clipRoot.selected === false
                PropertyChanges {
                    target: clipRoot
                    color: Qt.darker(getColor(), 1.5)
                    z: 0
                }
            },
            State {
                name: 'selected'
                when: clipRoot.selected === true
                PropertyChanges {
                    target: clipRoot
                    color: getColor()
                    z: 3
                }
            }
        ]

916
917
918
        MouseArea {
            // Add start composition area
            id: compInArea
919
920
            anchors.left: parent.left
            anchors.bottom: parent.bottom
921
            width: Math.min(root.baseUnit, container.height / 3)
922
            height: width
923
924
925
926
927
928
929
930
931
932
933
934
935
936
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            visible: !clipRoot.isAudio
            enabled: !clipRoot.isAudio && dragProxy.draggedItem === clipRoot.clipId && compositionIn.visible
            onPressed: {
                timeline.addCompositionToClip('', clipRoot.clipId, 0)
            }
            ToolTip {
                visible: compInArea.containsMouse && !dragProxyArea.pressed
                delay: 1000
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
937
                }
938
939
                contentItem: Label {
                    color: activePalette.text
940
                    font: miniFont
941
                    text: i18n("Click to add composition")
942
943
                }
            }
944
945
946
947
948
            Rectangle {
                // Start composition box
                id: compositionIn
                anchors.bottom: parent.bottom
                anchors.left: parent.left
949
                width: compInArea.containsMouse ? parent.width : 5
950
951
952
953
954
955
956
957
                height: width
                radius: width / 2
                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !dragProxyArea.pressed
                color: Qt.darker('mediumpurple')
                border.width: 3
                border.color: 'mediumpurple'
                Behavior on width { NumberAnimation { duration: 100 } }
            }
958
        }
959
960
961
962

        MouseArea {
            // Add end composition area
            id: compOutArea
963
964
            anchors.right: parent.right
            anchors.bottom: parent.bottom
965
            width: Math.min(root.baseUnit, container.height / 3)
966
            height: width
967
968
969
970
971
972
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            enabled: !clipRoot.isAudio && dragProxy.draggedItem === clipRoot.clipId && compositionOut.visible
            visible: !clipRoot.isAudio
            onPressed: {
                timeline.addCompositionToClip('', clipRoot.clipId, clipRoot.clipDuration - 1)
973
            }
974
            ToolTip {
975
                visible: compOutArea.containsMouse && !dragProxyArea.pressed
976
977
978
979
980
981
982
983
                delay: 1000
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
                }
                contentItem: Label {
                    color: activePalette.text
984
                    font: miniFont
985
                    text: i18n("Click to add composition")
986
987
                }
            }
988
989
990
991
992
            Rectangle {
                // End composition box
                id: compositionOut
                anchors.bottom: parent.bottom
                anchors.right: parent.right
993
                width: compOutArea.containsMouse ? parent.height : 5
994
995
996
997
998
999
1000
                height: width
                radius: width / 2
                visible: clipRoot.width > 4 * parent.width && mouseArea.containsMouse && !dragProxyArea.pressed
                color: Qt.darker('mediumpurple')
                border.width: 3
                border.color: 'mediumpurple'
                Behavior on width { NumberAnimation { duration: 100 } }
1001
1002
            }
        }
1003
1004
1005

        MouseArea {
            // Fade out drag zone
1006
            id: fadeOutMouseArea
1007
1008
1009
            anchors.right: parent.right
            anchors.rightMargin: clipRoot.fadeOut <= 0 ? 0 : fadeOutCanvas.width - width / 2
            anchors.top: parent.top
1010
            width: Math.min(root.baseUnit, container.height / 3)
1011
            height: width
1012
            hoverEnabled: true
1013
1014
            cursorShape: Qt.PointingHandCursor
            drag.target: fadeOutMouseArea
1015
            drag.axis: Drag.XAxis
1016
            drag.minimumX: - Math.ceil(width / 2)
1017
1018
            drag.maximumX: container.width + Math.ceil(width / 4)
            visible: clipRoot.width > 3 * width && mouseArea.containsMouse && !dragProxyArea.pressed
1019
1020
            property int startFadeOut
            property int lastDuration: -1
1021
1022
1023
1024
            drag.smoothed: false
            onClicked: {
                if (clipRoot.fadeOut == 0) {
                    timeline.adjustFade(clipRoot.clipId, 'fadeout', 0, -2)
1025
1026
                }
            }
1027
            onPressed: {
1028
                root.autoScrolling = false
1029
1030
                startFadeOut = clipRoot.fadeOut
                anchors.right = undefined
1031
                fadeOutCanvas.opacity = 0.6
1032
1033
            }
            onReleased: {
1034
                fadeOutCanvas.opacity = 0.4