Clip.qml 30.7 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
import QtQuick 2.6
20
import QtQuick.Controls 2.2
21
import Kdenlive.Controls 1.0
22
23
import QtQml.Models 2.2
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.0
30
31
32
    property string clipName: ''
    property string clipResource: ''
    property string mltService: ''
33
    property string effectNames
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
34
    property int modelStart
35
    property real scrollX: 0
36
37
38
39
    property int inPoint: 0
    property int outPoint: 0
    property int clipDuration: 0
    property bool isAudio: false
40
    property int audioChannels
41
    property bool showKeyframes: false
42
    property bool isGrabbed: false
43
    property bool grouped: false
44
    property var audioLevels
45
    property var markers
46
    property var keyframeModel
47
48
    property int clipStatus: 0
    property int itemType: 0
49
50
    property int fadeIn: 0
    property int fadeOut: 0
51
    property int binId: 0
52
    property var parentTrack
53
54
    property int trackIndex //Index in track repeater
    property int clipId     //Id of the clip in the model
55
56
57
    property int trackId: -1 // Id of the parent track in the model
    property int fakeTid: -1
    property int fakePosition: 0
58
    property int originalTrackId: -1
59
    property int originalX: x
60
61
    property int originalDuration: clipDuration
    property int lastValidDuration: clipDuration
62
    property int draggedX: x
63
    property bool selected: false
64
    property bool isLocked: parentTrack && parentTrack.isLocked == true
65
    property bool hasAudio
66
67
    property bool canBeAudio
    property bool canBeVideo
68
    property string hash: 'ccc' //TODO
69
    property double speed: 1.0
70
    property color borderColor: 'black'
71
    property bool forceReloadThumb
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
72
    width : clipDuration * timeScale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
73
    opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
74

75
76
77
78
    signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim)
    signal trimmedIn(var clip, bool shiftTrim)
    signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim)
    signal trimmedOut(var clip, bool shiftTrim)
79

80
81
82
83
84
85
86
    onIsGrabbedChanged: {
        if (clipRoot.isGrabbed) {
            clipRoot.forceActiveFocus();
            mouseArea.focus = true
        }
    }

87
    onInPointChanged: {
88
        if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
89
            thumbsLoader.item.reload()
90
        }
91
92
    }

93
    onClipResourceChanged: {
94
        if (itemType == ProducerType.Color) {
95
            color: Qt.darker(getColor(), 1.5)
96
97
        }
    }
98
    ToolTip {
99
        visible: mouseArea.containsMouse && !dragProxyArea.pressed
100
101
102
103
104
105
106
107
108
        font.pixelSize: root.baseUnit
        delay: 1000
        timeout: 5000
        background: Rectangle {
            color: activePalette.alternateBase
            border.color: activePalette.light
        }
        contentItem: Label {
            color: activePalette.text
109
            text: clipRoot.clipName + ' (' + timeline.timecode(clipRoot.inPoint) + '-' + timeline.timecode(clipRoot.outPoint) + ')'
110
111
112
        }
    }

113
114
    onKeyframeModelChanged: {
        console.log('keyframe model changed............')
115
116
117
        if (effectRow.keyframecanvas) {
            effectRow.keyframecanvas.requestPaint()
        }
118
119
    }

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
120
121
    onClipDurationChanged: {
        width = clipDuration * timeScale;
122
    }
123

124
125
126
    onModelStartChanged: {
        x = modelStart * timeScale;
    }
127
128
129
130
131
    onFakePositionChanged: {
        x = fakePosition * timeScale;
    }
    onFakeTidChanged: {
        if (clipRoot.fakeTid > -1 && parentTrack) {
132
133
134
135
136
137
138
            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
            }
139
140
            clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y
        }
141
142
    }

143
    onForceReloadThumbChanged: {
144
        // TODO: find a way to force reload of clip thumbs
145
146
147
        if (thumbsLoader.item) {
            thumbsLoader.item.reload()
        }
148
149
    }

