Clip.qml 29 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 int modelStart: x
34 35
    // Used to store the current frame on move
    property int currentFrame: -1
36
    property int scrollX: 0
37 38 39 40
    property int inPoint: 0
    property int outPoint: 0
    property int clipDuration: 0
    property bool isAudio: false
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
41
    property bool isComposition: false
42
    property bool showKeyframes: false
43
    property bool grouped: false
44
    property var audioLevels
45
    property var markers
46
    property var keyframeModel
47
    property var clipStatus: 0
48
    property var clipType: 0
49 50
    property int fadeIn: 0
    property int fadeOut: 0
51
    property int binId: 0
52
    property var parentTrack: trackRoot
53 54 55 56
    property int trackIndex //Index in track repeater
    property int trackId: -42    //Id in the model
    property int clipId     //Id of the clip in the model
    property int originalTrackId: trackId
57
    property int originalX: x
58 59
    property int originalDuration: clipDuration
    property int lastValidDuration: clipDuration
60
    property int draggedX: x
61
    property bool selected: false
62
    property bool hasAudio
63 64
    property bool canBeAudio
    property bool canBeVideo
65
    property string hash: 'ccc' //TODO
66
    property double speed: 1.0
67
    property color borderColor: 'black'
68 69 70
    property bool forceReloadThumb: false
    property alias inSource: inThumbnail.source
    property alias outSource: outThumbnail.source
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
71
    width : clipDuration * timeScale;
72

73
    signal clicked(var clip, int shiftClick)
74 75 76
    signal moved(var clip)
    signal dragged(var clip, var mouse)
    signal dropped(var clip)
77
    signal draggedToTrack(var clip, int pos, int xpos)
78 79 80 81
    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)
82

83 84 85 86 87 88 89 90
    SequentialAnimation on color {
        id: flashclip
        loops: 2
        running: false
        ColorAnimation { from: Qt.darker(getColor()); to: "#ff3300"; duration: 100 }
        ColorAnimation { from: "#ff3300"; to: Qt.darker(getColor()); duration: 100 }
    }

91 92 93 94
    onInPointChanged: {
        generateWaveform()
    }

95
    onClipResourceChanged: {
96
        if (clipType == ProducerType.Color) {
97 98 99 100
            color: Qt.darker(getColor())
        }
    }

101
    ToolTip {
102
        visible: mouseArea.containsMouse && !mouseArea.pressed
103 104 105 106 107 108 109 110 111
        font.pixelSize: root.baseUnit
        delay: 1000
        timeout: 5000
        background: Rectangle {
            color: activePalette.alternateBase
            border.color: activePalette.light
        }
        contentItem: Label {
            color: activePalette.text
112
            text: clipRoot.clipName + ' (' + timeline.timecode(clipRoot.inPoint) + '-' + timeline.timecode(clipRoot.outPoint) + ')'
113 114 115
        }
    }

116 117
    onKeyframeModelChanged: {
        console.log('keyframe model changed............')
118 119 120
        if (effectRow.keyframecanvas) {
            effectRow.keyframecanvas.requestPaint()
        }
121 122
    }

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
123 124
    onClipDurationChanged: {
        width = clipDuration * timeScale;
125
    }
126

127 128 129
    onModelStartChanged: {
        x = modelStart * timeScale;
    }
130

131 132 133 134 135 136
    onForceReloadThumbChanged: {
        if (inThumbnail.visible) {
            clipRoot.inSource = ''
            clipRoot.inSource = inThumbPath
            clipRoot.outSource = ''
            clipRoot.outSource = outThumbPath
137
        }
138 139
    }

140
    onTimeScaleChanged: {
141
        x = modelStart * timeScale;
142
        width = clipDuration * timeScale;
143
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
144
        generateWaveform();
145 146 147
    }
    onScrollXChanged: {
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
148 149
    }

150
    SystemPalette { id: activePalette }
151
    color: Qt.darker(getColor())
152

153
    border.color: selected? 'red' : grouped ? 'yellowgreen' : borderColor
154
    border.width: 1.5
