Clip.qml 36.4 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
    property int inPoint: 0
    property int outPoint: 0
    property int clipDuration: 0
39
    property int maxDuration: 0
40
    property bool isAudio: false
41
    property int audioChannels
42
    property bool showKeyframes: false
43
    property bool isGrabbed: false
44
    property bool grouped: false
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 int positionOffset: 0
53
    property var parentTrack
54 55
    property int trackIndex //Index in track repeater
    property int clipId     //Id of the clip in the model
56 57 58
    property int trackId: -1 // Id of the parent track in the model
    property int fakeTid: -1
    property int fakePosition: 0
59
    property int originalTrackId: -1
60
    property int originalX: x
61 62
    property int originalDuration: clipDuration
    property int lastValidDuration: clipDuration
63
    property int draggedX: x
64
    property bool selected: false
65
    property bool isLocked: parentTrack && parentTrack.isLocked == true
66
    property bool hasAudio
67 68
    property bool canBeAudio
    property bool canBeVideo
69
    property double speed: 1.0
70
    property color borderColor: 'black'
71
    property bool forceReloadThumb
72
    property bool isComposition: false
73
    property var groupTrimData
74
    property int scrollStart: scrollView.flickableItem.contentX - clipRoot.modelStart * timeline.scaleFactor
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
75
    width : clipDuration * timeScale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
76
    opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
77

78 79
    signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
    signal trimmedIn(var clip, bool shiftTrim, bool controlTrim)
80
    signal initGroupTrim(var clip)
81 82
    signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
    signal trimmedOut(var clip, bool shiftTrim, bool controlTrim)
83

84 85
    onIsGrabbedChanged: {
        if (clipRoot.isGrabbed) {
86
            grabItem()
87 88
        } else {
            mouseArea.focus = false
89 90 91
        }
    }

92 93 94 95 96
    function grabItem() {
        clipRoot.forceActiveFocus()
        mouseArea.focus = true
    }

97 98
    function clearAndMove(offset) {
        controller.requestClearSelection()
99 100
        controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true)
        controller.requestAddToSelection(clipRoot.clipId)
101 102
    }

103
    onClipResourceChanged: {
104
        if (itemType == ProducerType.Color) {
105
            color: Qt.darker(getColor(), 1.5)
106 107
        }
    }
108
    ToolTip {
109
        visible: mouseArea.containsMouse && !dragProxyArea.pressed
110 111 112 113 114 115 116 117
        delay: 1000
        timeout: 5000
        background: Rectangle {
            color: activePalette.alternateBase
            border.color: activePalette.light
        }
        contentItem: Label {
            color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
118
            font.pointSize: root.fontUnit
119
            text: label.text + ' (' + timeline.simplifiedTC(clipRoot.inPoint) + '-' + timeline.simplifiedTC(clipRoot.outPoint) + ')'
120 121 122
        }
    }

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

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

138 139 140
    onModelStartChanged: {
        x = modelStart * timeScale;
    }
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 154
            clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y
        }
155 156
    }

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

164
    onTimeScaleChanged: {
165
        x = modelStart * timeScale;
166
        width = clipDuration * timeScale;
167 168 169 170
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
    }
    onScrollXChanged: {
        labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0
171 172
    }

Vincent Pinon's avatar
Vincent Pinon committed
173
    border.color: selected ? root.selectionColor : grouped ? root.groupColor : borderColor
174
    border.width: isGrabbed ? 8 : 1.5
175

176 177 178 179 180
    function updateDrag() {
        var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height)
        initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
    }

181
    function getColor() {
182 183 184
        if (clipStatus == ClipState.Disabled) {
            return 'grey'
        }
185
        if (itemType == ProducerType.Color) {
186 187 188
            var color = clipResource.substring(clipResource.length - 9)
            if (color[0] == '#') {
                return color
189
            }
190
            return '#' + color.substring(color.length - 8, color.length - 2)
191
        }
192
        return isAudio? root.audioColor : root.videoColor
193 194
    }