150
    onTimeScaleChanged: {
151
        x = modelStart * timeScale;
152
        width = clipDuration * timeScale;
153
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
154
        if (parentTrack && parentTrack.isAudio) {
155
            thumbsLoader.item.reload();
156
        }
157
158
159
    }
    onScrollXChanged: {
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
160
161
    }

Vincent Pinon's avatar
Vincent Pinon committed
162
    border.color: selected ? root.selectionColor : grouped ? root.groupColor : borderColor
163
    border.width: isGrabbed ? 8 : 1.5
164

165
166
167
168
169
    function updateDrag() {
        var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height)
        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
    }

170
    function getColor() {
171
172
173
        if (clipStatus == ClipState.Disabled) {
            return 'grey'
        }
174
        if (itemType == ProducerType.Color) {
175
176
177
            var color = clipResource.substring(clipResource.length - 9)
            if (color[0] == '#') {
                return color
178
            }
179
            return '#' + color.substring(color.length - 8, color.length - 2)
180
        }
181
        return isAudio? root.audioColor : root.videoColor
182
183
    }

184
/*    function reparent(track) {
185
        console.log('TrackId: ',trackId)
186
187
        parent = track
        height = track.height
188
        parentTrack = track
189
        trackId = parentTrack.trackId
190
        console.log('Reparenting clip to Track: ', trackId)
191
        //generateWaveform()
192
    }
193
*/
194
195
    property bool variableThumbs: (isAudio || itemType == ProducerType.Color || mltService === '')
    property bool isImage: itemType == ProducerType.Image
196
197
198
    property string baseThumbPath: variableThumbs ? '' : 'image://thumbnail/' + binId + '/' + (isImage ? '#0' : '#')
    property string inThumbPath: (variableThumbs || isImage ) ? baseThumbPath : baseThumbPath + Math.floor(inPoint * speed)
    property string outThumbPath: (variableThumbs || isImage ) ? baseThumbPath : baseThumbPath + Math.floor(outPoint * speed)
199

200
    DropArea { //Drop area for clips
201
202
203
        anchors.fill: clipRoot
        keys: 'kdenlive/effect'
        property string dropData
204
        property string dropSource
205
        property int dropRow: -1
206
207
        onEntered: {
            dropData = drag.getDataAsString('kdenlive/effect')
208
            dropSource = drag.getDataAsString('kdenlive/effectsource')
209
210
211
        }
        onDropped: {
            console.log("Add effect: ", dropData)
212
            if (dropSource == '') {
213
214
215
                // drop from effects list
                controller.addClipEffect(clipRoot.clipId, dropData);
            } else {
216
                controller.copyClipEffect(clipRoot.clipId, dropSource);
217
            }
218
            dropSource = ''
219
            dropRow = -1
220
            drag.acceptProposedAction
221
222
223
        }
    }

224
    onAudioLevelsChanged: {
225
        if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
226
            thumbsLoader.item.reload()
227
228
        }
    }
229
230
    MouseArea {
        id: mouseArea
231
        visible: root.activeTool === 0
232
        anchors.fill: clipRoot
233
        acceptedButtons: Qt.RightButton
234
        hoverEnabled: true
235
        cursorShape: dragProxyArea.drag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
236
237
        onPressed: {
            root.stopScrolling = true
238
            if (mouse.button == Qt.RightButton) {
239
                if (timeline.selection.indexOf(clipRoot.clipId) == -1) {
240
                    controller.requestAddToSelection(clipRoot.clipId, true)
241
242
243
                }
                clipMenu.clipId = clipRoot.clipId
                clipMenu.clipStatus = clipRoot.clipStatus
244
                clipMenu.clipFrame = Math.round(mouse.x / timeline.scaleFactor)
245
246
247
248
249
                clipMenu.grouped = clipRoot.grouped
                clipMenu.trackId = clipRoot.trackId
                clipMenu.canBeAudio = clipRoot.canBeAudio
                clipMenu.canBeVideo = clipRoot.canBeVideo
                clipMenu.popup()
250
            }
251
        }
252
        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)
253
254
255
256
257
258
        Keys.onLeftPressed: {
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - 1, true, true, true);
        }
        Keys.onRightPressed: {
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart + 1, true, true, true);
        }
