TrackHead.qml 24 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.11
20
import QtQuick.Controls 2.4
21
22
23

Rectangle {
    id: trackHeadRoot
24
    property string trackName
25
26
    property string effectNames
    property bool isStackEnabled
27
    property bool isDisabled
28
    property bool collapsed: false
29
    property int isComposite
30
31
    property bool isLocked: false
    property bool isActive: false
32
    property bool isAudio
33
    property bool showAudioRecord: false
34
    property bool current: false
35
    property int trackId : -42
36
    property string trackTag
37
    property int thumbsFormat: 0
38
    border.width: 1
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
39
    border.color: root.frameColor
40
41

    function pulseLockButton() {
42
        flashLock.restart();
43
44
    }

45
    color: getTrackColor(isAudio, true)
46
47
    //border.color: selected? 'red' : 'transparent'
    //border.width: selected? 1 : 0
48
49
50
51
52
53
54
55
56
57
58
59
    clip: true
    state: 'normal'
    states: [
        State {
            name: 'current'
            when: trackHeadRoot.current
            PropertyChanges {
                target: trackHeadRoot
                color: selectedTrackColor
            }
        },
        State {
60
            when: !trackHeadRoot.current
61
62
63
            name: 'normal'
            PropertyChanges {
                target: trackHeadRoot
64
                color: getTrackColor(isAudio, true)
65
66
67
68
            }
        }
    ]

69
70
71
72
73
74
    Keys.onDownPressed: {
        root.moveSelectedTrack(1)
    }
    Keys.onUpPressed: {
        root.moveSelectedTrack(-1)
    }
75

76
    MouseArea {
77
        id: headerMouseArea
78
        anchors.fill: parent
79
        hoverEnabled: true
80
        acceptedButtons: Qt.LeftButton | Qt.RightButton
81
        onPressed: {
82
            timeline.activeTrack = trackId
83
            if (mouse.button == Qt.RightButton) {
84
                root.showHeaderMenu()
85
86
87
            }
        }
        onClicked: {
88
            console.log('TRACK ID: ', trackId)
89
            parent.forceActiveFocus()
90
            nameEdit.visible = false
91
92
93
            if (mouse.button == Qt.LeftButton) {
                timeline.showTrackAsset(trackId)
            }
94
95
        }
    }
96
97
98
99
100
101
102
103
104
    Label {
        id: trackTarget
        property color bgColor: 'grey'
        font: miniFont
        color: timeline.targetTextColor
        background: Rectangle {
            color: trackTarget.bgColor
        }
        width: 2 * fontMetrics.boundingRect("M").width
105
        height: trackHeadRoot.height
106
        verticalAlignment: Text.AlignTop
107
        horizontalAlignment: Text.AlignHCenter
108
        visible: trackHeadRoot.isAudio ? timeline.hasAudioTarget > 0 : timeline.hasVideoTarget
109
110
111
        anchors.top: parent.top
        anchors.bottom: parent.bottom
        anchors.margins: 1
112

113
114
        MouseArea {
            id: targetArea
115
            anchors.fill: parent
116
117
118
119
120
121
122
123
124
            hoverEnabled: true
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            cursorShape: Qt.PointingHandCursor
            onClicked: {
                if (mouse.button == Qt.RightButton) {
                    if (trackHeadRoot.isAudio) {
                        root.showTargetMenu(trackId)
                    } else {
                        root.showHeaderMenu()
125
126
                    }
                }
127
128
129
130
131
132
133
134
                else {
                    if (trackHeadRoot.isAudio) {
                        timeline.switchAudioTarget(trackHeadRoot.trackId);
                    } else {
                        if (trackHeadRoot.trackId == timeline.videoTarget) {
                            timeline.videoTarget = -1;
                        } else if (timeline.hasVideoTarget) {
                            timeline.videoTarget = trackHeadRoot.trackId;
135
136
                        }
                    }
137
138
                }
            }
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
            ToolButton {
                focusPolicy: Qt.NoFocus
                visible: trackHeadRoot.isAudio && timeline.clipTargets > 1 && trackHeadRoot.height > (2 * expandButton.height)
                background: Rectangle {
                    color: Qt.darker(trackTarget.bgColor, 1.5)
                    border.color: activePalette.light
                }
                anchors.bottom: parent.bottom
                width: parent.width
                height: width
                contentItem: Item {
                    Image {
                        source: "image://icon/go-down"
                        anchors.fill: parent
                    }
                }
                onClicked: {
                    root.showTargetMenu(trackId)
                }
            }
159
160
161
162
163
164
165
166
167
168
169
170
171
        }
        ToolTip {
            visible: targetArea.containsMouse
            font: miniFont
            delay: 1500
            timeout: 5000
            background: Rectangle {
                color: activePalette.alternateBase
                border.color: activePalette.light
            }
            contentItem: Label {
                color: activePalette.text
                text: i18n("Click to toggle track as target. Target tracks will receive the inserted clips")
172
173
            }
        }
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    state:  'normalTarget'
    states: [
        State {
            name: 'target'
            when: (trackHeadRoot.isAudio && timeline.audioTarget.indexOf(trackHeadRoot.trackId) > -1) || (!trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.videoTarget)
            PropertyChanges {
                target: trackTarget
                bgColor: timeline.targetColor
                text: trackHeadRoot.isAudio ? timeline.audioTargetName(trackHeadRoot.trackId) : ''
            }
        },
        State {
            name: 'inactiveTarget'
            when: (trackHeadRoot.isAudio && timeline.lastAudioTarget.indexOf(trackHeadRoot.trackId) > -1) || (!trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.lastVideoTarget)
            PropertyChanges {
                target: trackTarget
                opacity: 0.3
                bgColor: activePalette.text
                text: trackHeadRoot.isAudio ? timeline.audioTargetName(trackHeadRoot.trackId) : ''
            }
        },
        State {
            name: 'noTarget'
            when: !trackHeadRoot.isLocked && !trackHeadRoot.isDisabled
            PropertyChanges {
                target: trackTarget
                bgColor: activePalette.base
                text: ''
            }
        }
    ]
    transitions: [
        Transition {
            to: '*'
            ColorAnimation { target: trackTarget; duration: 300 }
        }
    ]
211
    }
212
    Item {
213
        id: trackHeadColumn
214
        anchors.fill: parent
215
        anchors.leftMargin: trackTarget.width
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
216
        anchors.topMargin: 0
217
218
219

        ToolButton {
            id: expandButton
220
            focusPolicy: Qt.NoFocus
221
            property var modifier: 0
222
223
224
225
226
227
228
229
230
            contentItem: Item {
                Image {
                    source: trackHeadRoot.collapsed ? "image://icon/go-next" : "image://icon/go-down"
                    anchors.centerIn: parent
                    width: root.collapsedHeight - 4
                    height: root.collapsedHeight - 4
                    cache: root.paletteUnchanged
                }
            }
231
            onClicked: {
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
                if (modifier & Qt.ShiftModifier) {
                    // Collapse / expand all tracks
                    timeline.collapseAllTrackHeight(trackId, !trackHeadRoot.collapsed, root.collapsedHeight)
                } else {
                    if (trackHeadRoot.collapsed) {
                        var newHeight = Math.max(root.collapsedHeight * 1.5, controller.getTrackProperty(trackId, "kdenlive:trackheight"))
                        controller.setTrackProperty(trackId, "kdenlive:trackheight", newHeight)
                        controller.setTrackProperty(trackId, "kdenlive:collapsed", "0")
                    } else {
                        controller.setTrackProperty(trackId, "kdenlive:collapsed", root.collapsedHeight)
                    }
                }
            }
            MouseArea {
                // Used to pass modifier state to expand button
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton
                onPressed: {
                    expandButton.modifier = mouse.modifiers
                    mouse.accepted = false
                }
253
            }
254
255
256
            anchors.left: parent.left
            width: root.collapsedHeight
            height: root.collapsedHeight
257
258
            ToolTip {
                visible: expandButton.hovered
259
                font: miniFont
260
261
262
263
264
265
266
267
268
                delay: 1500
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
                }
                contentItem: Label {
                    color: activePalette.text
                    text: trackLabel.visible? i18n("Minimize") : i18n("Expand")
269
270
                }
            }
271
        }
272
273
274
        Label {
            id: trackLed
            property color bgColor: Qt.darker(trackHeadRoot.color, 0.55)
275
            anchors.left: expandButton.right
276
277
278
279
280
281
            font: miniFont
            text: trackHeadRoot.trackTag
            color: activePalette.text
            background: Rectangle {
                color: trackLed.bgColor
            }
282
            width: fontMetrics.boundingRect("M").width * trackHeadRoot.trackTag.length
283
            height: root.collapsedHeight - 2
284
285
286
287
288
            y: 1
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            MouseArea {
                id: tagMouseArea
289
                anchors.fill: parent
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
                hoverEnabled: true
                cursorShape: Qt.PointingHandCursor
                onClicked: {
                    timeline.switchTrackActive(trackHeadRoot.trackId)
                }
            }
            ToolTip {
                visible: tagMouseArea.containsMouse
                font: miniFont
                delay: 1500
                timeout: 5000
                background: Rectangle {
                    color: activePalette.alternateBase
                    border.color: activePalette.light
                }
                contentItem: Label {
306
                    color: activePalette.text
307
                    text: i18n("Click to make track active/inactive. Active tracks will react to editing operations")
308
309
                }
                }
310
311
312
313
314
315
316
317
        state:  'normalled'
            states: [
                State {
                    name: 'locked'
                    when: trackHeadRoot.isLocked
                    PropertyChanges {
                        target: trackLed
                        bgColor: 'red'
318
                    }
319
320
321
322
323
324
325
326
                },
                State {
                    name: 'active'
                    when: trackHeadRoot.isActive
                    PropertyChanges {
                        target: trackLed
                        bgColor: timeline.targetColor
                        color: timeline.targetTextColor
327
                    }
328
329
330
331
332
333
334
                },
                State {
                    name: 'inactive'
                    when: !trackHeadRoot.isLocked && !trackHeadRoot.isActive
                    PropertyChanges {
                        target: trackLed
                        bgColor: Qt.darker(trackHeadRoot.color, 0.55)
335
                    }
336
337
338
339
340
341
342
343
                }
            ]
            transitions: [
                Transition {
                    to: '*'
                    ColorAnimation { target: trackLed; duration: 300 }
                }
            ]
344
345
        }
        Label {
346
            anchors.left: trackLed.right
347
            anchors.top: parent.top
348
            anchors.leftMargin: 2
349
            height: trackLed.height
350
            width: buttonsRow.x - x
351
            text: trackHeadRoot.trackName
352
            elide: Text.ElideRight
353
            font: miniFont
354
            verticalAlignment: Text.AlignVCenter
355
356
            horizontalAlignment: Text.AlignLeft
            visible: !trackLabel.visible && trackHeadRoot.width > (trackTarget.width + expandButton.width + trackLed.width + (4 * muteButton.width) + 4)
357
358
        }
        Row {
359
            id: buttonsRow
360
            width: childrenRect.width
361
            x: Math.max(2 * root.collapsedHeight + 2, parent.width - width - 4)
362
            spacing: 0
363
            ToolButton {
364
                id: effectButton
365
366
367
368
369
370
371
372
373
374
375
                focusPolicy: Qt.NoFocus
                contentItem: Item {
                    Image {
                        source: "image://icon/tools-wizard"
                        anchors.centerIn: parent
                        width: root.collapsedHeight - 4
                        height: root.collapsedHeight - 4
                        cache: root.paletteUnchanged
                        opacity: effectButton.enabled ? 1 : 0.5
                    }
                }
376
                enabled: trackHeadRoot.effectNames != ''
377
                checkable: true
378
379
380
381
                checked: enabled && trackHeadRoot.isStackEnabled
                onClicked: {
                    timeline.showTrackAsset(trackId)
                    controller.setTrackStackEnabled(trackId, !isStackEnabled)
382
                }
383
384
                width: root.collapsedHeight
                height: root.collapsedHeight
385
            }
386
387
            ToolButton {
                id: muteButton
388
                focusPolicy: Qt.NoFocus
389
390
391
392
393
394
395
396
397
398
399
                contentItem: Item {
                    Image {
                        source: isAudio ? (isDisabled ? "image://icon/kdenlive-hide-audio" : "image://icon/kdenlive-show-audio") : (isDisabled ? "image://icon/kdenlive-hide-video" : "image://icon/kdenlive-show-video")
                        anchors.centerIn: parent
                        width: root.collapsedHeight - 4
                        height: root.collapsedHeight - 4
                        cache: root.paletteUnchanged
                    }
                }
                width: root.collapsedHeight
                height: root.collapsedHeight
400
                onClicked: controller.hideTrack(trackId, isDisabled ? (isAudio ? '1' : '2') : '3')
401
402
                ToolTip {
                    visible: muteButton.hovered
403
                    font: miniFont
404
405
406
407
408
409
410
411
412
413
414
                    delay: 1500
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
                        text: isAudio ? (isDisabled? i18n("Unmute") : i18n("Mute")) : (isDisabled? i18n("Show") : i18n("Hide"))
                    }
                }
415
416
417
418
            }

            ToolButton {
                id: lockButton
419
420
                width: root.collapsedHeight
                height: root.collapsedHeight
421
                focusPolicy: Qt.NoFocus
422
423
424
425
426
427
428
429
430
                contentItem: Item {
                    Image {
                        source: trackHeadRoot.isLocked ? "image://icon/kdenlive-lock" : "image://icon/kdenlive-unlock"
                        anchors.centerIn: parent
                        width: root.collapsedHeight - 4
                        height: root.collapsedHeight - 4
                        cache: root.paletteUnchanged
                    }
                }
431
                onClicked: controller.setTrackLockedState(trackId, !isLocked)
432
433
                ToolTip {
                    visible: lockButton.hovered
434
                    font: miniFont
435
436
437
438
439
440
441
442
443
444
445
                    delay: 1500
                    timeout: 5000
                    background: Rectangle {
                        color: activePalette.alternateBase
                        border.color: activePalette.light
                    }
                    contentItem: Label {
                        color: activePalette.text
                        text: isLocked? i18n("Unlock track") : i18n("Lock track")
                    }
                }
446

447
                SequentialAnimation {
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
                    id: flashLock
                    loops: 1
                    ScaleAnimator {
                        target: lockButton
                        from: 1
                        to: 2
                        duration: 500
                    }
                    ScaleAnimator {
                        target: lockButton
                        from: 2
                        to: 1
                        duration: 500
                    }
                 }
            }
464
        }