195
/*    function reparent(track) {
196
        console.log('TrackId: ',trackId)
197 198
        parent = track
        height = track.height
199
        parentTrack = track
200
        trackId = parentTrack.trackId
201
        console.log('Reparenting clip to Track: ', trackId)
202
        //generateWaveform()
203
    }
204
*/
205 206
    property bool variableThumbs: (isAudio || itemType == ProducerType.Color || mltService === '')
    property bool isImage: itemType == ProducerType.Image
207
    property string baseThumbPath: variableThumbs ? '' : 'image://thumbnail/' + binId + '/' + documentId + '/' + (isImage ? '#0' : '#')
208

209
    DropArea { //Drop area for clips
210 211 212
        anchors.fill: clipRoot
        keys: 'kdenlive/effect'
        property string dropData
213
        property string dropSource
214
        property int dropRow: -1
215 216
        onEntered: {
            dropData = drag.getDataAsString('kdenlive/effect')
217
            dropSource = drag.getDataAsString('kdenlive/effectsource')
218 219 220
        }
        onDropped: {
            console.log("Add effect: ", dropData)
221
            if (dropSource == '') {
222 223 224
                // drop from effects list
                controller.addClipEffect(clipRoot.clipId, dropData);
            } else {
225
                controller.copyClipEffect(clipRoot.clipId, dropSource);
226
            }
227
            dropSource = ''
228
            dropRow = -1
229
            drag.acceptProposedAction
230 231
        }
    }
232 233
    MouseArea {
        id: mouseArea
234
        enabled: root.activeTool === 0
235
        anchors.fill: clipRoot
236
        acceptedButtons: Qt.RightButton
237
        hoverEnabled: root.activeTool === 0
238
        cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape
239
        onPressed: {
240
            root.autoScrolling = false
241
            if (mouse.button == Qt.RightButton) {
242
                if (timeline.selection.indexOf(clipRoot.clipId) == -1) {
243
                    controller.requestAddToSelection(clipRoot.clipId, true)
244 245 246
                }
                clipMenu.clipId = clipRoot.clipId
                clipMenu.clipStatus = clipRoot.clipStatus
247
                clipMenu.clipFrame = Math.round(mouse.x / timeline.scaleFactor)
248 249 250 251 252
                clipMenu.grouped = clipRoot.grouped
                clipMenu.trackId = clipRoot.trackId
                clipMenu.canBeAudio = clipRoot.canBeAudio
                clipMenu.canBeVideo = clipRoot.canBeVideo
                clipMenu.popup()
253
            }
254
        }
255
        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)
256
        Keys.onLeftPressed: {
257 258
            var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true);
259 260
        }
        Keys.onRightPressed: {
261 262
            var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1
            controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart + offset, true, true, true);
263
        }
264 265 266 267 268 269
        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);
        }
270 271
        Keys.onEscapePressed: {
            timeline.grabCurrent()
272
            //focus = false
273
        }
274
        onPositionChanged: {
275 276
            var mapped = parentTrack.mapFromItem(clipRoot, mouse.x, mouse.y).x
            root.mousePosChanged(Math.round(mapped / timeline.scaleFactor))
277
        }
278 279 280 281
        onEntered: {
            var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height)
            initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false)
        }
282

283 284
        onExited: {
            endDrag()
285
        }
286
        onWheel: zoomByWheel(wheel)
287

288 289
        Item {
            // Thumbs container
290
            anchors.fill: parent
291 292 293 294 295 296 297 298 299 300 301 302 303 304
            anchors.leftMargin: 0
            anchors.rightMargin: 0
            anchors.topMargin: clipRoot.border.width
            anchors.bottomMargin: clipRoot.border.width
            clip: true
            Loader {
                id: thumbsLoader
                asynchronous: true
                visible: status == Loader.Ready
                anchors.fill: parent
                source: parentTrack.isAudio ? (timeline.showAudioThumbnails ? "ClipAudioThumbs.qml" : "") : itemType == ProducerType.Color ? "" : timeline.showThumbnails ? "ClipThumbs.qml" : ""
                onLoaded: {
                    item.reload()
                }
305
            }
306
        }
