kdenliveclipmonitor.qml 21.7 KB
Newer Older
1
import QtQuick.Controls 2.4
2 3
import QtQuick.Window 2.2
import Kdenlive.Controls 1.0
4
import QtQuick 2.11
5
import com.enums 1.0
6 7 8

Item {
    id: root
9
    objectName: "root"
10

11 12
    SystemPalette { id: activePalette }

13 14
    // default size, but scalable by user
    height: 300; width: 400
15
    property string markerText
16
    property int itemType: 0
17
    property point profile: controller.profile
18
    property double zoom
19 20
    property double scalex
    property double scaley
21
    // Zoombar properties
22
    // The start position of the zoomed area, between 0 and 1
23
    property double zoomStart: 0
24
    // The zoom factor (between 0 and 1). 0.5 means 2x zoom
25
    property double zoomFactor: 1
26
    // The pixel height of zoom bar, used to offset markers info
27
    property int zoomOffset: 0
28
    property bool showZoomBar: false
29 30 31 32 33 34
    property bool dropped: false
    property string fps: '-'
    property bool showMarkers: false
    property bool showTimecode: false
    property bool showFps: false
    property bool showSafezone: false
35
    // Display hover audio thumbnails overlay
36
    property bool showAudiothumb: false
37 38
    // Always display audio thumbs under video
    property bool permanentAudiothumb: false
39
    property bool showToolbar: false
40
    property string clipName: controller.clipName
41
    property real baseUnit: fontMetrics.font.pixelSize
42 43 44 45
    property int duration: 300
    property int mouseRulerPos: 0
    property double frameSize: 10
    property double timeScale: 1
46 47 48
    property int overlayType: controller.overlayType
    property color overlayColor: 'cyan'
    property bool isClipMonitor: true
49
    property int dragType: 0
50
    property int overlayMargin: (audioThumb.stateVisible && !audioThumb.isAudioClip && audioThumb.visible) ? (audioThumb.height + root.zoomOffset) : root.zoomOffset + (audioThumb.isAudioClip && audioSeekZone.visible) ? audioSeekZone.height : 0
51
    
52 53
    FontMetrics {
        id: fontMetrics
54
        font: fixedFont
55 56
    }

57 58 59 60 61
    Timer {
        id: thumbTimer
        interval: 3000; running: false;
    }

62
    signal editCurrentMarker()
63

64 65 66 67 68 69 70 71 72 73
    function updateScrolling()
    {
        if (thumbMouseArea.pressed) {
            var pos = Math.max(thumbMouseArea.mouseX, 0)
            pos += audioThumb.width/root.zoomFactor * root.zoomStart
            controller.setPosition(Math.min(pos / root.timeScale, root.duration));
            
        }
    }

74
    onDurationChanged: {
75
        clipMonitorRuler.updateRuler()
76 77 78 79 80
        // Reset zoom on clip change
        root.zoomStart = 0
        root.zoomFactor = 1
        root.showZoomBar = false
        root.zoomOffset = 0
81
    }
82 83 84
    onWidthChanged: {
        clipMonitorRuler.updateRuler()
    }
85 86 87 88
    onClipNameChanged: {
        // Animate clip name
        clipNameLabel.opacity = 1
        showAnimate.restart()
89 90 91 92 93 94 95 96 97 98 99 100 101 102
        // adjust monitor image size if audio thumb is displayed
        if (audioThumb.stateVisible && root.permanentAudiothumb && audioThumb.visible) {
            controller.rulerHeight = audioThumb.height + root.zoomOffset
        } else {
            controller.rulerHeight = root.zoomOffset
        }
    }
    
    onZoomOffsetChanged: {
        if (audioThumb.stateVisible && root.permanentAudiothumb && audioThumb.visible) {
            controller.rulerHeight = audioThumb.height + root.zoomOffset
        } else {
            controller.rulerHeight = root.zoomOffset
        }
103
    }
104 105 106 107 108 109 110
    
    onHeightChanged: {
        if (audioThumb.stateVisible && root.permanentAudiothumb && audioThumb.visible) {
            controller.rulerHeight = audioThumb.height + root.zoomOffset
        } else {
            controller.rulerHeight = root.zoomOffset
        }
111
    }
112

113 114 115 116
    function updatePalette() {
        clipMonitorRuler.forceRepaint()
    }

117
    function switchOverlay() {
118
        if (controller.overlayType >= 5) {
119 120 121 122 123 124
            controller.overlayType = 0
        } else {
            controller.overlayType = controller.overlayType + 1;
        }
        root.overlayType = controller.overlayType
    }
125

126 127 128 129 130
    MouseArea {
        id: barOverArea
        hoverEnabled: true
        acceptedButtons: Qt.NoButton
        anchors.fill: parent
131 132 133
        onWheel: {
            controller.seek(wheel.angleDelta.x + wheel.angleDelta.y, wheel.modifiers)
        }
134
    }
135

136 137
    SceneToolBar {
        id: sceneToolBar
138 139 140 141 142
        barContainsMouse: sceneToolBar.rightSide ? barOverArea.mouseX >= x - 10 : barOverArea.mouseX < x + width + 10
        onBarContainsMouseChanged: {
            sceneToolBar.opacity = 1
            sceneToolBar.visible = sceneToolBar.barContainsMouse
        }
143 144 145 146 147
        anchors {
            right: parent.right
            top: parent.top
            topMargin: 4
            rightMargin: 4
148
            leftMargin: 4
149 150
        }
    }
151

152
    Item {
153
        height: root.height - controller.rulerHeight
154 155 156 157 158 159
        width: root.width
        Item {
            id: frame
            objectName: "referenceframe"
            width: root.profile.x * root.scalex
            height: root.profile.y * root.scaley
160
            anchors.centerIn: parent
161
            anchors.verticalCenterOffset : (root.permanentAudiothumb && audioThumb.visible) ? -(audioThumb.height + root.zoomOffset) / 2 : -root.zoomOffset / 2
162

163 164 165 166 167
            Loader {
                anchors.fill: parent
                source: {
                    switch(root.overlayType)
                    {
168
                        case 0:
169
                            return '';
170
                        case 1:
171
                            return "OverlayStandard.qml";
172
                        case 2:
173
                            return "OverlayMinimal.qml";
174
                        case 3:
175
                            return "OverlayCenter.qml";
176
                        case 4:
177
                            return "OverlayCenterDiagonal.qml";
178
                        case 5:
179
                            return "OverlayThirds.qml";
180
                    }
181
                }
182 183
            }
        }
184 185 186
        Item {
            id: monitorOverlay
            anchors.fill: parent
187

188
            Item {
189
                id: audioThumb
190
                property bool stateVisible: (root.permanentAudiothumb || clipMonitorRuler.containsMouse || thumbMouseArea.containsMouse || dragZone.opacity == 1 || thumbTimer.running || root.showZoomBar)
191
                property bool isAudioClip: controller.clipType == ProducerType.Audio
192 193 194
                anchors {
                    left: parent.left
                    bottom: parent.bottom
195
                    bottomMargin: root.zoomOffset
196
                }
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
                Label {
                    id: clipStreamLabel
                    font: fixedFont
                    anchors {
                        bottom: audioThumb.isAudioClip ? parent.bottom : parent.top
                        horizontalCenter: parent.horizontalCenter
                    }
                    color: "white"
                    text: controller.clipStream
                    background: Rectangle {
                        color: "#222277"
                    }
                    visible: text != ""
                    padding :4
                }
212
                height: isAudioClip ? parent.height : parent.height / 6
213 214
                //font.pixelSize * 3
                width: parent.width
215 216 217 218 219 220 221 222 223
                visible: (root.permanentAudiothumb || root.showAudiothumb) && (isAudioClip || controller.clipType == ProducerType.AV)
                onStateVisibleChanged: {
                    // adjust monitor image size
                    if (stateVisible && root.permanentAudiothumb && audioThumb.visible) {
                        controller.rulerHeight = audioThumb.height + root.zoomOffset
                    } else {
                        controller.rulerHeight = root.zoomOffset
                    }
                }
224

225
                states: [
226
                    State { when: audioThumb.stateVisible || audioThumb.isAudioClip;
227
                        PropertyChanges {   target: audioThumb; opacity: 1.0    } },
228
                    State { when: !audioThumb.stateVisible && !audioThumb.isAudioClip;
229
                        PropertyChanges {   target: audioThumb; opacity: 0.0    } }
230 231
                ]
                transitions: [ Transition {
232
                    NumberAnimation { property: "opacity"; duration: audioThumb.isAudioClip ? 0 : 500}
233
                } ]
234
                Rectangle {
235
                    color: activePalette.dark
236
                    opacity: audioThumb.isAudioClip || root.permanentAudiothumb ? 1 : 0.6
237 238 239 240 241 242
                    anchors.fill: parent
                }
                Rectangle {
                    color: "yellow"
                    opacity: 0.3
                    height: parent.height
243 244
                    x: controller.zoneIn * timeScale - (audioThumb.width/root.zoomFactor * root.zoomStart)
                    width: (controller.zoneOut - controller.zoneIn) * timeScale
245 246
                    visible: controller.zoneIn > 0 || controller.zoneOut < duration - 1
                }
247 248 249
                Repeater {
                    id: streamThumb
                    model: controller.audioThumb.length
250 251 252
                    onCountChanged: {
                        thumbTimer.start()
                    }
253 254 255 256 257 258 259 260 261
                    property double streamHeight: parent.height / streamThumb.count
                    Item {
                        anchors.fill: parent
                        Image {
                            anchors.left: parent.left
                            anchors.right: parent.right
                            height: streamThumb.streamHeight
                            y: model.index * height
                            source: controller.audioThumb[model.index]
262 263 264 265
                            transform: [
                                Translate { x: (-audioThumb.width * root.zoomStart)},
                                Scale {xScale: 1/root.zoomFactor}
                            ]
266
                            asynchronous: true
267
                            cache: false
268
                            smooth: false
269 270 271 272 273 274 275 276 277
                        }
                        Rectangle {
                            width: parent.width
                            y: (model.index + 1) * streamThumb.streamHeight
                            height: 1
                            visible: streamThumb.count > 1 && model.index < streamThumb.count - 1
                            color: 'black'
                        }
                    }
278 279 280 281 282
                }
                Rectangle {
                    color: "red"
                    width: 1
                    height: parent.height
283
                    x: controller.position * timeScale - (audioThumb.width/root.zoomFactor * root.zoomStart)
284
                }
285 286 287
                MouseArea {
                    id: thumbMouseArea
                    anchors.fill: parent
288
                    acceptedButtons: Qt.LeftButton
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
289
                    hoverEnabled: true
290
                    propagateComposedEvents: true
291
                    onPressed: {
292 293 294 295
                        if (audioThumb.isAudioClip && mouseY < audioSeekZone.y) {
                            mouse.accepted = false
                            return
                        }
296 297 298 299
                        var pos = Math.max(mouseX, 0)
                        pos += audioThumb.width/root.zoomFactor * root.zoomStart
                        controller.setPosition(Math.min(pos / root.timeScale, root.duration));
                    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
300
                    onPositionChanged: {
301 302 303 304 305
                        if (audioThumb.isAudioClip && mouseY < audioSeekZone.y) {
                            mouse.accepted = false
                            return
                        }
                        if (mouse.modifiers & Qt.ShiftModifier || pressed) {
306
                            var pos = Math.max(mouseX, 0)
307
                            pos += audioThumb.width/root.zoomFactor * root.zoomStart
308
                            controller.setPosition(Math.min(pos / root.timeScale, root.duration));
309 310
                        }
                    }
311 312 313 314 315 316 317 318 319
                    onWheel: {
                        if (wheel.modifiers & Qt.ControlModifier) {
                            if (wheel.angleDelta.y < 0) {
                                // zoom out
                                clipMonitorRuler.zoomOutRuler(wheel.x)
                            } else {
                                // zoom in
                                clipMonitorRuler.zoomInRuler(wheel.x)
                            }
320 321
                        } else {
                            wheel.accepted = false
322
                        }
323
                        
324
                    }
325 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
                    Rectangle {
                        id: audioSeekZone
                        width: parent.width
                        height: parent.height / 6
                        anchors.centerIn: parent
                        anchors.verticalCenterOffset: audioThumb.isAudioClip ? parent.height * 5 / 12 : 0
                        visible: audioThumb.isAudioClip && thumbMouseArea.containsMouse && thumbMouseArea.mouseY > y
                        color: 'yellow'
                        opacity: 0.5
                        Rectangle {
                            width: parent.width
                            height: 1
                            color: '#000'
                            anchors.top: parent.top
                        }
                        // frame ticks
                        Repeater {
                            id: rulerAudioTicks
                            model: parent.width / root.frameSize + 2
                            Rectangle {
                                x: index * root.frameSize - (clipMonitorRuler.rulerZoomOffset % root.frameSize)
                                anchors.top: audioSeekZone.top
                                height: (index % 5) ? audioSeekZone.height / 6 : audioSeekZone.height / 3
                                width: 1
                                color: '#000'
                                opacity: 0.8
                            }
                        }
                    }
354
                }
355
            }
356 357 358 359 360 361 362 363 364
            Label {
                id: clipNameLabel
                font: fixedFont
                anchors {
                    top: parent.top
                    horizontalCenter: parent.horizontalCenter
                }
                color: "white"
                text: clipName
365 366 367 368 369 370
                onTextChanged: {
                    if (thumbTimer.running) {
                        thumbTimer.stop()
                    }
                    thumbTimer.start()
                }
371 372 373 374
                background: Rectangle {
                    color: "#222277"
                }
                visible: clipName != ""
375
                padding :4
376 377 378 379 380 381 382
                SequentialAnimation {
                    id: showAnimate
                    running: false
                    NumberAnimation { target: clipNameLabel; duration: 3000 }
                    NumberAnimation { target: clipNameLabel; property: "opacity"; to: 0; duration: 1000 }
                }
            }
383

384
            Label {
385
                id: timecode
386 387
                font.family: fontMetrics.font.family
                font.pointSize: 1.5 * fontMetrics.font.pointSize
388
                objectName: "timecode"
389 390 391 392 393
                color: "#ffffff"
                padding: 2
                background: Rectangle {
                    color: "#66000000"
                }
394
                text: controller.toTimecode(controller.position)
395 396 397 398
                visible: root.showTimecode
                anchors {
                    right: parent.right
                    bottom: parent.bottom
399
                    bottomMargin: overlayMargin
400
                }
401
            }
402
            Label {
403
                id: fpsdropped
404 405
                font.family: fontMetrics.font.family
                font.pointSize: 1.5 * fontMetrics.font.pointSize
406
                objectName: "fpsdropped"
407 408 409
                color: "#ffffff"
                padding: 2
                background: Rectangle {
410
                    color: root.dropped ? "#99ff0000" : "#66004400"
411
                }
412
                text: i18n("%1fps", root.fps)
413 414 415 416
                visible: root.showFps
                anchors {
                    right: timecode.visible ? timecode.left : parent.right
                    bottom: parent.bottom
417
                    bottomMargin: overlayMargin
418
                }
419
            }
420 421 422 423 424 425
            Label {
                id: inPoint
                font: fixedFont
                anchors {
                    left: parent.left
                    bottom: parent.bottom
426
                    bottomMargin: overlayMargin
427 428 429 430 431 432 433
                }
                visible: root.showMarkers && controller.position == controller.zoneIn
                text: i18n("In Point")
                color: "white"
                background: Rectangle {
                    color: "#228b22"
                }
434
                padding:4
435 436 437 438 439 440 441 442
                horizontalAlignment: TextInput.AlignHCenter
            }
            Label {
                id: outPoint
                font: fixedFont
                anchors {
                    left: inPoint.visible ? inPoint.right : parent.left
                    bottom: parent.bottom
443
                    bottomMargin: overlayMargin
444
                }
445
                visible: root.showMarkers && controller.position + 1 == controller.zoneOut
446 447 448
                text: i18n("Out Point")
                color: "white"
                background: Rectangle {
449
                    color: "#770000"
450
                }
451
                padding: 4
452 453
                horizontalAlignment: TextInput.AlignHCenter
            }
454 455
            TextField {
                id: marker
456
                font: fixedFont
457 458
                objectName: "markertext"
                activeFocusOnPress: true
459
                text: controller.markerComment
460 461 462 463 464 465
                onEditingFinished: {
                    root.markerText = marker.displayText
                    marker.focus = false
                    root.editCurrentMarker()
                }
                anchors {
466
                    left: outPoint.visible ? outPoint.right : inPoint.visible ? inPoint.right : parent.left
467
                    bottom: parent.bottom
468
                    bottomMargin: overlayMargin
469 470
                }
                visible: root.showMarkers && text != ""
471 472 473
                height: inPoint.height
                width: fontMetrics.boundingRect(displayText).width + 10
                horizontalAlignment: displayText == text ? TextInput.AlignHCenter : TextInput.AlignLeft
474
                background: Rectangle {
475
                        color: "#990000ff"
476
                }
477 478
                color: "#ffffff"
                padding: 0
479
                maximumLength: 20
480
            }
481
        }
482

483 484
        Rectangle {
            // Audio or video only drag zone
485
            id: dragZone
486
            x: 2
487
            y: inPoint.visible || outPoint.visible || marker.visible ? parent.height - inPoint.height - height - 2 - overlayMargin : parent.height - height - 2 - overlayMargin
488 489 490 491
            width: childrenRect.width
            height: childrenRect.height
            color: Qt.rgba(activePalette.highlight.r, activePalette.highlight.g, activePalette.highlight.b, 0.7)
            radius: 4
492
            opacity: (dragAudioArea.containsMouse || dragVideoArea.containsMouse  || thumbMouseArea.containsMouse || (barOverArea.containsMouse && (barOverArea.mouseY >= (parent.height - inPoint.height - height - 2 - (audioThumb.height + root.zoomOffset) - root.baseUnit)))) ? 1 : 0
493
            visible: controller.clipHasAV
494 495 496 497 498 499 500 501
            onOpacityChanged: {
                if (opacity == 1) {
                    videoDragButton.x = 0
                    videoDragButton.y = 0
                    audioDragButton.x = videoDragButton.x + videoDragButton.width
                    audioDragButton.y = 0
                }
            }
502
            Row {
503 504 505 506
                id: dragRow
                ToolButton {
                    id: videoDragButton
                    icon.name: "kdenlive-show-video"
507 508 509
                    Drag.active: dragVideoArea.drag.active
                    Drag.dragType: Drag.Automatic
                    Drag.mimeData: {
510
                        "kdenlive/producerslist" : "V" + controller.clipId + "/" + controller.zoneIn + "/" + (controller.zoneOut - 1)
511
                    }
512 513 514 515 516 517
                    MouseArea {
                        id: dragVideoArea
                        hoverEnabled: true
                        anchors.fill: parent
                        propagateComposedEvents: true
                        cursorShape: Qt.PointingHand
518
                        drag.target: parent
519
                        onExited: {
520 521
                            parent.x = 0
                            parent.y = 0
522 523 524 525 526 527
                        }
                    }
                }
                ToolButton {
                    id: audioDragButton
                    icon.name: "audio-volume-medium"
528 529 530
                    Drag.active: dragAudioArea.drag.active
                    Drag.dragType: Drag.Automatic
                    Drag.mimeData: {
531
                        "kdenlive/producerslist" : "A" + controller.clipId + "/" + controller.zoneIn + "/" + (controller.zoneOut - 1)
532
                    }
533 534 535 536 537 538
                    MouseArea {
                        id: dragAudioArea
                        hoverEnabled: true
                        anchors.fill: parent
                        propagateComposedEvents: true
                        cursorShape: Qt.PointingHand
539
                        drag.target: parent
540
                        onExited: {
541 542
                            parent.x = videoDragButton.x + videoDragButton.width
                            parent.y = 0
543 544 545
                        }
                    }
                }
546 547
            }
        }
548
    }
549 550 551 552 553 554 555
    MonitorRuler {
        id: clipMonitorRuler
        anchors {
            left: root.left
            right: root.right
            bottom: root.bottom
        }
556
        height: controller.rulerHeight
557
    }
558
}