465
466
        Item {
            id: recLayout
467
            y: root.collapsedHeight + 4
468
469
470
            anchors.left: trackHeadColumn.left
            anchors.right: trackHeadColumn.right
            anchors.margins: 2
471
            height: showAudioRecord ? root.collapsedHeight : 0
472
473
474
475
476
477
478
479
            Loader {
                id: audioVuMeter
                anchors.fill: parent
                visible: showAudioRecord && (trackHeadRoot.height >= 2 * muteButton.height + resizer.height)
                source: isAudio && showAudioRecord ? "AudioLevels.qml" : ""
                onLoaded: item.trackId = trackId
            }
        }
480
481
482
483
484
485
        Item {
            anchors.bottom: trackHeadColumn.bottom
            anchors.left: trackHeadColumn.left
            anchors.right: trackHeadColumn.right
            anchors.margins: 2
            height: nameEdit.height
486
487
488
489
            Rectangle {
                id: trackLabel
                color: 'transparent'
                radius: 2
490
                anchors.fill: parent
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
491
                border.color: trackNameMouseArea.containsMouse ? activePalette.highlight : 'transparent'
492
                visible: (trackHeadRoot.height >= trackLabel.height + muteButton.height + resizer.height + recLayout.height)
493
494
495
496
                MouseArea {
                    id: trackNameMouseArea
                    anchors.fill: parent
                    hoverEnabled: true
497
                    propagateComposedEvents: true
498
                    cursorShape: Qt.IBeamCursor
499
                    onDoubleClicked: {
500
501
502
503
                        nameEdit.visible = true
                        nameEdit.focus = true
                        nameEdit.selectAll()
                    }
504
                    onClicked: {
505
                        timeline.showTrackAsset(trackId)
506
                        timeline.activeTrack = trackId
507
                        trackHeadRoot.focus = true
508
                    }
509
                }
510
511
512
513
                Label {
                    text: trackName
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
514
515
                    anchors.leftMargin: 4
                    elide: Qt.ElideRight
516
                    font: miniFont
517
518
519
                }
                Label {
                    id: placeHolder
520
                    visible: trackName == '' && (trackNameMouseArea.containsMouse || headerMouseArea.containsMouse)
521
                    enabled: false
Yuri Chornoivan's avatar
Yuri Chornoivan committed
522
                    text: i18n("Edit track name")
523
524
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
525
526
                    anchors.leftMargin: 4
                    elide: Qt.ElideRight
527
                    font: miniFont
528
                }
529
530
531
532
533
                TextField {
                    id: nameEdit
                    visible: false
                    width: parent.width
                    text: trackName
534
                    font: miniFont
535
536
537
538
539
540
                    background: Rectangle {
                        radius: 2
                        color: activePalette.window
                        anchors.fill: parent
                    }
                    /*style: TextFieldStyle {
541
542
543
                        padding.top:0
                        padding.bottom: 0
                        background: Rectangle {
544
545
                            radius: 2
                            color: activePalette.window
546
547
                            anchors.fill: parent
                        }
548
                    }*/
549
                    onEditingFinished: {
550
                        controller.setTrackName(trackId, text)
551
552
                        visible = false
                    }
553
554
555
                }
            }
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
556
557
    }
    Rectangle {
Nicolas Carion's avatar
Nicolas Carion committed
558
            id: resizer
559
            height: 4
Nicolas Carion's avatar
Nicolas Carion committed
560
561
562
563
            color: 'red'
            opacity: 0
            Drag.active: trimInMouseArea.drag.active
            Drag.proposedAction: Qt.MoveAction
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
564
565
            width: trackHeadRoot.width
            y: trackHeadRoot.height - height
566

Nicolas Carion's avatar
Nicolas Carion committed
567
568
569
570
571
572
573
            MouseArea {
                id: trimInMouseArea
                anchors.fill: parent
                hoverEnabled: true
                cursorShape: Qt.SizeVerCursor
                drag.target: parent
                drag.axis: Drag.YAxis
574
                drag.minimumY: root.collapsedHeight - resizer.height
Nicolas Carion's avatar
Nicolas Carion committed
575
576
                property double startY
                property double originalY
577
                drag.smoothed: false
578
                property bool dragStarted: false
579

Nicolas Carion's avatar
Nicolas Carion committed
580
                onPressed: {
581
                    root.autoScrolling = false
582
                    dragStarted = false
Nicolas Carion's avatar
Nicolas Carion committed
583
584
585
586
                    startY = mapToItem(null, x, y).y
                    originalY = trackHeadRoot.height // reusing originalX to accumulate delta for bubble help
                }
                onReleased: {
587
                    root.autoScrolling = timeline.autoScroll
588
589
590
                    if (!trimInMouseArea.containsMouse) {
                        parent.opacity = 0
                    }
591
                    if (mouse.modifiers & Qt.ShiftModifier && dragStarted) {
592
                        timeline.adjustAllTrackHeight(trackHeadRoot.trackId, trackHeadRoot.height)
593
                    }
Nicolas Carion's avatar
Nicolas Carion committed
594
                }
595
                onEntered: parent.opacity = 0.3
Nicolas Carion's avatar
Nicolas Carion committed
596
                onExited: parent.opacity = 0
597
598
599
                onDoubleClicked: {
                    timeline.defaultTrackHeight(mouse.modifiers & Qt.ShiftModifier ? -1 : trackHeadRoot.trackId)
                }
Nicolas Carion's avatar
Nicolas Carion committed
600
601
602
                onPositionChanged: {
                    if (mouse.buttons === Qt.LeftButton) {
                        parent.opacity = 0.5
603
604
605
                        if (!dragStarted && Math.abs(mapToItem(null, x, y).y - startY) > 2) {
                            dragStarted = true
                        }
Nicolas Carion's avatar
Nicolas Carion committed
606
                        var newHeight = originalY + (mapToItem(null, x, y).y - startY)
607
                        newHeight =  Math.max(root.collapsedHeight, newHeight)
608
609
610
611
612
613
                        if (newHeight == root.collapsedHeight) {
                            controller.setTrackProperty(trackId, "kdenlive:collapsed", root.collapsedHeight)
                        } else {
                            controller.setTrackProperty(trackId, "kdenlive:trackheight", newHeight)
                            controller.setTrackProperty(trackId, "kdenlive:collapsed", "0")
                        }
Nicolas Carion's avatar
Nicolas Carion committed
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
    DropArea { //Drop area for tracks
        anchors.fill: trackHeadRoot
        keys: 'kdenlive/effect'
        property string dropData
        property string dropSource
        property int dropRow: -1
        onEntered: {
            dropData = drag.getDataAsString('kdenlive/effect')
            dropSource = drag.getDataAsString('kdenlive/effectsource')
        }
        onDropped: {
            console.log("Add effect: ", dropData)
            if (dropSource == '') {
                // drop from effects list
                controller.addTrackEffect(trackHeadRoot.trackId, dropData);
            } else {
                controller.copyTrackEffect(trackHeadRoot.trackId, dropSource);
            }
            dropSource = ''
            dropRow = -1
            drag.acceptProposedAction
        }
    }
641
642
}