155 156 157 158 159
    Drag.active: mouseArea.drag.active
    Drag.proposedAction: Qt.MoveAction
    opacity: Drag.active? 0.5 : 1.0

    function getColor() {
160 161 162
        if (clipStatus == ClipState.Disabled) {
            return 'grey'
        }
163
        if (clipType == ProducerType.Color) {
164 165 166
            var color = clipResource.substring(clipResource.length - 9)
            if (color[0] == '#') {
                return color
167
            }
168
            return '#' + color.substring(color.length - 8, color.length - 2)
169
        }
170
        return isAudio? '#445f5a' : '#416e8c'
171 172 173
    }

    function reparent(track) {
174
        console.log('TrackId: ',trackId)
175 176
        parent = track
        height = track.height
177
        parentTrack = track
178
        trackId = parentTrack.trackId
179
        console.log('Reparenting clip to Track: ', trackId)
180
        //generateWaveform()
181 182
    }

183
    function generateWaveform() {
184 185
        // This is needed to make the model have the correct count.
        // Model as a property expression is not working in all cases.
186 187 188 189
        if (timeline.showAudioThumbnails) {
            waveformRepeater.model = Math.ceil(waveform.innerWidth / waveform.maxWidth)
            for (var i = 0; i < waveformRepeater.count; i++) {
                // This looks suspicious. Why not itemAt(i) ?? code borrowed from Shotcut
190
                waveformRepeater.itemAt(i).update();
191
            }
192
        }
193
    }
194 195
    property bool variableThumbs: (isAudio || clipType == ProducerType.Color || mltService === '')
    property bool isImage: clipType == 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: generateWaveform()
225 226
    MouseArea {
        id: mouseArea
227
        visible: root.activeTool === 0
228
        anchors.fill: clipRoot
229
        acceptedButtons: Qt.LeftButton | Qt.RightButton
230 231 232 233
        drag.target: parent
        drag.axis: Drag.XAxis
        property int startX
        drag.smoothed: false
234
        hoverEnabled: true
235
        cursorShape: containsMouse ? pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor : tracksArea.cursorShape
236 237
        onPressed: {
            root.stopScrolling = true
238
            originalX = clipRoot.x
239
            originalTrackId = clipRoot.trackId
240
            startX = clipRoot.x
241
            root.stopScrolling = true
242
            clipRoot.forceActiveFocus();
243
            if (!clipRoot.selected) {
244 245
                clipRoot.clicked(clipRoot, mouse.modifiers == Qt.ShiftModifier)
            }
246 247 248 249 250 251 252 253 254 255 256 257
            if (mouse.button == Qt.LeftButton) {
                drag.target = clipRoot
            } else if (mouse.button == Qt.RightButton) {
                drag.target = undefined
                clipMenu.clipId = clipRoot.clipId
                clipMenu.clipStatus = clipRoot.clipStatus
                clipMenu.grouped = clipRoot.grouped
                clipMenu.trackId = clipRoot.trackId
                clipMenu.canBeAudio = clipRoot.canBeAudio
                clipMenu.canBeVideo = clipRoot.canBeVideo
                clipMenu.popup()
            }
258 259
        }
        onPositionChanged: {
260
            if (pressed && mouse.buttons === Qt.LeftButton) {
261 262
                var trackIndex = Logic.getTrackIndexFromId(clipRoot.trackId)
                if ((mouse.y < 0 && trackIndex > 0) || (mouse.y > height && trackIndex < tracksRepeater.count - 1)) {
263
                    var mapped = parentTrack.mapFromItem(clipRoot, mouse.x, mouse.y).x
264
                    clipRoot.draggedToTrack(clipRoot, mapToItem(null, 0, mouse.y).y, mapped)
265
                } else {
266
                    clipRoot.dragged(clipRoot, mouse)
267
                }
268 269 270 271
            }
        }
        onReleased: {
            root.stopScrolling = false
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
            if (mouse.button == Qt.LeftButton) {
                var delta = clipRoot.x - startX
                drag.target = undefined
                cursorShape = Qt.OpenHandCursor
                if (trackId !== originalTrackId) {
                    var track = Logic.getTrackById(trackId)
                    parent.moved(clipRoot)
                    reparent(track)
                    originalX = clipRoot.x
                    clipRoot.y = 0
                    originalTrackId = trackId
                } else if (delta != 0) {
                    parent.dropped(clipRoot)
                    originalX = clipRoot.x
                }
287 288
            }
        }
289
        onDoubleClicked: {
290 291 292 293 294 295 296 297 298
            if (mouse.modifiers & Qt.ShiftModifier) {
                if (keyframeModel && showKeyframes) {
                    // Add new keyframe
                    var xPos = Math.round(mouse.x  / timeline.scaleFactor)
                    var yPos = (clipRoot.height - mouse.y) / clipRoot.height
                    keyframeModel.addKeyframe(xPos + clipRoot.inPoint, yPos)
                } else {
                    timeline.position = clipRoot.x / timeline.scaleFactor
                }
299
            } else {
300
                timeline.editItemDuration(clipId)
301
            }
302
            drag.target = undefined
303
        }
304
        onWheel: zoomByWheel(wheel)
305
    }