259
260
261
262
263
264
        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);
        }
265
        onPositionChanged: {
266
267
            var mapped = parentTrack.mapFromItem(clipRoot, mouse.x, mouse.y).x
            root.mousePosChanged(Math.round(mapped / timeline.scaleFactor))
268
        }
269
270
271
272
        onEntered: {
            var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height)
            initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
        }
273

274
275
        onExited: {
            endDrag()
276
        }
277
        onWheel: zoomByWheel(wheel)
278
    }
279

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
280
    Item {
281
        // Thumbs container
282
        anchors.fill: parent
283
284
285
286
        anchors.leftMargin: 0
        anchors.rightMargin: 0
        anchors.topMargin: parent.border.width
        anchors.bottomMargin: parent.border.width
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
287
        clip: true
288
289
        Loader {
            id: thumbsLoader
290
291
            asynchronous: true
            visible: status == Loader.Ready
292
            anchors.fill: parent
293
            source: parentTrack.isAudio ? (timeline.showAudioThumbnails ? "ClipAudioThumbs.qml" : "") : itemType == ProducerType.Color ? "" : timeline.showThumbnails ? "ClipThumbs.qml" : ""
294
295
296
            onLoaded: {
                item.reload()
            }
297
        }
298
299
300
301
302
303
304
305
    }

    Item {
        // Clipping container
        id: container
        anchors.fill: parent
        anchors.margins: 1.5
        clip: true
306

307
        Rectangle {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
308
309
            // text background
            id: labelRect
310
            color: clipRoot.selected ? 'darkred' : '#66000000'
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
311
312
            width: label.width + 2
            height: label.height
313
            visible: clipRoot.width > width / 2
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
314
315
            Text {
                id: label
316
                text: clipName + (clipRoot.speed != 1.0 ? ' [' + Math.round(clipRoot.speed*100) + '%]': '')
317
                font.pixelSize: root.baseUnit * 1.2
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
318
                anchors {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
319
320
                    top: labelRect.top
                    left: labelRect.left
321
322
                    topMargin: 1
                    leftMargin: 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
323
                }
324
325
326
                color: 'white'
                style: Text.Outline
                styleColor: 'black'
327
328
            }
        }
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
        Rectangle {
            // effects
            id: effectsRect
            color: '#555555'
            width: effectLabel.width + 2
            height: effectLabel.height
            x: labelRect.x
            anchors.top: labelRect.bottom
            visible: labelRect.visible && clipRoot.effectNames != ''
            Text {
                id: effectLabel
                text: clipRoot.effectNames
                font.pixelSize: root.baseUnit * 1.2
                anchors {
                    top: effectsRect.top
                    left: effectsRect.left
                    topMargin: 1
                    leftMargin: 1
                    // + ((isAudio || !settings.timelineShowThumbnails) ? 0 : inThumbnail.width) + 1
                }
                color: 'white'
                //style: Text.Outline
                styleColor: 'black'
            }
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
354
355

        Repeater {
356
            model: markers
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
357
358
359
360
361
            delegate:
            Item {
                anchors.fill: parent
                Rectangle {
                    id: markerBase
362
                    width: 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
363
                    height: parent.height
364
                    x: (model.frame - clipRoot.inPoint) * timeScale;
Nicolas Carion's avatar
Nicolas Carion committed
365
                    color: model.color
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
366
367
                }
                Rectangle {
368
                    visible: mlabel.visible
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
369
370
371
372
373
374
375
376
                    opacity: 0.7
                    x: markerBase.x
                    radius: 2
                    width: mlabel.width + 4
                    height: mlabel.height
                    anchors {
                        bottom: parent.verticalCenter
                    }
Nicolas Carion's avatar
Nicolas Carion committed
377
                    color: model.color
378
379
380
381
382
383
                    MouseArea {
                        z: 10
                        anchors.fill: parent
                        acceptedButtons: Qt.LeftButton
                        cursorShape: Qt.PointingHandCursor
                        hoverEnabled: true
384
                        onDoubleClicked: timeline.editMarker(clipRoot.clipId, clipRoot.modelStart + model.frame - clipRoot.inPoint)
385
386
                        onClicked: timeline.position = (clipRoot.x + markerBase.x) / timeline.scaleFactor
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
387
388
389
                }
                Text {
                    id: mlabel
390
                    visible: timeline.showMarkers && parent.width > width * 1.5
391
                    text: model.comment
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
392
393
394
395
396
397
398
399
400
                    font.pixelSize: root.baseUnit
                    x: markerBase.x
                    anchors {
                        bottom: parent.verticalCenter
                        topMargin: 2
                        leftMargin: 2
                    }
                    color: 'white'
                }
401
402
            }
        }
403

404
        KeyframeView {
405
            id: effectRow
406
            visible: clipRoot.showKeyframes && clipRoot.keyframeModel
407
            selected: clipRoot.selected
408
409
            inPoint: clipRoot.inPoint
            outPoint: clipRoot.outPoint
410
            masterObject: clipRoot
411
            kfrModel: clipRoot.keyframeModel
412
        }
413
    }
414

415
    states: [
416
417
418
419
420
        State {
            name: 'locked'
            when: isLocked
            PropertyChanges {
                target: clipRoot
421
                color: root.lockedColor
422
423
424
425
                opacity: 0.8
                z: 0
            }
        },
426
427
        State {
            name: 'normal'
428
            when: clipRoot.selected === false
429
            PropertyChanges {
430
                target: clipRoot
431
                color: Qt.darker(getColor(), 1.5)
432
                z: 0
433
434
435
436
            }
        },
        State {
            name: 'selected'
437
            when: clipRoot.selected === true
438
439
            PropertyChanges {
                target: clipRoot
440
                color: getColor()
441
                z: 3
442
443
444
445
            }
        }
    ]

446
447
448
449
    Rectangle {
        id: compositionIn
        anchors.left: parent.left
        anchors.bottom: parent.bottom
450
        anchors.bottomMargin: 2
451
        anchors.leftMargin: 4
452
        width: root.baseUnit * 1.2
453
454
455
456
457
458
        height: width
        radius: 2
        color: Qt.darker('mediumpurple')
        border.width: 2
        border.color: 'green'
        opacity: 0
459
        enabled: !clipRoot.isAudio && !dragProxy.isComposition
460
        visible: clipRoot.width > 4 * width
461
462
463
464
465
466
467
468
469
470
471
472
473
474
        MouseArea {
            id: compInArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            onEntered: parent.opacity = 0.7
            onExited: {
                if (!pressed) {
                  parent.opacity = 0
                }
            }
            onPressed: {
                timeline.addCompositionToClip('', clipRoot.clipId, 0)
            }
475
476
477
            onReleased: {
                parent.opacity = 0
            }
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
            ToolTip {
                visible: compInArea.containsMouse && !dragProxyArea.pressed
                font.pixelSize: root.baseUnit
                delay: 1000
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
                }
                contentItem: Label {
                    color: activePalette.text
                    text: 'Click to add composition'
                }
            }
        }
    }
    Rectangle {
        id: compositionOut
        anchors.right: parent.right
        anchors.bottom: parent.bottom
498
        anchors.bottomMargin: 2
499
        anchors.rightMargin: 4
500
        width: root.baseUnit * 1.2
501
502
503
504
505
506
507
        height: width
        radius: 2
        color: Qt.darker('mediumpurple')
        border.width: 2
        border.color: 'green'
        opacity: 0
        enabled: !clipRoot.isAudio
508
        visible: clipRoot.width > 4 * width
509
510
511
512
513
        MouseArea {
            id: compOutArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
514
515
516
            onEntered: {
                parent.opacity = 0.7
            }
517
            onExited: {
518
519
520
                if (!pressed) {
                    parent.opacity = 0
                }
521
522
            }
            onPressed: {
523
                timeline.addCompositionToClip('', clipRoot.clipId, clipRoot.clipDuration - 1)
524
            }
525
526
527
            onReleased: {
                parent.opacity = 0
            }
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
            ToolTip {
                visible: compOutArea.containsMouse && !dragProxyArea.pressed
                font.pixelSize: root.baseUnit
                delay: 1000
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
                }
                contentItem: Label {
                    color: activePalette.text
                    text: 'Click to add composition'
                }
            }
        }
    }

