Clip.qml 51.1 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 int scrollStart: scrollView.contentX - (clipRoot.modelStart * timeline.scaleFactor)
82
    property int mouseXPos: mouseArea.mouseX
83
    width : clipDuration * timeScale
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
84
    opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
85

86
87
    signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
    signal trimmedIn(var clip, bool shiftTrim, bool controlTrim)
88
    signal initGroupTrim(var clip)
89
90
    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
91

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

96
97
    onIsGrabbedChanged: {
        if (clipRoot.isGrabbed) {
98
            grabItem()
99
        } else {
100
            timeline.showToolTip()
101
            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

122
    onKeyframeModelChanged: {
123
        if (effectRow.keyframecanvas) {
124
            console.log('keyframe model changed............')
125
126
            effectRow.keyframecanvas.requestPaint()
        }
127
128
    }

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
129
    onClipDurationChanged: {
130
        width = clipDuration * timeScale
131
132
        if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
            // Duration changed, we may need a different number of repeaters
133
            thumbsLoader.item.reload(1)
134
        }
135
    }
136

137
138
139
    onModelStartChanged: {
        x = modelStart * timeScale;
    }
140

141
142
143
144
145
    onFakePositionChanged: {
        x = fakePosition * timeScale;
    }
    onFakeTidChanged: {
        if (clipRoot.fakeTid > -1 && parentTrack) {
146
147
148
149
150
151
152
            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
            }
153
            clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y
154
            clipRoot.height = Logic.getTrackById(clipRoot.fakeTid).height
155
        }
156
157
    }

158
    onForceReloadThumbChanged: {
159
        // TODO: find a way to force reload of clip thumbs
160
        if (thumbsLoader.item) {
161
            thumbsLoader.item.reload(0)
162
        }
163
164
    }

165
    onTimeScaleChanged: {
166
        x = modelStart * timeScale;
167
        width = clipDuration * timeScale;
168
        updateLabelOffset()
169
170
    }
    onScrollXChanged: {
171
172
173
174
175
        updateLabelOffset()
    }
    
    function updateLabelOffset()
    {
176
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
177
178
    }

179
    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
180
    border.width: isGrabbed ? 8 : 2
181

182
183
184
185
    function updateDrag() {
        var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height)
        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
    }
186
187
188
189
190
191
192
193
194
    
    function showClipInfo() {
        var text = i18n("%1 (%2-%3), Position: %4, Duration: %5".arg(clipRoot.clipName)
                        .arg(timeline.simplifiedTC(clipRoot.inPoint))
                        .arg(timeline.simplifiedTC(clipRoot.outPoint))
                        .arg(timeline.simplifiedTC(clipRoot.modelStart))
                        .arg(timeline.simplifiedTC(clipRoot.clipDuration)))
        timeline.showToolTip(text)
    }
195

196
    function getColor() {
197
198
        if (clipRoot.clipState == ClipState.Disabled) {
            return '#888'
199
        }
200
201
202
        if (clipRoot.tagColor) {
            return clipRoot.tagColor
        }
203
204
205
        if (itemType == ProducerType.Text) {
            return titleColor
        }
206
        if (itemType == ProducerType.Image) {
207
208
            return imageColor
        }
209
210
211
        if (itemType == ProducerType.SlideShow) {
            return slideshowColor
        }
212
        if (itemType == ProducerType.Color) {
213
214
215
            var color = clipResource.substring(clipResource.length - 9)
            if (color[0] == '#') {
                return color
216
            }
217
            return '#' + color.substring(color.length - 8, color.length - 2)
218
        }
219
        return isAudio? root.audioColor : root.videoColor
220
221
    }

222
/*    function reparent(track) {
223
        console.log('TrackId: ',trackId)
224
225
        parent = track
        height = track.height
226
        parentTrack = track
227
        trackId = parentTrack.trackId
228
        console.log('Reparenting clip to Track: ', trackId)
229
        //generateWaveform()
230
    }
231
*/
232
    property bool noThumbs: (isAudio || itemType == ProducerType.Color || mltService === '')
233
    property string baseThumbPath: noThumbs ? '' : 'image://thumbnail/' + binId + '/' + documentId + '/#'
234

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

313
314
        onExited: {
            endDrag()
315
316
317
            if (!trimInMouseArea.containsMouse && !trimOutMouseArea.containsMouse && !compInArea.containsMouse && !compOutArea.containsMouse) {
                timeline.showToolTip()
            }
318
        }
319
        onWheel: zoomByWheel(wheel)
320

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

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

                    opacity: mixArea.containsMouse || trimInMixArea.pressed || trimInMixArea.containsMouse || root.selectedMix == clipRoot.clipId ? 1 : 0.7
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
                    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