307

308 309 310 311 312 313
        Item {
            // Clipping container
            id: container
            anchors.fill: parent
            anchors.margins: 1.5
            clip: true
314

315 316 317 318 319 320 321 322 323 324
            Rectangle {
                // text background
                id: labelRect
                color: clipRoot.selected ? 'darkred' : '#66000000'
                width: label.width + 2
                height: label.height
                visible: clipRoot.width > width / 2
                Text {
                    id: label
                    text: clipName + (clipRoot.speed != 1.0 ? ' [' + Math.round(clipRoot.speed*100) + '%]': '')
325
                    font.pointSize: root.fontUnit
326 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 354 355 356 357 358 359 360 361 362 363
                    anchors {
                        top: labelRect.top
                        left: labelRect.left
                        topMargin: 1
                        leftMargin: 1
                    }
                    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
                visible: labelRect.visible && positionOffset != 0
                MouseArea {
                    id: offsetArea
                    hoverEnabled: true
                    cursorShape: Qt.PointingHandCursor
                    anchors.fill: parent
                    onClicked: {
                        clearAndMove(positionOffset)
                    }
                    ToolTip {
                        visible: offsetArea.containsMouse
                        delay: 1000
                        timeout: 5000
                        background: Rectangle {
                            color: activePalette.alternateBase
                            border.color: activePalette.light
                        }
                        contentItem: Label {
                            color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
364
                            font.pointSize: root.fontUnit
365
                            text: positionOffset < 0 ? i18n("Offset: -%1", timeline.simplifiedTC(-positionOffset)) : i18n("Offset: %1", timeline.simplifiedTC(positionOffset))
366 367 368 369 370
                        }
                    }
                    Text {
                        id: offsetLabel
                        text: positionOffset
371
                        font.pointSize: root.fontUnit
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
                        anchors {
                            horizontalCenter: parent.horizontalCenter
                            topMargin: 1
                            leftMargin: 1
                        }
                        color: 'white'
                        style: Text.Outline
                        styleColor: 'black'
                    }
                }
            }
            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
395
                    font.pointSize: root.fontUnit
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
                    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'
                }
            }

            Repeater {
                model: markers
                delegate:
                Item {
                    anchors.fill: parent
                    Rectangle {
                        id: markerBase
                        width: 1
                        height: parent.height
418
                        x: clipRoot.speed < 0 ? clipRoot.clipDuration * timeScale + (Math.round(model.frame / clipRoot.speed) - (clipRoot.maxDuration - clipRoot.outPoint)) * timeScale : (Math.round(model.frame / clipRoot.speed) - clipRoot.inPoint) * timeScale;
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
                        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
438
                            onDoubleClicked: timeline.editMarker(clipRoot.clipId, model.frame)
439
                            onClicked: proxy.position = (clipRoot.x + markerBase.x) / timeline.scaleFactor
440 441 442 443 444 445
                        }
                    }
                    Text {
                        id: mlabel
                        visible: timeline.showMarkers && parent.width > width * 1.5
                        text: model.comment
446
                        font.pointSize: root.fontUnit
447 448 449 450 451 452 453 454
                        x: markerBase.x
                        anchors {
                            bottom: parent.verticalCenter
                            topMargin: 2
                            leftMargin: 2
                        }
                        color: 'white'
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
455
                }
456 457 458 459 460 461 462 463 464 465
            }

            KeyframeView {
                id: effectRow
                visible: clipRoot.showKeyframes && clipRoot.keyframeModel
                selected: clipRoot.selected
                inPoint: clipRoot.inPoint
                outPoint: clipRoot.outPoint
                masterObject: clipRoot
                kfrModel: clipRoot.keyframeModel
466 467
            }
        }
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

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