306

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
307 308
    Item {
        // Clipping container
309
        id: container
310
        anchors.fill: parent
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
311 312 313 314
        anchors.margins:1.5
        clip: true
        Image {
            id: outThumbnail
315 316
            visible: inThumbnail.visible
            opacity: inThumbnail.opacity
317 318 319
            anchors.top: container.top
            anchors.right: container.right
            anchors.bottom: container.bottom
320
            anchors.rightMargin: Math.min(0, container.width - 2 * inThumbnail.width)
321
            width: inThumbnail.width
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
322 323
            fillMode: Image.PreserveAspectFit
            asynchronous: true
324
            cache: false
325
            source: outThumbPath
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
326
        }
327

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
328 329
        Image {
            id: inThumbnail
330 331
            visible: timeline.showThumbnails && mltService != 'color' && !isAudio
            opacity: parentTrack.isAudio || parentTrack.isHidden || clipStatus == ClipState.Disabled ? 0.2 : 1
332 333 334
            anchors.left: container.left
            anchors.bottom: container.bottom
            anchors.top: container.top
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
335 336 337
            width: height * 16.0/9.0
            fillMode: Image.PreserveAspectFit
            asynchronous: true
338
            cache: false
339
            source: inThumbPath
340
        }
341

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
342 343
        Row {
            id: waveform
344
            visible: clipStatus != ClipState.VideoOnly && parentTrack.isAudio && timeline.showAudioThumbnails  && !parentTrack.isMute
345 346
            height: isAudio || parentTrack.isAudio || clipStatus == ClipState.AudioOnly ? container.height - 1 : (container.height - 1) / 2
            opacity: clipStatus == ClipState.Disabled ? 0.2 : 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
347 348
            anchors.left: container.left
            anchors.right: container.right
349
            anchors.bottom: container.bottom
350
            property int maxWidth: 1000
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
351
            property int innerWidth: clipRoot.width - clipRoot.border.width * 2
352 353
            property int scrollEnd: ((scrollView.flickableItem.contentX + scrollView.width) / timeScale - clipRoot.modelStart) * timeScale
            property int scrollStart: (scrollView.flickableItem.contentX / timeScale - clipRoot.modelStart) * timeScale
354

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
355 356 357 358 359
            Repeater {
                id: waveformRepeater
                TimelineWaveform {
                    width: Math.min(waveform.innerWidth, waveform.maxWidth)
                    height: waveform.height
360
                    showItem: waveform.visible && (index * waveform.maxWidth) < waveform.scrollEnd && (index * waveform.maxWidth + width) > waveform.scrollStart
361
                    format: timeline.audioThumbFormat
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
362 363 364 365 366
                    property int channels: 2
                    inPoint: Math.round((clipRoot.inPoint + index * waveform.maxWidth / timeScale) * speed) * channels
                    outPoint: inPoint + Math.round(width / timeScale * speed) * channels
                    levels: audioLevels
                }
367
            }
368 369
        }

370
        Rectangle {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
371 372
            // text background
            id: labelRect
373
            color: clipRoot.selected ? 'darkred' : '#66000000'
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
374 375
            width: label.width + 2
            height: label.height
376
            visible: clipRoot.width > width / 2
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
377 378
            Text {
                id: label
379
                text: clipName + (clipRoot.speed != 1.0 ? ' [' + Math.round(clipRoot.speed*100) + '%]': '')
380
                font.pixelSize: root.baseUnit * 1.2
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
381
                anchors {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
382 383
                    top: labelRect.top
                    left: labelRect.left
384 385
                    topMargin: 1
                    leftMargin: 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
386 387
                    // + ((isAudio || !settings.timelineShowThumbnails) ? 0 : inThumbnail.width) + 1
                }
388 389 390
                color: 'white'
                style: Text.Outline
                styleColor: 'black'
391 392
            }
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
393 394

        Repeater {
395
            model: markers
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
396 397 398 399 400
            delegate:
            Item {
                anchors.fill: parent
                Rectangle {
                    id: markerBase
401
                    width: 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
402
                    height: parent.height
403
                    x: (model.frame - clipRoot.inPoint) * timeScale;
Nicolas Carion's avatar
Nicolas Carion committed
404
                    color: model.color
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
405 406
                }
                Rectangle {
407
                    visible: mlabel.visible
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
408 409 410 411 412 413 414 415
                    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
416
                    color: model.color
417 418 419 420 421 422 423 424 425
                    MouseArea {
                        z: 10
                        anchors.fill: parent
                        acceptedButtons: Qt.LeftButton
                        cursorShape: Qt.PointingHandCursor
                        hoverEnabled: true
                        onDoubleClicked: timeline.editMarker(clipRoot.binId, model.frame)
                        onClicked: timeline.position = (clipRoot.x + markerBase.x) / timeline.scaleFactor
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
426 427 428
                }
                Text {
                    id: mlabel
429
                    visible: timeline.showMarkers && parent.width > width * 1.5
430
                    text: model.comment
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
431 432 433 434 435 436 437 438 439
                    font.pixelSize: root.baseUnit
                    x: markerBase.x
                    anchors {
                        bottom: parent.verticalCenter
                        topMargin: 2
                        leftMargin: 2
                    }
                    color: 'white'
                }
440 441
            }
        }
442

443
        KeyframeView {
444
            id: effectRow
445
            visible: clipRoot.showKeyframes && keyframeModel
446
            selected: clipRoot.selected
447 448
            inPoint: clipRoot.inPoint
            outPoint: clipRoot.outPoint
449
        }
450
    }
451

452 453 454
    states: [
        State {
            name: 'normal'
455
            when: clipRoot.selected === false
456
            PropertyChanges {
457 458
                target: clipRoot
                color: Qt.darker(getColor())
459 460 461 462
            }
        },
        State {
            name: 'selected'
463
            when: clipRoot.selected === true
464 465 466
            PropertyChanges {
                target: clipRoot
                z: 1
467
                color: getColor()
468 469 470 471 472
            }
        }
    ]


473
    TimelineTriangle {
474
        id: fadeInTriangle
475 476
        fillColor: 'green'
        width: Math.min(clipRoot.fadeIn * timeScale, clipRoot.width)
477 478 479 480
        height: clipRoot.height - clipRoot.border.width * 2
        anchors.left: clipRoot.left
        anchors.top: clipRoot.top
        anchors.margins: clipRoot.border.width
481
        opacity: 0.3
482 483 484 485 486 487
    }
    Rectangle {
        id: fadeInControl
        anchors.left: fadeInTriangle.width > radius? undefined : fadeInTriangle.left
        anchors.horizontalCenter: fadeInTriangle.width > radius? fadeInTriangle.right : undefined
        anchors.top: fadeInTriangle.top
488 489 490 491
        anchors.topMargin: -10
        width: root.baseUnit * 2
        height: width
        radius: width / 2
492
        color: '#FF66FFFF'
493
        border.width: 2
494
        border.color: 'green'
495 496 497 498 499 500 501 502
        opacity: 0
        Drag.active: fadeInMouseArea.drag.active
        MouseArea {
            id: fadeInMouseArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            drag.target: parent
503
            drag.minimumX: -root.baseUnit * 2
504
            drag.maximumX: container.width
505 506 507 508
            drag.axis: Drag.XAxis
            property int startX
            property int startFadeIn
            onEntered: parent.opacity = 0.7
509 510 511 512 513
            onExited: {
                if (!pressed) {
                  parent.opacity = 0
                }
            }
514
            drag.smoothed: false
515 516 517
            onPressed: {
                root.stopScrolling = true
                startX = parent.x
518
                startFadeIn = clipRoot.fadeIn
519 520 521
                parent.anchors.left = undefined
                parent.anchors.horizontalCenter = undefined
                parent.opacity = 1
522
                fadeInTriangle.opacity = 0.5
523
                // parentTrack.clipSelected(clipRoot, parentTrack) TODO
524 525 526
            }
            onReleased: {
                root.stopScrolling = false
527
                fadeInTriangle.opacity = 0.3
528
                parent.opacity = 0
529 530 531 532
                if (fadeInTriangle.width > parent.radius)
                    parent.anchors.horizontalCenter = fadeInTriangle.right
                else
                    parent.anchors.left = fadeInTriangle.left
533
                console.log('released fade: ', clipRoot.fadeIn)
534
                timeline.adjustFade(clipRoot.clipId, 'fadein', clipRoot.fadeIn, startFadeIn)
535 536 537 538 539
                bubbleHelp.hide()
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
                    var delta = Math.round((parent.x - startX) / timeScale)
540 541 542 543 544 545 546 547 548
                    if (delta != 0) {
                        var duration = Math.max(0, startFadeIn + delta)
                        duration = Math.min(duration, clipRoot.clipDuration)
                        if (clipRoot.fadeIn - 1 != duration) {
                            timeline.adjustFade(clipRoot.clipId, 'fadein', duration, -1)
                        }
                        // 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)
549
                    }
550 551 552 553 554
                }
            }
        }
        SequentialAnimation on scale {
            loops: Animation.Infinite
555
            running: fadeInMouseArea.containsMouse && !fadeInMouseArea.pressed
556 557
            NumberAnimation {
                from: 1.0
558
                to: 0.7
559 560 561 562
                duration: 250
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
563
                from: 0.7
564 565 566 567 568
                to: 1.0
                duration: 250
                easing.type: Easing.InOutQuad
            }
        }
569
    }
570

571
    TimelineTriangle {
572
        id: fadeOutCanvas
573 574
        fillColor: 'red'
        width: Math.min(clipRoot.fadeOut * timeScale, clipRoot.width)
575 576 577 578
        height: clipRoot.height - clipRoot.border.width * 2
        anchors.right: clipRoot.right
        anchors.top: clipRoot.top
        anchors.margins: clipRoot.border.width
579
        opacity: 0.3
580 581 582 583 584 585 586
        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
587 588 589 590 591
        anchors.topMargin: -10
        width: root.baseUnit * 2
        height: width
        radius: width / 2
        color: '#66FFFFFF'
592
        border.width: 2
593
        border.color: 'red'
594 595 596 597 598 599 600 601 602
        opacity: 0
        Drag.active: fadeOutMouseArea.drag.active
        MouseArea {
            id: fadeOutMouseArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            drag.target: parent
            drag.axis: Drag.XAxis
603
            drag.minimumX: -root.baseUnit * 2
604
            drag.maximumX: container.width
605 606 607
            property int startX
            property int startFadeOut
            onEntered: parent.opacity = 0.7
608 609 610 611 612
            onExited: {
                if (!pressed) {
                  parent.opacity = 0
                }
            }
613
            drag.smoothed: false
614 615 616
            onPressed: {
                root.stopScrolling = true
                startX = parent.x
617
                startFadeOut = clipRoot.fadeOut
618 619 620
                parent.anchors.right = undefined
                parent.anchors.horizontalCenter = undefined
                parent.opacity = 1
621
                fadeOutCanvas.opacity = 0.5
622 623
            }
            onReleased: {
624
                fadeOutCanvas.opacity = 0.3
625
                parent.opacity = 0
626 627 628 629 630
                root.stopScrolling = false
                if (fadeOutCanvas.width > parent.radius)
                    parent.anchors.horizontalCenter = fadeOutCanvas.left
                else
                    parent.anchors.right = fadeOutCanvas.right
631
                timeline.adjustFade(clipRoot.clipId, 'fadeout', clipRoot.fadeOut, startFadeOut)
632 633 634 635 636
                bubbleHelp.hide()
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
                    var delta = Math.round((startX - parent.x) / timeScale)
637 638 639 640 641 642 643 644 645
                    if (delta != 0) {
                        var duration = Math.max(0, startFadeOut + delta)
                        duration = Math.min(duration, clipRoot.clipDuration)
                        if (clipRoot.fadeOut - 1 != duration) {
                            timeline.adjustFade(clipRoot.clipId, 'fadeout', duration, -1)
                        }
                        // 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)
646
                    }
647 648 649 650 651
                }
            }
        }
        SequentialAnimation on scale {
            loops: Animation.Infinite
652
            running: fadeOutMouseArea.containsMouse && !fadeOutMouseArea.pressed
653 654
            NumberAnimation {
                from: 1.0
655
                to: 0.7
656 657 658 659
                duration: 250
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
660
                from: 0.7
661 662 663 664 665
                to: 1.0
                duration: 250
                easing.type: Easing.InOutQuad
            }
        }
666
    }