386
                        color: "navy"
387
                    }
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
                    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
407
                            mixOut.color = 'darkorchid'
408
409
                            anchors.left = undefined
                            parent.anchors.right = undefined
410
                            mixCutPos.anchors.right = undefined
411
412
                        }
                        onReleased: {
413
                            controller.resizeStartMix(clipRoot.clipId, Math.round(Math.max(0, x) / clipRoot.timeScale), mouse.modifiers & Qt.ShiftModifier)
414
415
416
417
418
419
                            root.autoScrolling = timeline.autoScroll
                            if (sizeChanged) {
                                sizeChanged = false
                            }
                            anchors.left = parent.left
                            parent.anchors.right = mixContainer.right
420
                            mixBackground.anchors.bottom = mixContainer.bottom
421
                            mixOut.color = clipRoot.border.color
422
                            mixCutPos.anchors.right = mixCutPos.parent.right
423
424
425
426
427
428
429
430
431
432
                        }
                        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)
                                }
433
434
435
436
437
438
                                if (x < mixCutPos.x) {
                                    // This will delete the mix
                                    mixBackground.anchors.bottom = mixContainer.top
                                } else {
                                    mixBackground.anchors.bottom = mixContainer.bottom
                                }
439
440
441
442
                            }
                        }
                        onEntered: {
                            if (!pressed) {
443
                                mixOut.color = 'darkorchid'
444
                                timeline.showToolTip(i18n("Mix:%1", timeline.simplifiedTC(clipRoot.mixDuration)))
445
446
447
                            }
                        }
                        onExited: {
448
                            if (!pressed) {
449
                                mixOut.color = clipRoot.border.color
450
                                timeline.showToolTip()
451
                            }
452
453
454
455
                        }
                        Rectangle {
                            id: mixOut
                            width: clipRoot.border.width
456
                            height: mixContainer.height
457
                            color: clipRoot.border.color
458
459
460
461
                            Drag.active: trimInMixArea.drag.active
                            Drag.proposedAction: Qt.MoveAction
                            visible: trimInMixArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)
                        }
462
463
                    }
                }
464
                
465
            }
466
467

            Repeater {
468
                // Clip markers
469
470
471
472
                model: markers
                delegate:
                Item {
                    anchors.fill: parent
473
                    visible: markerBase.x >= 0 && markerBase.x < clipRoot.width
474
475
476
477
                    Rectangle {
                        id: markerBase
                        width: 1
                        height: parent.height
478
                        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;
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
                        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
498
                            onDoubleClicked: timeline.editMarker(clipRoot.clipId, model.frame)
499
                            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))
500
501
                        }
                    }
502
503
504
505
506
507
508
                    TextMetrics {
                        id: textMetrics
                        font: miniFont
                        text: model.comment
                        elide: root.timeScale > 1 ? Text.ElideNone : Text.ElideRight
                        elideWidth: root.maxLabelWidth
                    }
509
510
                    Text {
                        id: mlabel
511
512
                        visible: timeline.showMarkers && textMetrics.elideWidth > root.baseUnit
                        text: textMetrics.elidedText
513
                        font: miniFont
514
515
516
517
518
519
520
521
                        x: markerBase.x
                        anchors {
                            bottom: parent.verticalCenter
                            topMargin: 2
                            leftMargin: 2
                        }
                        color: 'white'
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
522
                }
523
524
            }

525
526
527
            MouseArea {
                // Left resize handle
                id: trimInMouseArea
528
                x: -clipRoot.border.width
529
530
                height: parent.height
                width: root.baseUnit / 2
531
                visible: root.activeTool === 0
532
                enabled: !isLocked && (pressed || clipRoot.width > 3 * width)
533
534
535
536
537
538
539
                hoverEnabled: true
                drag.target: trimInMouseArea
                drag.axis: Drag.XAxis
                drag.smoothed: false
                property bool shiftTrim: false
                property bool controlTrim: false
                property bool sizeChanged: false
540
                cursorShape: (containsMouse || pressed ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
541
542
543
544
545
546
547
548
549
550
551
552
553
                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
554
                    x = -clipRoot.border.width
555
556
557
                    if (sizeChanged) {
                        clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim)
                        sizeChanged = false
558
559
560
561
562
                        if (!controlTrim) {
                            updateDrag()
                        } else {
                            endDrag()
                        }
563
564
                    }
                }
565
566
567
                onDoubleClicked: {
                    timeline.mixClip(clipRoot.clipId, -1)
                }
568
569
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
570
571
572
                        var currentFrame = Math.round((clipRoot.x + (x + clipRoot.border.width)) / timeScale)
                        var currentClipPos = clipRoot.modelStart
                        var delta = currentFrame - currentClipPos