500
        Rectangle {
501 502 503 504 505 506 507 508 509 510 511 512
            id: compositionIn
            anchors.left: parent.left
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            anchors.leftMargin: 4
            width: root.baseUnit * 1.2
            height: width
            radius: 2
            color: Qt.darker('mediumpurple')
            border.width: 2
            border.color: 'green'
            opacity: 0
513
            enabled: !clipRoot.isAudio && dragProxy.draggedItem === clipRoot.clipId
514
            visible: clipRoot.width > 4 * width
515
            MouseArea {
516 517
                id: compInArea
                anchors.fill: parent
518 519
                hoverEnabled: true
                cursorShape: Qt.PointingHandCursor
520 521 522 523 524 525 526 527 528 529 530
                onEntered: parent.opacity = 0.7
                onExited: {
                    if (!pressed) {
                    parent.opacity = 0
                    }
                }
                onPressed: {
                    timeline.addCompositionToClip('', clipRoot.clipId, 0)
                }
                onReleased: {
                    parent.opacity = 0
531 532
                }
                ToolTip {
533
                    visible: compInArea.containsMouse && !dragProxyArea.pressed
534 535 536 537 538 539 540 541
                    delay: 1000
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
542
                        font.pointSize: root.fontUnit
Yuri Chornoivan's avatar
Yuri Chornoivan committed
543
                        text: i18n("Click to add composition")
544 545 546 547
                    }
                }
            }
        }
548
        Rectangle {
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
            id: compositionOut
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            anchors.rightMargin: 4
            width: root.baseUnit * 1.2
            height: width
            radius: 2
            color: Qt.darker('mediumpurple')
            border.width: 2
            border.color: 'green'
            opacity: 0
            enabled: !clipRoot.isAudio  && dragProxy.draggedItem == clipRoot.clipId
            visible: clipRoot.width > 4 * width
            MouseArea {
                id: compOutArea
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
565
                anchors.fill: parent
566 567 568 569
                hoverEnabled: true
                cursorShape: Qt.PointingHandCursor
                onEntered: {
                    parent.opacity = 0.7
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
570
                }
571 572 573
                onExited: {
                    if (!pressed) {
                        parent.opacity = 0
574
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
575
                }
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
                onPressed: {
                    timeline.addCompositionToClip('', clipRoot.clipId, clipRoot.clipDuration - 1)
                }
                onReleased: {
                    parent.opacity = 0
                }
                ToolTip {
                    visible: compOutArea.containsMouse && !dragProxyArea.pressed
                    delay: 1000
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
592
                        font.pointSize: root.fontUnit
Yuri Chornoivan's avatar
Yuri Chornoivan committed
593
                        text: i18n("Click to add composition")
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
594 595
                    }
                }
596 597
            }
        }
598

599

600 601 602 603 604 605 606 607 608
        TimelineTriangle {
            id: fadeInTriangle
            fillColor: 'green'
            width: Math.min(clipRoot.fadeIn * timeScale, clipRoot.width)
            height: clipRoot.height - clipRoot.border.width * 2
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.margins: clipRoot.border.width
            opacity: 0.3
609
        }
