1/*
2    SPDX-FileCopyrightText: 2013-2016 Meltytech LLC
3    SPDX-FileCopyrightText: 2013-2016 Dan Dennedy <dan@dennedy.org>
4
5    SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6*/
7
8import QtQuick 2.11
9import QtQml.Models 2.11
10import com.enums 1.0
11
12Item{
13    id: trackRoot
14    property alias trackModel: trackModel.model
15    property alias rootIndex : trackModel.rootIndex
16    property bool isAudio
17    property real timeScale: 1.0
18    property bool isLocked: false
19    property int trackInternalId : -42
20    property int trackThumbsFormat
21    property int itemType: 0
22    property var effectZones
23    opacity: model.disabled ? 0.4 : 1
24
25    function clipAt(index) {
26        return repeater.itemAt(index)
27    }
28
29    function isClip(type) {
30        return type != ProducerType.Composition && type != ProducerType.Track;
31    }
32
33    width: clipRow.width
34
35    DelegateModel {
36        id: trackModel
37        delegate: Item {
38            property var itemModel : model
39            property bool clipItem: isClip(model.clipType)
40            function calculateZIndex() {
41                // Z order indicates the items that will be drawn on top.
42                if (model.clipType == ProducerType.Composition) {
43                    // Compositions should be top, then clips
44                    return 50000;
45                }
46
47                if (model.mixDuration > 0) {
48                    // Clips with mix should be ordered related to their position so that the right clip of a clip mix is always on top (the mix UI is drawn over the right clip)
49                    return model.start / 25;
50                }
51
52                if (root.activeTool === ProjectTool.SlipTool && model.selected) {
53                    return model.item === timeline.trimmingMainClip ? 2 : 1;
54                }
55
56                if (root.activeTool === ProjectTool.RippleTool && model.item === timeline.trimmingMainClip) {
57                    return 1;
58                }
59                return 0;
60            }
61            z: calculateZIndex()
62            Loader {
63                id: loader
64                Binding {
65                    target: loader.item
66                    property: "timeScale"
67                    value: trackRoot.timeScale
68                    when: loader.status == Loader.Ready && loader.item
69                }
70                Binding {
71                    target: loader.item
72                    property: "fakeTid"
73                    value: model.fakeTrackId
74                    when: loader.status == Loader.Ready && loader.item && clipItem
75                }
76                Binding {
77                    target: loader.item
78                    property: "tagColor"
79                    value: model.tag
80                    when: loader.status == Loader.Ready && loader.item && clipItem
81                }
82                Binding {
83                    target: loader.item
84                    property: "fakePosition"
85                    value: model.fakePosition
86                    when: loader.status == Loader.Ready && loader.item && clipItem
87                }
88                Binding {
89                    target: loader.item
90                    property: "mixDuration"
91                    value: model.mixDuration
92                    when: loader.status == Loader.Ready && loader.item && clipItem
93                }
94                Binding {
95                    target: loader.item
96                    property: "mixCut"
97                    value: model.mixCut
98                    when: loader.status == Loader.Ready && loader.item && clipItem
99                }
100                Binding {
101                    target: loader.item
102                    property: "selected"
103                    value: model.selected
104                    when: loader.status == Loader.Ready && model.clipType != ProducerType.Track
105                }
106                Binding {
107                    target: loader.item
108                    property: "mltService"
109                    value: model.mlt_service
110                    when: loader.status == Loader.Ready && loader.item
111                }
112                Binding {
113                    target: loader.item
114                    property: "modelStart"
115                    value: model.start
116                    when: loader.status == Loader.Ready && loader.item
117                }
118                Binding {
119                    target: loader.item
120                    property: "scrollX"
121                    value: scrollView.contentX
122                    when: loader.status == Loader.Ready && loader.item
123                }
124                Binding {
125                    target: loader.item
126                    property: "fadeIn"
127                    value: model.fadeIn
128                    when: loader.status == Loader.Ready && clipItem
129                }
130                Binding {
131                    target: loader.item
132                    property: "positionOffset"
133                    value: model.positionOffset
134                    when: loader.status == Loader.Ready && clipItem
135                }
136                Binding {
137                    target: loader.item
138                    property: "effectNames"
139                    value: model.effectNames
140                    when: loader.status == Loader.Ready && clipItem
141                }
142                Binding {
143                    target: loader.item
144                    property: "clipStatus"
145                    value: model.clipStatus
146                    when: loader.status == Loader.Ready && clipItem
147                }
148                Binding {
149                    target: loader.item
150                    property: "fadeOut"
151                    value: model.fadeOut
152                    when: loader.status == Loader.Ready && clipItem
153                }
154                Binding {
155                    target: loader.item
156                    property: "showKeyframes"
157                    value: model.showKeyframes
158                    when: loader.status == Loader.Ready && loader.item
159                }
160                Binding {
161                    target: loader.item
162                    property: "isGrabbed"
163                    value: model.isGrabbed
164                    when: loader.status == Loader.Ready && loader.item
165                }
166                Binding {
167                    target: loader.item
168                    property: "keyframeModel"
169                    value: model.keyframeModel
170                    when: loader.status == Loader.Ready && loader.item
171                }
172                Binding {
173                    target: loader.item
174                    property: "aTrack"
175                    value: model.a_track
176                    when: loader.status == Loader.Ready && model.clipType == ProducerType.Composition
177                }
178                Binding {
179                    target: loader.item
180                    property: "trackHeight"
181                    value: root.trackHeight
182                    when: loader.status == Loader.Ready && model.clipType == ProducerType.Composition
183                }
184                Binding {
185                    target: loader.item
186                    property: "clipDuration"
187                    value: model.duration
188                    when: loader.status == Loader.Ready && loader.item
189                }
190                Binding {
191                    target: loader.item
192                    property: "inPoint"
193                    value: model.in
194                    when: loader.status == Loader.Ready && loader.item
195                }
196                Binding {
197                    target: loader.item
198                    property: "outPoint"
199                    value: model.out
200                    when: loader.status == Loader.Ready && loader.item
201                }
202                Binding {
203                    target: loader.item
204                    property: "grouped"
205                    value: model.grouped
206                    when: loader.status == Loader.Ready && loader.item
207                }
208                Binding {
209                    target: loader.item
210                    property: "clipName"
211                    value: model.name
212                    when: loader.status == Loader.Ready && loader.item
213                }
214                Binding {
215                    target: loader.item
216                    property: "clipResource"
217                    value: model.resource
218                    when: loader.status == Loader.Ready && clipItem
219                }
220                Binding {
221                    target: loader.item
222                    property: "clipState"
223                    value: model.clipState
224                    when: loader.status == Loader.Ready && clipItem
225                }
226                Binding {
227                    target: loader.item
228                    property: "maxDuration"
229                    value: model.maxDuration
230                    when: loader.status == Loader.Ready && clipItem
231                }
232                Binding {
233                    target: loader.item
234                    property: "forceReloadThumb"
235                    value: model.reloadThumb
236                    when: loader.status == Loader.Ready && clipItem
237                }
238                Binding {
239                    target: loader.item
240                    property: "binId"
241                    value: model.binId
242                    when: loader.status == Loader.Ready && clipItem
243                }
244                Binding {
245                    target: loader.item
246                    property: "timeremap"
247                    value: model.timeremap
248                    when: loader.status == Loader.Ready && clipItem
249                }
250                sourceComponent: {
251                    if (clipItem) {
252                        return clipDelegate
253                    } else if (model.clipType == ProducerType.Composition) {
254                        return compositionDelegate
255                    } else {
256                        // Track
257                        return undefined
258                    }
259                }
260                onLoaded: {
261                    item.clipId= model.item
262                    item.parentTrack = trackRoot
263                    if (clipItem) {
264                        console.log('loaded clip: ', model.start, ', ID: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex,', TYPE:', model.clipType)
265                        item.isAudio= model.audio
266                        item.markers= model.markers
267                        item.hasAudio = model.hasAudio
268                        item.canBeAudio = model.canBeAudio
269                        item.canBeVideo = model.canBeVideo
270                        item.itemType = model.clipType
271                        item.audioChannels = model.audioChannels
272                        item.audioStream = model.audioStream
273                        item.multiStream = model.multiStream
274                        item.aStreamIndex = model.audioStreamIndex
275                        console.log('loaded clip with Astream: ', model.audioStream)
276                        // Speed change triggers a new clip insert so no binding necessary
277                        item.speed = model.speed
278                    } else if (model.clipType == ProducerType.Composition) {
279                        console.log('loaded composition: ', model.start, ', ID: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex)
280                        //item.aTrack = model.a_track
281                    } else {
282                        console.log('loaded unwanted element: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex)
283                    }
284                    item.trackId = model.trackId
285                    //item.selected= trackRoot.selection.indexOf(item.clipId) != -1
286                    //console.log(width, height);
287                }
288            }
289        }
290    }
291
292    Item {
293        id: clipRow
294        height: trackRoot.height
295        Repeater { id: repeater; model: trackModel }
296    }
297
298    Component {
299        id: clipDelegate
300        Clip {
301            height: trackRoot.height
302            onInitGroupTrim: {
303                // We are resizing a group, remember coordinates of all elements
304                root.groupTrimData = controller.getGroupData(clip.clipId)
305            }
306            onTrimmingIn: {
307                if (root.activeTool === ProjectTool.SelectTool && controlTrim) {
308                    newDuration = controller.requestItemSpeedChange(clip.clipId, newDuration, false, root.snapping)
309                    if (!speedController.visible) {
310                        // Store original speed
311                        speedController.originalSpeed = clip.speed
312                    }
313                    clip.x += clip.width - (newDuration * trackRoot.timeScale)
314                    clip.width = newDuration * root.timeScale
315                    speedController.x = clip.x + clip.border.width
316                    speedController.width = Math.max(0, clip.width - 2 * clip.border.width)
317                    speedController.lastValidDuration = newDuration
318                    clip.speed = clip.originalDuration * speedController.originalSpeed / newDuration
319                    speedController.visible = true
320                    var s = timeline.simplifiedTC(Math.abs(delta))
321                    s = '%1:%2, %3:%4'.arg(i18n("Speed"))
322                        .arg(clip.speed)
323                        .arg(i18n("Duration"))
324                        .arg(timeline.simplifiedTC(newDuration))
325                    timeline.showToolTip(s)
326                    return
327                }
328                var new_duration = 0;
329                if (root.activeTool === ProjectTool.RippleTool) {
330                    console.log("In: Request for " + newDuration)
331                    new_duration = timeline.requestItemRippleResize(clip.clipId, newDuration, false, false, root.snapping, shiftTrim)
332                    timeline.requestStartTrimmingMode(clip.clipId, false, false);
333                    timeline.ripplePosChanged(new_duration, false);
334                } else {
335                    new_duration = controller.requestItemResize(clip.clipId, newDuration, false, false, root.snapping, shiftTrim)
336                }
337
338                if (new_duration > 0) {
339                    clip.lastValidDuration = new_duration
340                    clip.originalX = clip.draggedX
341                    // Show amount trimmed as a time in a "bubble" help.
342                    var delta = new_duration - clip.originalDuration
343                    var s = timeline.simplifiedTC(Math.abs(delta))
344                    s = '%1%2, %3:%4'.arg((delta <= 0)? '+' : '-')
345                        .arg(s)
346                        .arg(i18n("In"))
347                        .arg(timeline.simplifiedTC(clip.inPoint))
348                    timeline.showToolTip(s)
349                    //bubbleHelp.show(clip.x - 20, trackRoot.y + trackRoot.height, s)
350                }
351            }
352            onTrimmedIn: {
353                //bubbleHelp.hide()
354                timeline.showToolTip();
355                if (shiftTrim || (root.groupTrimData == undefined/*TODO > */ || root.activeTool === ProjectTool.RippleTool /* < TODO*/) || controlTrim) {
356                    // We only resize one element
357                    if (root.activeTool === ProjectTool.RippleTool) {
358                        timeline.requestItemRippleResize(clip.clipId, clip.originalDuration, false, false, 0, shiftTrim)
359                    } else {
360                        controller.requestItemResize(clip.clipId, clip.originalDuration, false, false, 0, shiftTrim)
361                    }
362
363                    if (root.activeTool === ProjectTool.SelectTool && controlTrim) {
364                        // Update speed
365                        speedController.visible = false
366                        controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, false, root.snapping, shiftTrim, clip.originalDuration * speedController.originalSpeed / speedController.lastValidDuration)
367                        speedController.originalSpeed = 1
368                    } else {
369                        if (root.activeTool === ProjectTool.RippleTool) {
370                            timeline.requestItemRippleResize(clip.clipId, clip.lastValidDuration, false, true, 0, shiftTrim)
371                            timeline.requestEndTrimmingMode();
372                        } else {
373                            controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, 0, shiftTrim)
374                        }
375                    }
376                } else {
377                    var updatedGroupData = controller.getGroupData(clip.clipId)
378                    controller.processGroupResize(root.groupTrimData, updatedGroupData, false)
379                }
380                root.groupTrimData = undefined
381            }
382            onTrimmingOut: {
383                if (root.activeTool === ProjectTool.SelectTool && controlTrim) {
384                    if (!speedController.visible) {
385                        // Store original speed
386                        speedController.originalSpeed = clip.speed
387                    }
388                    speedController.x = clip.x + clip.border.width
389                    newDuration = controller.requestItemSpeedChange(clip.clipId, newDuration, true, root.snapping)
390                    clip.width = newDuration * trackRoot.timeScale
391                    speedController.width = Math.max(0, clip.width - 2 * clip.border.width)
392                    speedController.lastValidDuration = newDuration
393                    clip.speed = clip.originalDuration * speedController.originalSpeed / newDuration
394                    speedController.visible = true
395                    var s = '%1:%2\%, %3:%4'.arg(i18n("Speed"))
396                        .arg(Math.round(clip.speed*100))
397                        .arg(i18n("Duration"))
398                        .arg(timeline.simplifiedTC(newDuration))
399                    timeline.showToolTip(s)
400                    return
401                }
402                var new_duration = 0;
403                if (root.activeTool === ProjectTool.RippleTool) {
404                    console.log("Out: Request for " + newDuration)
405                    new_duration = timeline.requestItemRippleResize(clip.clipId, newDuration, true, false, root.snapping, shiftTrim)
406                    timeline.requestStartTrimmingMode(clip.clipId, false, true);
407                    timeline.ripplePosChanged(new_duration, true);
408                } else {
409                    new_duration = controller.requestItemResize(clip.clipId, newDuration, true, false, root.snapping, shiftTrim)
410                }
411                if (new_duration > 0) {
412                    clip.lastValidDuration = new_duration
413                    // Show amount trimmed as a time in a "bubble" help.
414                    var delta = clip.originalDuration - new_duration
415                    var s = timeline.simplifiedTC(Math.abs(delta))
416                    s = '%1%2, %3:%4'.arg((delta <= 0)? '+' : '-')
417                        .arg(s)
418                        .arg(i18n("Duration"))
419                        .arg(timeline.simplifiedTC(new_duration))
420                    timeline.showToolTip(s);
421                    //bubbleHelp.show(clip.x + clip.width - 20, trackRoot.y + trackRoot.height, s)
422                }
423            }
424            onTrimmedOut: {
425                timeline.showToolTip();
426                //bubbleHelp.hide()
427                if (shiftTrim || (root.groupTrimData == undefined/*TODO > */ || root.activeTool === ProjectTool.RippleTool /* < TODO*/) || controlTrim) {
428                    if (root.activeTool === ProjectTool.RippleTool) {
429                        timeline.requestItemRippleResize(clip.clipId, clip.originalDuration, true, false, 0, shiftTrim)
430                    } else {
431                        controller.requestItemResize(clip.clipId, clip.originalDuration, true, false, 0, shiftTrim)
432                    }
433
434                    if (root.activeTool === ProjectTool.SelectTool && controlTrim) {
435                        speedController.visible = false
436                        // Update speed
437                        controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, true, root.snapping, shiftTrim, clip.originalDuration * speedController.originalSpeed / speedController.lastValidDuration)
438                        speedController.originalSpeed = 1
439                    } else {
440                        if (root.activeTool === ProjectTool.RippleTool) {
441                            timeline.requestItemRippleResize(clip.clipId, clip.lastValidDuration, true, true, 0, shiftTrim)
442                            timeline.requestEndTrimmingMode();
443                        } else {
444                            controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, 0, shiftTrim)
445                        }
446                    }
447                } else {
448                    var updatedGroupData = controller.getGroupData(clip.clipId)
449                    controller.processGroupResize(root.groupTrimData, updatedGroupData, true)
450                }
451                root.groupTrimData = undefined
452            }
453        }
454    }
455    Component {
456        id: compositionDelegate
457        Composition {
458            displayHeight: Math.max(trackRoot.height / 2, trackRoot.height - (root.baseUnit * 2))
459            opacity: 0.8
460            selected: root.timelineSelection.indexOf(clipId) != -1
461            onTrimmingIn: {
462                var new_duration = controller.requestItemResize(clip.clipId, newDuration, false, false, root.snapping)
463                if (new_duration > 0) {
464                    clip.lastValidDuration = newDuration
465                    clip.originalX = clip.draggedX
466                    // Show amount trimmed as a time in a "bubble" help.
467                    var delta = clip.originalDuration - new_duration
468                    var s = timeline.simplifiedTC(Math.abs(delta))
469                    s = i18n("%1%2, Duration = %3", ((delta <= 0)? '+' : '-')
470                        , s, timeline.simplifiedTC(new_duration))
471                    timeline.showToolTip(s)
472                }
473            }
474            onTrimmedIn: {
475                timeline.showToolTip()
476                //bubbleHelp.hide()
477                controller.requestItemResize(clip.clipId, clip.originalDuration, false, false, root.snapping)
478                controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, root.snapping)
479            }
480            onTrimmingOut: {
481                var new_duration = controller.requestItemResize(clip.clipId, newDuration, true, false, root.snapping)
482                if (new_duration > 0) {
483                    clip.lastValidDuration = newDuration
484                    // Show amount trimmed as a time in a "bubble" help.
485                    var delta = clip.originalDuration - new_duration
486                    var s = timeline.simplifiedTC(Math.abs(delta))
487                    s = i18n("%1%2, Duration = %3", ((delta <= 0)? '+' : '-')
488                        , s, timeline.simplifiedTC(new_duration))
489                    timeline.showToolTip(s)
490                }
491            }
492            onTrimmedOut: {
493                timeline.showToolTip()
494                //bubbleHelp.hide()
495                controller.requestItemResize(clip.clipId, clip.originalDuration, true, false, root.snapping)
496                controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, root.snapping)
497            }
498        }
499    }
500    Rectangle {
501        id: speedController
502        anchors.bottom: parent.bottom
503        color: activePalette.highlight //'#cccc0000'
504        visible: false
505        clip: true
506        height: root.baseUnit * 1.5
507        property int lastValidDuration: 0
508        property real originalSpeed: 1
509        Text {
510            id: speedLabel
511            text: i18n("Adjusting speed")
512            font: miniFont
513            anchors.fill: parent
514            verticalAlignment: Text.AlignVCenter
515            horizontalAlignment: Text.AlignHCenter
516            color: activePalette.highlightedText
517        }
518        transitions: [ Transition {
519            NumberAnimation { property: "opacity"; duration: 300}
520        } ]
521    }
522    Repeater {
523        model: effectZones
524        Rectangle {
525            x: effectZones[index].x * timeline.scaleFactor
526            height: 2
527            width: (effectZones[index].y - effectZones[index].x) * timeline.scaleFactor
528            color: 'blueviolet'
529            opacity: 1
530            anchors.top: parent.top
531        }
532    }
533}
534