573
                        if (delta !== 0) {
574
                            if (maxDuration > 0 && delta < -inPoint && !(mouse.modifiers & Qt.ControlModifier)) {
575
576
577
578
579
580
581
582
583
584
585
                                delta = -inPoint
                            }
                            var newDuration =  clipDuration - delta
                            sizeChanged = true
                            clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
                        }
                    }
                }
                onEntered: {
                    if (!pressed) {
                        trimIn.opacity = 1
586
587
                        var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height)
                        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
588
589
                        var s = i18n("In:%1, Position:%2", timeline.simplifiedTC(clipRoot.inPoint),timeline.simplifiedTC(clipRoot.modelStart))
                        timeline.showToolTip(s)
590
                        timeline.showKeyBinding(i18n("<b>Ctrl drag</b> to change speed, <b>Double click</b> to mix with adjacent clip"))
591
592
593
594
                    }
                }
                onExited: {
                    trimIn.opacity = 0
595
596
597
598
                    if (!pressed) {
                        if (!mouseArea.containsMouse) {
                            timeline.showToolTip()
                        } else {
599
                            clipRoot.showClipInfo()
600
                        }
601
602
603
                        if (!fadeInMouseArea.containsMouse) {
                            timeline.showKeyBinding()
                        }
604
                    }
605
606
607
608
609
610
611
612
613
614
                }
                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
615
                    visible: trimInMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)
616

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

            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
643
                visible: root.activeTool === 0
644
                enabled: !isLocked && (pressed || clipRoot.width > 3 * width)
645
646
647
                property bool shiftTrim: false
                property bool controlTrim: false
                property bool sizeChanged: false
648
                cursorShape: (containsMouse || pressed ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
                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
670
671
672
673
674
                        if (!controlTrim) {
                            updateDrag()
                        } else {
                            endDrag()
                        }
675
676
                    }
                }
677
678
679
                onDoubleClicked: {
                    timeline.mixClip(clipRoot.clipId, 1)
                }
680
681
682
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
                        var newDuration = Math.round((x + width) / timeScale)
683
                        if (maxDuration > 0 && (newDuration > maxDuration - inPoint) && !(mouse.modifiers & Qt.ControlModifier)) {
684
685
686
687
688
689
690
691
692
693
694
                            newDuration = maxDuration - inPoint
                        }
                        if (newDuration != clipDuration) {
                            sizeChanged = true
                            clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
                        }
                    }
                }
                onEntered: {
                    if (!pressed) {
                        trimOut.opacity = 1
695
696
                        var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height)
                        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
697
698
                        var s = i18n("Out:%1, Position:%2", timeline.simplifiedTC(clipRoot.outPoint),timeline.simplifiedTC(clipRoot.modelStart + clipRoot.clipDuration))
                        timeline.showToolTip(s)
699
700
701
                        if (!fadeOutMouseArea.containsMouse) {
                            timeline.showKeyBinding(i18n("<b>Ctrl drag</b> to change speed, <b>Double click</b> to mix with adjacent clip"))
                        }
702
703
                    }
                }
704
705
706
707
708
709
710
711
712
713
714
715
716
                onExited: {
                    trimOut.opacity = 0
                    if (!pressed) {
                         if (!mouseArea.containsMouse) {
                            timeline.showToolTip()
                         } else {
                             var text = i18n("%1 (%2-%3), Position: %4, Duration: %5".arg(clipRoot.clipName)
                        .arg(timeline.simplifiedTC(clipRoot.inPoint))
                        .arg(timeline.simplifiedTC(clipRoot.outPoint))
                        .arg(timeline.simplifiedTC(clipRoot.modelStart))
                        .arg(timeline.simplifiedTC(clipRoot.clipDuration)))
                             timeline.showToolTip(text)
                         }
717
718
719
                         if (!fadeOutMouseArea.containsMouse) {
                             timeline.showKeyBinding()
                         }
720
721
722
                    }
                }
                /*ToolTip {
723
724
725
726
727
728
729
730
731
                    visible: trimOutMouseArea.containsMouse && !trimOutMouseArea.pressed
                    delay: 1000
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
732
                        font: miniFont
733
734
                        text: i18n("Out: ") + timeline.simplifiedTC(clipRoot.outPoint)
                    }
735
                }*/
736
737
738
739
740
741
742
743
744
                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
745
                    visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && parent.enabled)
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
                }
            }

            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
775
                anchors.leftMargin: mixContainer.width