545

546
    TimelineTriangle {
547
        id: fadeInTriangle
548
549
        fillColor: 'green'
        width: Math.min(clipRoot.fadeIn * timeScale, clipRoot.width)
550
551
552
553
        height: clipRoot.height - clipRoot.border.width * 2
        anchors.left: clipRoot.left
        anchors.top: clipRoot.top
        anchors.margins: clipRoot.border.width
554
        opacity: 0.3
555
556
557
558
559
560
    }
    Rectangle {
        id: fadeInControl
        anchors.left: fadeInTriangle.width > radius? undefined : fadeInTriangle.left
        anchors.horizontalCenter: fadeInTriangle.width > radius? fadeInTriangle.right : undefined
        anchors.top: fadeInTriangle.top
561
562
563
564
        anchors.topMargin: -10
        width: root.baseUnit * 2
        height: width
        radius: width / 2
565
        color: '#FF66FFFF'
566
        border.width: 2
567
        border.color: 'green'
568
        enabled: !isLocked && !dragProxy.isComposition
569
        opacity: 0
570
        visible : clipRoot.width > 3 * width
571
572
573
574
575
576
577
        Drag.active: fadeInMouseArea.drag.active
        MouseArea {
            id: fadeInMouseArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            drag.target: parent
578
            drag.minimumX: -root.baseUnit
579
            drag.maximumX: container.width
580
            drag.axis: Drag.XAxis
581
            drag.smoothed: false
582
583
584
            property int startX
            property int startFadeIn
            onEntered: parent.opacity = 0.7
585
586
587
588
589
            onExited: {
                if (!pressed) {
                  parent.opacity = 0
                }
            }
590
591
            onPressed: {
                root.stopScrolling = true
592
                startX = Math.round(parent.x / timeScale)
593
                startFadeIn = clipRoot.fadeIn
594
595
596
                parent.anchors.left = undefined
                parent.anchors.horizontalCenter = undefined
                parent.opacity = 1
597
                fadeInTriangle.opacity = 0.5
598
                // parentTrack.clipSelected(clipRoot, parentTrack) TODO
599
600
601
            }
            onReleased: {
                root.stopScrolling = false
602
                fadeInTriangle.opacity = 0.3
603
                parent.opacity = 0
604
605
606
607
                if (fadeInTriangle.width > parent.radius)
                    parent.anchors.horizontalCenter = fadeInTriangle.right
                else
                    parent.anchors.left = fadeInTriangle.left
608
                console.log('released fade: ', clipRoot.fadeIn)
609
                timeline.adjustFade(clipRoot.clipId, 'fadein', clipRoot.fadeIn, startFadeIn)
610
611
612
613
                bubbleHelp.hide()
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
614
                    var delta = Math.round(parent.x / timeScale) - startX
615
                    var duration = Math.max(0, startFadeIn + delta)
616
                    duration = Math.min(duration, clipRoot.clipDuration)
617
                    if (duration != clipRoot.fadeIn) {
618
                        timeline.adjustFade(clipRoot.clipId, 'fadein', duration, -1)
619
620
621
                        // Show fade duration as time in a "bubble" help.
                        var s = timeline.timecode(Math.max(duration, 0))
                        bubbleHelp.show(clipRoot.x, parentTrack.y + clipRoot.height, s)
622
                    }
623
624
625
626
627
                }
            }
        }
        SequentialAnimation on scale {
            loops: Animation.Infinite
628
            running: fadeInMouseArea.containsMouse && !fadeInMouseArea.pressed
629
630
            NumberAnimation {
                from: 1.0
631
                to: 0.7
632
633
634
635
                duration: 250
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
636
                from: 0.7
637
638
639
640
641
                to: 1.0
                duration: 250
                easing.type: Easing.InOutQuad
            }
        }