667 668 669

    Rectangle {
        id: trimIn
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
670
        anchors.left: clipRoot.left
671 672 673 674 675 676 677
        anchors.leftMargin: 0
        height: parent.height
        width: 5
        color: isAudio? 'green' : 'lawngreen'
        opacity: 0
        Drag.active: trimInMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
678
        visible: root.activeTool === 0 && !mouseArea.drag.active
679 680 681 682 683 684 685

        MouseArea {
            id: trimInMouseArea
            anchors.fill: parent
            hoverEnabled: true
            drag.target: parent
            drag.axis: Drag.XAxis
686
            drag.smoothed: false
687
            property bool shiftTrim: false
688
            property bool sizeChanged: false
689
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
690 691
            onPressed: {
                root.stopScrolling = true
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
692
                clipRoot.originalX = clipRoot.x
693
                clipRoot.originalDuration = clipDuration
694
                parent.anchors.left = undefined
695
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
696
                parent.opacity = 0
697 698 699 700
            }
            onReleased: {
                root.stopScrolling = false
                parent.anchors.left = clipRoot.left
701 702 703 704
                if (sizeChanged) {
                    clipRoot.trimmedIn(clipRoot, shiftTrim)
                    sizeChanged = false
                }
705 706 707
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
708
                    var delta = Math.round((trimIn.x) / timeScale)
709
                    if (delta !== 0) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
710
                        var newDuration =  clipDuration - delta
711
                        sizeChanged = true
712
                        clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim)
713
                    }
714 715
                }
            }