776
777
778
779
780
                clip: true
                Rectangle {
                    // Clip name background
                    id: labelRect
                    color: clipRoot.selected ? 'darkred' : '#66000000'
781
                    width: label.width + (2 * clipRoot.border.width)
782
783
784
785
786
                    height: label.height
                    visible: clipRoot.width > width / 2
                    Text {
                        // Clip name text
                        id: label
787
                        property string clipNameString: (clipRoot.isAudio && clipRoot.multiStream) ? ((clipRoot.audioStream > 10000 ? 'Merged' : clipRoot.aStreamIndex) + '|' + clipName ) : clipName
788
                        text: (clipRoot.speed != 1.0 ? ('[' + Math.round(clipRoot.speed*100) + '%] ') : '') + clipNameString
789
                        font: miniFont
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
                        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)
                        }
819
820
821
822
823
824
                        onEntered: {
                            var text = positionOffset < 0 ? i18n("Offset: -%1", timeline.simplifiedTC(-positionOffset)) : i18n("Offset: %1", timeline.simplifiedTC(positionOffset))
                            timeline.showToolTip(text)
                        }
                        onExited: {
                            timeline.showToolTip()
825
826
827
828
                        }
                        Text {
                            id: offsetLabel
                            text: positionOffset
829
                            font: miniFont
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
                            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
855
                        font: miniFont
856
857
858
859
860
861
862
863
864
865
866
                        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'
                    }
867
               }
868
869
870
               Rectangle{
                    //proxy 
                    id:proxyRect
871
                    color: '#fdbc4b'
872
873
874
875
876
                    width: labelRect.height
                    height: labelRect.height
                    x: labelRect.x
                    anchors.top: labelRect.top
                    anchors.left: labelRect.right
877
                    visible: !clipRoot.isAudio && clipRoot.clipStatus == ClipStatus.StatusProxy || clipRoot.clipStatus == ClipStatus.StatusProxyOnly
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
                    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'
                    }
                }
894
            }
895
896
897
898
899
900
901
902
903
904
905
906

            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
            }
907
        }
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939

        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
                }
            }
        ]

940
941
942
        MouseArea {
            // Add start composition area
            id: compInArea
943
944
            anchors.left: parent.left
            anchors.bottom: parent.bottom
945
            width: Math.min(root.baseUnit, container.height / 3)
946
            height: width
947
948
949
950
951
952
953
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            visible: !clipRoot.isAudio
            enabled: !clipRoot.isAudio && dragProxy.draggedItem === clipRoot.clipId && compositionIn.visible
            onPressed: {
                timeline.addCompositionToClip('', clipRoot.clipId, 0)
            }
954
955
956
957
958
            onEntered: {
                timeline.showKeyBinding(i18n("<b>Click</b> to add composition"))
            }
            onExited: {
                timeline.showKeyBinding()
959
            }
960
961
962
963
964
            Rectangle {
                // Start composition box
                id: compositionIn
                anchors.bottom: parent.bottom
                anchors.left: parent.left
965
                width: compInArea.containsMouse ? parent.width : 5
966
967
968
969
970
971
972
973
                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 } }
            }
974
        }
975
976
977
978

        MouseArea {
            // Add end composition area
            id: compOutArea
979
980
            anchors.right: parent.right
            anchors.bottom: parent.bottom
981
            width: Math.min(root.baseUnit, container.height / 3)
982
            height: width
983
984
985
986
987
988
            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)
989
            }
990
991
992
993
994
            onEntered: {
                timeline.showKeyBinding(i18n("<b>Click</b> to add composition"))
            }
            onExited: {
                timeline.showKeyBinding()
995
            }
996
997
998
999
1000
            Rectangle {
                // End composition box
                id: compositionOut
                anchors.bottom: parent.bottom
                anchors.right: parent.right
1001
                width: compOutArea.containsMouse ? parent.height : 5
1002
1003
1004
1005
1006
1007
1008
                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 } }
1009
1010
            }
        }
1011
1012
1013

        MouseArea {
            // Fade out drag zone
1014
            id: fadeOutMouseArea
1015
1016
1017
            anchors.right: parent.right
            anchors.rightMargin: clipRoot.fadeOut <= 0 ? 0 : fadeOutCanvas.width - width / 2
            anchors.top: parent.top
1018
            width: Math.min(root.baseUnit, container.height / 3)
1019
            height: width
1020
            hoverEnabled: true
1021
1022
            cursorShape: Qt.PointingHandCursor
            drag.target: fadeOutMouseArea
1023
            drag.axis: Drag.XAxis
1024
            drag.minimumX: - Math.ceil(width / 2)
1025
1026
            drag.maximumX: container.width + Math.ceil(width / 4)
            visible: clipRoot.width > 3 * width && mouseArea.containsMouse && !dragProxyArea.pressed