642
    }
643

644
    TimelineTriangle {
645
        id: fadeOutCanvas
646
647
        fillColor: 'red'
        width: Math.min(clipRoot.fadeOut * timeScale, clipRoot.width)
648
649
650
651
        height: clipRoot.height - clipRoot.border.width * 2
        anchors.right: clipRoot.right
        anchors.top: clipRoot.top
        anchors.margins: clipRoot.border.width
652
        opacity: 0.3
653
654
655
656
657
658
659
        transform: Scale { xScale: -1; origin.x: fadeOutCanvas.width / 2}
    }
    Rectangle {
        id: fadeOutControl
        anchors.right: fadeOutCanvas.width > radius? undefined : fadeOutCanvas.right
        anchors.horizontalCenter: fadeOutCanvas.width > radius? fadeOutCanvas.left : undefined
        anchors.top: fadeOutCanvas.top
660
661
662
663
664
        anchors.topMargin: -10
        width: root.baseUnit * 2
        height: width
        radius: width / 2
        color: '#66FFFFFF'
665
        border.width: 2
666
        border.color: 'red'
667
        opacity: 0
668
        enabled: !isLocked && !dragProxy.isComposition
669
        Drag.active: fadeOutMouseArea.drag.active
670
        visible : clipRoot.width > 3 * width
671
672
673
674
675
676
677
        MouseArea {
            id: fadeOutMouseArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            drag.target: parent
            drag.axis: Drag.XAxis
678
            drag.minimumX: -root.baseUnit
679
            drag.maximumX: container.width
680
681
682
            property int startX
            property int startFadeOut
            onEntered: parent.opacity = 0.7
683
684
685
686
687
            onExited: {
                if (!pressed) {
                  parent.opacity = 0
                }
            }
688
            drag.smoothed: false
689
690
            onPressed: {
                root.stopScrolling = true
691
                startX = Math.round(parent.x / timeScale)
692
                startFadeOut = clipRoot.fadeOut
693
694
695
                parent.anchors.right = undefined
                parent.anchors.horizontalCenter = undefined
                parent.opacity = 1
696
                fadeOutCanvas.opacity = 0.5
697
698
            }
            onReleased: {
699
                fadeOutCanvas.opacity = 0.3
700
                parent.opacity = 0
701
702
703
704
705
                root.stopScrolling = false
                if (fadeOutCanvas.width > parent.radius)
                    parent.anchors.horizontalCenter = fadeOutCanvas.left
                else
                    parent.anchors.right = fadeOutCanvas.right
706
                timeline.adjustFade(clipRoot.clipId, 'fadeout', clipRoot.fadeOut, startFadeOut)
707
708
709
710
                bubbleHelp.hide()
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
711
712
713
714
715
                    var delta = startX - Math.round(parent.x / timeScale)
                    var duration = Math.max(0, startFadeOut + delta)
                    duration = Math.min(duration, clipRoot.clipDuration)
                    if (clipRoot.fadeOut != duration) {
                        timeline.adjustFade(clipRoot.clipId, 'fadeout', duration, -1)
716
717
718
                        // Show fade duration as time in a "bubble" help.
                        var s = timeline.timecode(Math.max(duration, 0))
                        bubbleHelp.show(clipRoot.x + clipRoot.width, parentTrack.y + clipRoot.height, s)
719
                    }
720
721
722
723
724
                }
            }
        }
        SequentialAnimation on scale {
            loops: Animation.Infinite
725
            running: fadeOutMouseArea.containsMouse && !fadeOutMouseArea.pressed
726
727
            NumberAnimation {
                from: 1.0
728
                to: 0.7
729
730
731
732
                duration: 250
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
733
                from: 0.7
734
735
736
737
738
                to: 1.0
                duration: 250
                easing.type: Easing.InOutQuad
            }
        }