610 611
        Rectangle {
            id: fadeInControl
612 613
            anchors.left: fadeInTriangle.right
            anchors.leftMargin: fadeInTriangle.width > root.baseUnit ? -root.baseUnit : 0
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
            anchors.top: fadeInTriangle.top
            anchors.topMargin: -10
            width: root.baseUnit * 2
            height: width
            radius: width / 2
            color: '#FF66FFFF'
            border.width: 2
            border.color: 'green'
            enabled: !isLocked && !dragProxy.isComposition
            opacity: 0
            visible : clipRoot.width > 3 * width
            Drag.active: fadeInMouseArea.drag.active
            MouseArea {
                id: fadeInMouseArea
                anchors.fill: parent
                hoverEnabled: true
                cursorShape: Qt.PointingHandCursor
                drag.target: parent
                drag.minimumX: -root.baseUnit
                drag.maximumX: container.width
                drag.axis: Drag.XAxis
                drag.smoothed: false
                property int startX
                property int startFadeIn
                onEntered: parent.opacity = 0.7
                onExited: {
                    if (!pressed) {
                    parent.opacity = 0
                    }
643
                }
644
                onPressed: {
645
                    root.autoScrolling = false
646 647 648 649 650 651
                    startX = Math.round(parent.x / timeScale)
                    startFadeIn = clipRoot.fadeIn
                    parent.anchors.left = undefined
                    parent.opacity = 1
                    fadeInTriangle.opacity = 0.5
                    // parentTrack.clipSelected(clipRoot, parentTrack) TODO
652
                }
653
                onReleased: {
654
                    root.autoScrolling = timeline.autoScroll
655
                    fadeInTriangle.opacity = 0.3
656
                    parent.opacity = 0
657
                    parent.anchors.left = fadeInTriangle.right
658 659 660 661 662 663 664 665
                    console.log('released fade: ', clipRoot.fadeIn)
                    timeline.adjustFade(clipRoot.clipId, 'fadein', clipRoot.fadeIn, startFadeIn)
                    bubbleHelp.hide()
                }
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
                        var delta = Math.round(parent.x / timeScale) - startX
                        var duration = Math.max(0, startFadeIn + delta)
666
                        duration = Math.min(duration, clipRoot.clipDuration - 1)
667 668 669
                        if (duration != clipRoot.fadeIn) {
                            timeline.adjustFade(clipRoot.clipId, 'fadein', duration, -1)
                            // Show fade duration as time in a "bubble" help.
670
                            var s = timeline.simplifiedTC(Math.max(duration, 0))
671 672 673
                            bubbleHelp.show(clipRoot.x, parentTrack.y + clipRoot.height, s)
                        }
                    }
674
                }
675
            }
676 677 678 679 680 681 682 683
            SequentialAnimation on scale {
                loops: Animation.Infinite
                running: fadeInMouseArea.containsMouse && !fadeInMouseArea.pressed
                NumberAnimation {
                    from: 1.0
                    to: 0.7
                    duration: 250
                    easing.type: Easing.InOutQuad
684
                }
685 686 687 688 689
                NumberAnimation {
                    from: 0.7
                    to: 1.0
                    duration: 250
                    easing.type: Easing.InOutQuad
690 691 692 693
                }
            }
        }