716
            onEntered: {
717 718 719
                if (!pressed) {
                    parent.opacity = 0.5
                }
720 721 722 723
            }
            onExited: {
                parent.opacity = 0
            }
724 725 726 727
        }
    }
    Rectangle {
        id: trimOut
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
728
        anchors.right: clipRoot.right
729 730 731 732 733 734 735
        anchors.rightMargin: 0
        height: parent.height
        width: 5
        color: 'red'
        opacity: 0
        Drag.active: trimOutMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
736
        visible: root.activeTool === 0 && !mouseArea.drag.active
737 738 739 740 741

        MouseArea {
            id: trimOutMouseArea
            anchors.fill: parent
            hoverEnabled: true
742
            property bool shiftTrim: false
743
            property bool sizeChanged: false
744
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
745 746
            drag.target: parent
            drag.axis: Drag.XAxis
747
            drag.smoothed: false
748 749 750

            onPressed: {
                root.stopScrolling = true
751
                clipRoot.originalDuration = clipDuration
752
                parent.anchors.right = undefined
753
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
754
                parent.opacity = 0
755 756 757 758
            }
            onReleased: {
                root.stopScrolling = false
                parent.anchors.right = clipRoot.right
759 760 761 762
                if (sizeChanged) {
                    clipRoot.trimmedOut(clipRoot, shiftTrim)
                    sizeChanged = false
                }
763 764 765 766
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
                    var newDuration = Math.round((parent.x + parent.width) / timeScale)
767
                    if (newDuration != clipDuration) {
768
                        sizeChanged = true
769
                        clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim)
770
                    }
771 772
                }
            }
773 774 775 776 777
            onEntered: {
                if (!pressed) {
                    parent.opacity = 0.5
                }
            }
778 779 780
            onExited: parent.opacity = 0
        }
    }
781

782
        /*MenuItem {
783
            id: mergeItem
784
            text: i18n('Merge with next clip')
785 786 787
            onTriggered: timeline.mergeClipWithNext(trackIndex, index, false)
        }
        MenuItem {
788
            text: i18n('Rebuild Audio Waveform')
789
            onTriggered: timeline.remakeAudioLevels(trackIndex, index)
790
        }*/
791
        /*onPopupVisibleChanged: {
792 793 794 795 796
            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))
            }
797
        }*/
798
}