739
    }
740
741
742

    Rectangle {
        id: trimIn
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
743
        anchors.left: clipRoot.left
744
745
        anchors.leftMargin: 0
        height: parent.height
746
        enabled: !isLocked
747
748
749
750
751
        width: 5
        color: isAudio? 'green' : 'lawngreen'
        opacity: 0
        Drag.active: trimInMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
752
        visible: trimInMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width)
753
754
755
756
757
758
759

        MouseArea {
            id: trimInMouseArea
            anchors.fill: parent
            hoverEnabled: true
            drag.target: parent
            drag.axis: Drag.XAxis
760
            drag.smoothed: false
761
            property bool shiftTrim: false
762
            property bool sizeChanged: false
763
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
764
765
            onPressed: {
                root.stopScrolling = true
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
766
                clipRoot.originalX = clipRoot.x
767
                clipRoot.originalDuration = clipDuration
768
                parent.anchors.left = undefined
769
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
770
                parent.opacity = 0
771
772
773
774
            }
            onReleased: {
                root.stopScrolling = false
                parent.anchors.left = clipRoot.left
775
776
777
778
                if (sizeChanged) {
                    clipRoot.trimmedIn(clipRoot, shiftTrim)
                    sizeChanged = false
                }
779
780
781
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
782
                    var delta = Math.round((trimIn.x) / timeScale)
783
                    if (delta !== 0) {
784
785
786
                        if (delta < -modelStart) {
                            delta = -modelStart
                        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
787
                        var newDuration =  clipDuration - delta
788
                        sizeChanged = true
789
                        clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim)
790
                    }
791
792
                }
            }