694 695 696 697 698 699 700 701 702 703 704 705 706
        TimelineTriangle {
            id: fadeOutCanvas
            fillColor: 'red'
            width: Math.min(clipRoot.fadeOut * timeScale, clipRoot.width)
            height: clipRoot.height - clipRoot.border.width * 2
            anchors.right: parent.right
            anchors.top: parent.top
            anchors.margins: clipRoot.border.width
            opacity: 0.3
            transform: Scale { xScale: -1; origin.x: fadeOutCanvas.width / 2}
        }
        Rectangle {
            id: fadeOutControl
707 708
            anchors.right: fadeOutCanvas.left
            anchors.rightMargin: fadeOutCanvas.width > root.baseUnit ? -root.baseUnit : 0
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
            anchors.top: fadeOutCanvas.top
            anchors.topMargin: -10
            width: root.baseUnit * 2
            height: width
            radius: width / 2
            color: '#66FFFFFF'
            border.width: 2
            border.color: 'red'
            opacity: 0
            enabled: !isLocked && !dragProxy.isComposition
            Drag.active: fadeOutMouseArea.drag.active
            visible : clipRoot.width > 3 * width
            MouseArea {
                id: fadeOutMouseArea
                anchors.fill: parent
                hoverEnabled: true
                cursorShape: Qt.PointingHandCursor
                drag.target: parent
                drag.axis: Drag.XAxis
                drag.minimumX: -root.baseUnit
                drag.maximumX: container.width
                property int startX
                property int startFadeOut
                onEntered: parent.opacity = 0.7
                onExited: {
                    if (!pressed) {
                    parent.opacity = 0
736
                    }
737
                }
738 739
                drag.smoothed: false
                onPressed: {
740
                    root.autoScrolling = false
741 742 743 744 745
                    startX = Math.round(parent.x / timeScale)
                    startFadeOut = clipRoot.fadeOut
                    parent.anchors.right = undefined
                    parent.opacity = 1
                    fadeOutCanvas.opacity = 0.5
746
                }
747 748 749
                onReleased: {
                    fadeOutCanvas.opacity = 0.3
                    parent.opacity = 0
750
                    root.autoScrolling = timeline.autoScroll
751
                    parent.anchors.right = fadeOutCanvas.left
752 753 754 755 756
                    var duration = clipRoot.fadeOut
                    if (duration > 0) {
                        duration += 1
                    }
                    timeline.adjustFade(clipRoot.clipId, 'fadeout', duration, startFadeOut)
757 758 759 760 761 762 763 764 765 766
                    bubbleHelp.hide()
                }
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
                        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)
                            // Show fade duration as time in a "bubble" help.
767
                            var s = timeline.simplifiedTC(Math.max(duration, 0))
768 769
                            bubbleHelp.show(clipRoot.x + clipRoot.width, parentTrack.y + clipRoot.height, s)
                        }
770
                    }
771 772
                }
            }
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
            SequentialAnimation on scale {
                loops: Animation.Infinite
                running: fadeOutMouseArea.containsMouse && !fadeOutMouseArea.pressed
                NumberAnimation {
                    from: 1.0
                    to: 0.7
                    duration: 250
                    easing.type: Easing.InOutQuad
                }
                NumberAnimation {
                    from: 0.7
                    to: 1.0
                    duration: 250
                    easing.type: Easing.InOutQuad
                }
788 789
            }
        }
790
    }
791 792 793

    Rectangle {
        id: trimIn
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
794
        anchors.left: clipRoot.left
795 796
        anchors.leftMargin: 0
        height: parent.height
797
        enabled: !isLocked
798
        width: root.baseUnit / 2
799 800 801 802
        color: isAudio? 'green' : 'lawngreen'
        opacity: 0
        Drag.active: trimInMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
803
        visible: trimInMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width)
804

805 806 807 808 809 810 811 812 813 814
        ToolTip {
            visible: trimInMouseArea.containsMouse && !trimInMouseArea.pressed
            delay: 1000
            timeout: 5000
            background: Rectangle {
                color: activePalette.alternateBase
                border.color: activePalette.light
            }
            contentItem: Label {
                color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
815
                font.pointSize: root.fontUnit
816 817 818 819
                text: i18n("In:%1\nPosition:%2", timeline.simplifiedTC(clipRoot.inPoint),timeline.simplifiedTC(clipRoot.modelStart))
            }
        }

820 821 822 823 824 825
        MouseArea {
            id: trimInMouseArea
            anchors.fill: parent
            hoverEnabled: true
            drag.target: parent
            drag.axis: Drag.XAxis
826
            drag.smoothed: false
827
            property bool shiftTrim: false
828
            property bool controlTrim: false
829
            property bool sizeChanged: false
830
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
831
            onPressed: {
832
                root.autoScrolling = false
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
833
                clipRoot.originalX = clipRoot.x
834
                clipRoot.originalDuration = clipDuration
835
                parent.anchors.left = undefined
836
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
837
                controlTrim = mouse.modifiers & Qt.ControlModifier
838 839 840
                if (!shiftTrim && clipRoot.grouped) {
                    clipRoot.initGroupTrim(clipRoot)
                }
841
                parent.opacity = 0
842 843
            }
            onReleased: {
844
                root.autoScrolling = timeline.autoScroll
845
                parent.anchors.left = clipRoot.left
846
                if (sizeChanged) {
847
                    clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim)
848 849
                    sizeChanged = false
                }
850 851 852
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
853
                    var delta = Math.round((trimIn.x) / timeScale)
854
                    if (delta !== 0) {
855 856 857
                        if (delta < -modelStart) {
                            delta = -modelStart
                        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
858
                        var newDuration =  clipDuration - delta
859
                        sizeChanged = true
860
                        clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
861
                    }
862 863
                }
            }