793
            onEntered: {
794
795
796
                if (!pressed) {
                    parent.opacity = 0.5
                }
797
798
799
800
            }
            onExited: {
                parent.opacity = 0
            }
801
802
803
804
        }
    }
    Rectangle {
        id: trimOut
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
805
        anchors.right: clipRoot.right
806
807
808
809
810
        anchors.rightMargin: 0
        height: parent.height
        width: 5
        color: 'red'
        opacity: 0
811
        enabled: !isLocked
812
813
        Drag.active: trimOutMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
814
        visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width)
815
816
817
818
819

        MouseArea {
            id: trimOutMouseArea
            anchors.fill: parent
            hoverEnabled: true
820
            property bool shiftTrim: false
821
            property bool sizeChanged: false
822
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
823
824
            drag.target: parent
            drag.axis: Drag.XAxis
825
            drag.smoothed: false
826
827
828

            onPressed: {
                root.stopScrolling = true
829
                clipRoot.originalDuration = clipDuration
830
                parent.anchors.right = undefined
831
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
832
                parent.opacity = 0
833
834
835
836
            }
            onReleased: {
                root.stopScrolling = false
                parent.anchors.right = clipRoot.right
837
838
839
840
                if (sizeChanged) {
                    clipRoot.trimmedOut(clipRoot, shiftTrim)
                    sizeChanged = false
                }
841
842
843
844
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
                    var newDuration = Math.round((parent.x + parent.width) / timeScale)
845
                    if (newDuration != clipDuration) {
846
                        sizeChanged = true
847
                        clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim)
848
                    }
849
850
                }
            }
851
852
853
854
855
            onEntered: {
                if (!pressed) {
                    parent.opacity = 0.5
                }
            }
856
857
858
            onExited: parent.opacity = 0
        }
    }
859

860
        /*MenuItem {
861
            id: mergeItem
862
            text: i18n('Merge with next clip')
863
864
865
            onTriggered: timeline.mergeClipWithNext(trackIndex, index, false)
        }
        MenuItem {
866
            text: i18n('Rebuild Audio Waveform')
867
            onTriggered: timeline.remakeAudioLevels(trackIndex, index)
868
        }*/
869
        /*onPopupVisibleChanged: {
870
871
872
873
874
            if (visible && application.OS !== 'OS X' && __popupGeometry.height > 0) {
                // Try to fix menu running off screen. This only works intermittently.
                menu.__yOffset = Math.min(0, Screen.height - (__popupGeometry.y + __popupGeometry.height + 40))
                menu.__xOffset = Math.min(0, Screen.width - (__popupGeometry.x + __popupGeometry.width))
            }
875
        }*/
876
}