864
            onEntered: {
865 866 867
                if (!pressed) {
                    parent.opacity = 0.5
                }
868 869 870 871
            }
            onExited: {
                parent.opacity = 0
            }
872 873 874 875
        }
    }
    Rectangle {
        id: trimOut
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
876
        anchors.right: clipRoot.right
877 878
        anchors.rightMargin: 0
        height: parent.height
879
        width: root.baseUnit / 2
880 881
        color: 'red'
        opacity: 0
882
        enabled: !isLocked
883 884
        Drag.active: trimOutMouseArea.drag.active
        Drag.proposedAction: Qt.MoveAction
885
        visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width)
886

887 888 889 890 891 892 893 894 895 896
        ToolTip {
            visible: trimOutMouseArea.containsMouse && !trimOutMouseArea.pressed
            delay: 1000
            timeout: 5000
            background: Rectangle {
                color: activePalette.alternateBase
                border.color: activePalette.light
            }
            contentItem: Label {
                color: activePalette.text
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
897
                font.pointSize: root.fontUnit
898 899 900
                text: i18n("Out: ") + timeline.simplifiedTC(clipRoot.outPoint)
            }
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
901

902 903 904 905
        MouseArea {
            id: trimOutMouseArea
            anchors.fill: parent
            hoverEnabled: true
906
            property bool shiftTrim: false
907
            property bool controlTrim: false
908
            property bool sizeChanged: false
909
            cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
910 911
            drag.target: parent
            drag.axis: Drag.XAxis
912
            drag.smoothed: false
913 914

            onPressed: {
915
                root.autoScrolling = false
916
                clipRoot.originalDuration = clipDuration
917
                parent.anchors.right = undefined
918
                shiftTrim = mouse.modifiers & Qt.ShiftModifier
919
                controlTrim = mouse.modifiers & Qt.ControlModifier
920 921 922
                if (!shiftTrim && clipRoot.grouped) {
                    clipRoot.initGroupTrim(clipRoot)
                }
923
                parent.opacity = 0
924 925
            }
            onReleased: {
926
                root.autoScrolling = timeline.autoScroll
927
                parent.anchors.right = clipRoot.right
928
                if (sizeChanged) {
929
                    clipRoot.trimmedOut(clipRoot, shiftTrim, controlTrim)
930 931
                    sizeChanged = false
                }
932 933 934 935
            }
            onPositionChanged: {
                if (mouse.buttons === Qt.LeftButton) {
                    var newDuration = Math.round((parent.x + parent.width) / timeScale)
936
                    if (newDuration != clipDuration) {
937
                        sizeChanged = true
938
                        clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
939
                    }
940 941
                }
            }
942 943 944 945 946
            onEntered: {
                if (!pressed) {
                    parent.opacity = 0.5
                }
            }
947 948 949
            onExited: parent.opacity = 0
        }
    }
950

951
        /*MenuItem {
952
            id: mergeItem
Yuri Chornoivan's avatar
Yuri Chornoivan committed
953
            text: i18n("Merge with next clip")
954 955 956
            onTriggered: timeline.mergeClipWithNext(trackIndex, index, false)
        }
        MenuItem {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
957
            text: i18n("Rebuild Audio Waveform")
958
            onTriggered: timeline.remakeAudioLevels(trackIndex, index)
959
        }*/
960
        /*onPopupVisibleChanged: {
961 962 963 964 965
            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))
            }
966
        }*/
967
}