diff --git a/src/timeline2/view/qml/Clip.qml b/src/timeline2/view/qml/Clip.qml index 32c7375e3..023384225 100644 --- a/src/timeline2/view/qml/Clip.qml +++ b/src/timeline2/view/qml/Clip.qml @@ -1,980 +1,980 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * * 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 . */ import QtQuick 2.6 import QtQuick.Controls 2.2 import Kdenlive.Controls 1.0 import QtQml.Models 2.2 import QtQuick.Window 2.2 import 'Timeline.js' as Logic import com.enums 1.0 Rectangle { id: clipRoot property real timeScale: 1.0 property string clipName: '' property string clipResource: '' property string mltService: '' property string effectNames property int modelStart property real scrollX: 0 property int inPoint: 0 property int outPoint: 0 property int clipDuration: 0 property int maxDuration: 0 property bool isAudio: false property int audioChannels property bool showKeyframes: false property bool isGrabbed: false property bool grouped: false property var markers property var keyframeModel property int clipStatus: 0 property int itemType: 0 property int fadeIn: 0 property int fadeOut: 0 property int binId: 0 property int positionOffset: 0 property var parentTrack property int trackIndex //Index in track repeater property int clipId //Id of the clip in the model property int trackId: -1 // Id of the parent track in the model property int fakeTid: -1 property int fakePosition: 0 property int originalTrackId: -1 property int originalX: x property int originalDuration: clipDuration property int lastValidDuration: clipDuration property int draggedX: x property bool selected: false property bool isLocked: parentTrack && parentTrack.isLocked == true property bool hasAudio property bool canBeAudio property bool canBeVideo property string hash: 'ccc' //TODO property double speed: 1.0 property color borderColor: 'black' property bool forceReloadThumb property bool isComposition: false property var groupTrimData width : clipDuration * timeScale; opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0 signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim) signal trimmedIn(var clip, bool shiftTrim, bool controlTrim) signal initGroupTrim(var clip) signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim) signal trimmedOut(var clip, bool shiftTrim, bool controlTrim) onIsGrabbedChanged: { if (clipRoot.isGrabbed) { grabItem() } else { mouseArea.focus = false } } function grabItem() { clipRoot.forceActiveFocus() mouseArea.focus = true } function clearAndMove(offset) { controller.requestClearSelection() controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true) controller.requestAddToSelection(clipRoot.clipId) } onClipResourceChanged: { if (itemType == ProducerType.Color) { color: Qt.darker(getColor(), 1.5) } } ToolTip { visible: mouseArea.containsMouse && !dragProxyArea.pressed font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: label.text + ' (' + timeline.simplifiedTC(clipRoot.inPoint) + '-' + timeline.simplifiedTC(clipRoot.outPoint) + ')' } } onKeyframeModelChanged: { console.log('keyframe model changed............') if (effectRow.keyframecanvas) { effectRow.keyframecanvas.requestPaint() } } onClipDurationChanged: { width = clipDuration * timeScale; if (parentTrack && parentTrack.isAudio && thumbsLoader.item) { // Duration changed, we may need a different number of repeaters thumbsLoader.item.reload() } } onModelStartChanged: { x = modelStart * timeScale; } onFakePositionChanged: { x = fakePosition * timeScale; } onFakeTidChanged: { if (clipRoot.fakeTid > -1 && parentTrack) { 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 } clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y } } onForceReloadThumbChanged: { // TODO: find a way to force reload of clip thumbs if (thumbsLoader.item) { thumbsLoader.item.reload() } } onTimeScaleChanged: { x = modelStart * timeScale; width = clipDuration * timeScale; labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 if (parentTrack && parentTrack.isAudio && thumbsLoader.item) { thumbsLoader.item.reload(); } } onScrollXChanged: { labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 } border.color: selected ? root.selectionColor : grouped ? root.groupColor : borderColor border.width: isGrabbed ? 8 : 1.5 function updateDrag() { var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height) initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false) } function getColor() { if (clipStatus == ClipState.Disabled) { return 'grey' } if (itemType == ProducerType.Color) { var color = clipResource.substring(clipResource.length - 9) if (color[0] == '#') { return color } return '#' + color.substring(color.length - 8, color.length - 2) } return isAudio? root.audioColor : root.videoColor } /* function reparent(track) { console.log('TrackId: ',trackId) parent = track height = track.height parentTrack = track trackId = parentTrack.trackId console.log('Reparenting clip to Track: ', trackId) //generateWaveform() } */ property bool variableThumbs: (isAudio || itemType == ProducerType.Color || mltService === '') property bool isImage: itemType == ProducerType.Image property string baseThumbPath: variableThumbs ? '' : 'image://thumbnail/' + binId + '/' + (isImage ? '#0' : '#') property string inThumbPath: (variableThumbs || isImage ) ? baseThumbPath : baseThumbPath + Math.floor(inPoint * speed) property string outThumbPath: (variableThumbs || isImage ) ? baseThumbPath : baseThumbPath + Math.floor(outPoint * speed) DropArea { //Drop area for clips anchors.fill: clipRoot 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.addClipEffect(clipRoot.clipId, dropData); } else { controller.copyClipEffect(clipRoot.clipId, dropSource); } dropSource = '' dropRow = -1 drag.acceptProposedAction } } MouseArea { id: mouseArea enabled: root.activeTool === 0 anchors.fill: clipRoot acceptedButtons: Qt.RightButton hoverEnabled: root.activeTool === 0 cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape onPressed: { - root.stopScrolling = true + root.autoScrolling = false if (mouse.button == Qt.RightButton) { if (timeline.selection.indexOf(clipRoot.clipId) == -1) { controller.requestAddToSelection(clipRoot.clipId, true) } clipMenu.clipId = clipRoot.clipId clipMenu.clipStatus = clipRoot.clipStatus clipMenu.clipFrame = Math.round(mouse.x / timeline.scaleFactor) clipMenu.grouped = clipRoot.grouped clipMenu.trackId = clipRoot.trackId clipMenu.canBeAudio = clipRoot.canBeAudio clipMenu.canBeVideo = clipRoot.canBeVideo clipMenu.popup() } } 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) Keys.onLeftPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - offset, true, true, true); } Keys.onRightPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart + offset, true, true, true); } 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); } Keys.onEscapePressed: { timeline.grabCurrent() //focus = false } onPositionChanged: { var mapped = parentTrack.mapFromItem(clipRoot, mouse.x, mouse.y).x root.mousePosChanged(Math.round(mapped / timeline.scaleFactor)) } onEntered: { var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height) initDrag(clipRoot, itemPos, clipRoot.clipId, clipRoot.modelStart, clipRoot.trackId, false) } onExited: { endDrag() } onWheel: zoomByWheel(wheel) Item { // Thumbs container anchors.fill: parent 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() } } } Item { // Clipping container id: container anchors.fill: parent anchors.margins: 1.5 clip: true 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) + '%]': '') font.pixelSize: root.baseUnit * 1.2 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 font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: positionOffset < 0 ? i18n("Offset: -%1", timeline.simplifiedTC(-positionOffset)) : i18n("Offset: %1", timeline.simplifiedTC(positionOffset)) } } Text { id: offsetLabel text: positionOffset font.pixelSize: root.baseUnit * 1.2 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 font.pixelSize: root.baseUnit * 1.2 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 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; 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 onDoubleClicked: timeline.editMarker(clipRoot.clipId, model.frame) onClicked: timeline.position = (clipRoot.x + markerBase.x) / timeline.scaleFactor } } Text { id: mlabel visible: timeline.showMarkers && parent.width > width * 1.5 text: model.comment font.pixelSize: root.baseUnit x: markerBase.x anchors { bottom: parent.verticalCenter topMargin: 2 leftMargin: 2 } color: 'white' } } } KeyframeView { id: effectRow visible: clipRoot.showKeyframes && clipRoot.keyframeModel selected: clipRoot.selected inPoint: clipRoot.inPoint outPoint: clipRoot.outPoint masterObject: clipRoot kfrModel: clipRoot.keyframeModel } } 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 } } ] Rectangle { 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 enabled: !clipRoot.isAudio && dragProxy.draggedItem === clipRoot.clipId visible: clipRoot.width > 4 * width MouseArea { id: compInArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: parent.opacity = 0.7 onExited: { if (!pressed) { parent.opacity = 0 } } onPressed: { timeline.addCompositionToClip('', clipRoot.clipId, 0) } onReleased: { parent.opacity = 0 } ToolTip { visible: compInArea.containsMouse && !dragProxyArea.pressed font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: i18n("Click to add composition") } } } } Rectangle { 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 anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: { parent.opacity = 0.7 } onExited: { if (!pressed) { parent.opacity = 0 } } onPressed: { timeline.addCompositionToClip('', clipRoot.clipId, clipRoot.clipDuration - 1) } onReleased: { parent.opacity = 0 } ToolTip { visible: compOutArea.containsMouse && !dragProxyArea.pressed font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: i18n("Click to add composition") } } } } 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 } Rectangle { id: fadeInControl anchors.left: fadeInTriangle.width > radius? undefined : fadeInTriangle.left anchors.horizontalCenter: fadeInTriangle.width > radius? fadeInTriangle.right : undefined 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 } } onPressed: { - root.stopScrolling = true + root.autoScrolling = false startX = Math.round(parent.x / timeScale) startFadeIn = clipRoot.fadeIn parent.anchors.left = undefined parent.anchors.horizontalCenter = undefined parent.opacity = 1 fadeInTriangle.opacity = 0.5 // parentTrack.clipSelected(clipRoot, parentTrack) TODO } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll fadeInTriangle.opacity = 0.3 parent.opacity = 0 if (fadeInTriangle.width > parent.radius) parent.anchors.horizontalCenter = fadeInTriangle.right else parent.anchors.left = fadeInTriangle.left 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) duration = Math.min(duration, clipRoot.clipDuration - 1) if (duration != clipRoot.fadeIn) { timeline.adjustFade(clipRoot.clipId, 'fadein', duration, -1) // Show fade duration as time in a "bubble" help. var s = timeline.simplifiedTC(Math.max(duration, 0)) bubbleHelp.show(clipRoot.x, parentTrack.y + clipRoot.height, s) } } } } SequentialAnimation on scale { loops: Animation.Infinite running: fadeInMouseArea.containsMouse && !fadeInMouseArea.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 } } } 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 anchors.right: fadeOutCanvas.width > radius? undefined : fadeOutCanvas.right anchors.horizontalCenter: fadeOutCanvas.width > radius? fadeOutCanvas.left : undefined 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 } } drag.smoothed: false onPressed: { - root.stopScrolling = true + root.autoScrolling = false startX = Math.round(parent.x / timeScale) startFadeOut = clipRoot.fadeOut parent.anchors.right = undefined parent.anchors.horizontalCenter = undefined parent.opacity = 1 fadeOutCanvas.opacity = 0.5 } onReleased: { fadeOutCanvas.opacity = 0.3 parent.opacity = 0 - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll if (fadeOutCanvas.width > parent.radius) parent.anchors.horizontalCenter = fadeOutCanvas.left else parent.anchors.right = fadeOutCanvas.right var duration = clipRoot.fadeOut if (duration > 0) { duration += 1 } timeline.adjustFade(clipRoot.clipId, 'fadeout', duration, startFadeOut) 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. var s = timeline.simplifiedTC(Math.max(duration, 0)) bubbleHelp.show(clipRoot.x + clipRoot.width, parentTrack.y + clipRoot.height, s) } } } } 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 } } } } Rectangle { id: trimIn anchors.left: clipRoot.left anchors.leftMargin: 0 height: parent.height enabled: !isLocked width: root.baseUnit / 3 color: isAudio? 'green' : 'lawngreen' opacity: 0 Drag.active: trimInMouseArea.drag.active Drag.proposedAction: Qt.MoveAction visible: trimInMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width) ToolTip { visible: trimInMouseArea.containsMouse && !trimInMouseArea.pressed font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: i18n("In:%1\nPosition:%2", timeline.simplifiedTC(clipRoot.inPoint),timeline.simplifiedTC(clipRoot.modelStart)) } } MouseArea { id: trimInMouseArea anchors.fill: parent hoverEnabled: true drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false property bool shiftTrim: false property bool controlTrim: false property bool sizeChanged: false cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor); onPressed: { - root.stopScrolling = true + root.autoScrolling = false clipRoot.originalX = clipRoot.x clipRoot.originalDuration = clipDuration parent.anchors.left = undefined shiftTrim = mouse.modifiers & Qt.ShiftModifier controlTrim = mouse.modifiers & Qt.ControlModifier if (!shiftTrim && clipRoot.grouped) { clipRoot.initGroupTrim(clipRoot) } parent.opacity = 0 } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll parent.anchors.left = clipRoot.left if (sizeChanged) { clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim) sizeChanged = false } } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var delta = Math.round((trimIn.x) / timeScale) if (delta !== 0) { if (delta < -modelStart) { delta = -modelStart } var newDuration = clipDuration - delta sizeChanged = true clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim) } } } onEntered: { if (!pressed) { parent.opacity = 0.5 } } onExited: { parent.opacity = 0 } } } Rectangle { id: trimOut anchors.right: clipRoot.right anchors.rightMargin: 0 height: parent.height width: root.baseUnit / 3 color: 'red' opacity: 0 enabled: !isLocked Drag.active: trimOutMouseArea.drag.active Drag.proposedAction: Qt.MoveAction visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !mouseArea.drag.active && clipRoot.width > 4 * width) ToolTip { visible: trimOutMouseArea.containsMouse && !trimOutMouseArea.pressed font.pixelSize: root.baseUnit delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: i18n("Out: ") + timeline.simplifiedTC(clipRoot.outPoint) } } MouseArea { id: trimOutMouseArea anchors.fill: parent hoverEnabled: true property bool shiftTrim: false property bool controlTrim: false property bool sizeChanged: false cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor); drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false onPressed: { - root.stopScrolling = true + root.autoScrolling = false clipRoot.originalDuration = clipDuration parent.anchors.right = undefined shiftTrim = mouse.modifiers & Qt.ShiftModifier controlTrim = mouse.modifiers & Qt.ControlModifier if (!shiftTrim && clipRoot.grouped) { clipRoot.initGroupTrim(clipRoot) } parent.opacity = 0 } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll parent.anchors.right = clipRoot.right if (sizeChanged) { clipRoot.trimmedOut(clipRoot, shiftTrim, controlTrim) sizeChanged = false } } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var newDuration = Math.round((parent.x + parent.width) / timeScale) if (newDuration != clipDuration) { sizeChanged = true clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim, controlTrim) } } } onEntered: { if (!pressed) { parent.opacity = 0.5 } } onExited: parent.opacity = 0 } } /*MenuItem { id: mergeItem text: i18n("Merge with next clip") onTriggered: timeline.mergeClipWithNext(trackIndex, index, false) } MenuItem { text: i18n("Rebuild Audio Waveform") onTriggered: timeline.remakeAudioLevels(trackIndex, index) }*/ /*onPopupVisibleChanged: { 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)) } }*/ } diff --git a/src/timeline2/view/qml/Composition.qml b/src/timeline2/view/qml/Composition.qml index 15cc4457e..dff8726d5 100644 --- a/src/timeline2/view/qml/Composition.qml +++ b/src/timeline2/view/qml/Composition.qml @@ -1,358 +1,358 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.org. * * Based on work by Dan Dennedy (Shotcut) * * * * 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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 . * ***************************************************************************/ import QtQuick 2.6 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Window 2.2 import 'Timeline.js' as Logic Item { id: compositionRoot property real timeScale: 1.0 property string clipName: '' property string clipResource: '' property string mltService: '' property int modelStart property int displayHeight: 0 property var parentTrack: trackRoot property int inPoint: 0 property int outPoint: 0 property int clipDuration: 0 property bool isAudio: false property bool showKeyframes: false property var itemType: 0 property bool isGrabbed: false property var keyframeModel property bool grouped: false property int binId: 0 property int scrollX: 0 property int trackHeight property int trackIndex //Index in track repeater property int trackId: -42 //Id in the model property int aTrack: -1 property int clipId //Id of the clip in the model property int originalTrackId: trackId property bool isComposition: true property int originalX: x property int originalDuration: clipDuration property int lastValidDuration: clipDuration property int draggedX: x property bool selected: false property double speed: 1.0 property color color: displayRect.color property color borderColor: 'black' signal moved(var clip) signal dragged(var clip, var mouse) signal dropped(var clip) signal draggedToTrack(var clip, int pos, int xpos) signal trimmingIn(var clip, real newDuration, var mouse) signal trimmedIn(var clip) signal trimmingOut(var clip, real newDuration, var mouse) signal trimmedOut(var clip) onKeyframeModelChanged: { if (effectRow.keyframecanvas) { effectRow.keyframecanvas.requestPaint() } } onModelStartChanged: { x = modelStart * timeScale; } onIsGrabbedChanged: { if (compositionRoot.isGrabbed) { grabItem() } } function grabItem() { compositionRoot.forceActiveFocus() mouseArea.focus = true } onTrackIdChanged: { compositionRoot.parentTrack = Logic.getTrackById(trackId) compositionRoot.y = compositionRoot.originalTrackId == -1 || trackId == originalTrackId ? 0 : parentTrack.y - Logic.getTrackById(compositionRoot.originalTrackId).y; } onClipDurationChanged: { width = clipDuration * timeScale; } onTimeScaleChanged: { x = modelStart * timeScale; width = clipDuration * timeScale; labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 } onScrollXChanged: { labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 } /* function reparent(track) { parent = track isAudio = track.isAudio parentTrack = track displayHeight = track.height / 2 compositionRoot.trackId = parentTrack.trackId } */ function updateDrag() { console.log('XXXXXXXXXXXXXXX\n\nXXXXXXXXXXXXX \nUPDATING COMPO DRAG') var itemPos = mapToItem(tracksContainerArea, 0, displayRect.y, displayRect.width, displayRect.height) initDrag(compositionRoot, itemPos, compositionRoot.clipId, compositionRoot.modelStart, compositionRoot.trackId, true) } MouseArea { id: mouseArea anchors.fill: displayRect acceptedButtons: Qt.RightButton hoverEnabled: true Keys.onShortcutOverride: event.accepted = compositionRoot.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) Keys.onLeftPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestCompositionMove(compositionRoot.clipId, compositionRoot.originalTrackId, compositionRoot.modelStart - offset, true, true) } Keys.onRightPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestCompositionMove(compositionRoot.clipId, compositionRoot.originalTrackId, compositionRoot.modelStart + offset, true, true) } Keys.onUpPressed: { controller.requestCompositionMove(compositionRoot.clipId, controller.getNextTrackId(compositionRoot.originalTrackId), compositionRoot.modelStart, true, true) } Keys.onDownPressed: { controller.requestCompositionMove(compositionRoot.clipId, controller.getPreviousTrackId(compositionRoot.originalTrackId), compositionRoot.modelStart, true, true) } Keys.onEscapePressed: { timeline.grabCurrent() } cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape onPressed: { - root.stopScrolling = true + root.autoScrolling = false compositionRoot.forceActiveFocus(); if (mouse.button == Qt.RightButton) { if (timeline.selection.indexOf(compositionRoot.clipId) == -1) { timeline.addSelection(compositionRoot.clipId, true) } compositionMenu.clipId = compositionRoot.clipId compositionMenu.grouped = compositionRoot.grouped compositionMenu.trackId = compositionRoot.trackId compositionMenu.popup() } } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll } onEntered: { var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height) initDrag(compositionRoot, itemPos, compositionRoot.clipId, compositionRoot.modelStart, compositionRoot.trackId, true) } onExited: { endDrag() } onDoubleClicked: { if (mouse.modifiers & Qt.ShiftModifier) { if (keyframeModel && showKeyframes) { // Add new keyframe var xPos = Math.round(mouse.x / timeline.scaleFactor) var yPos = (compositionRoot.height - mouse.y) / compositionRoot.height keyframeModel.addKeyframe(xPos + compositionRoot.inPoint, yPos) } else { timeline.position = compositionRoot.x / timeline.scaleFactor } } else { timeline.editItemDuration(clipId) } } onPositionChanged: { if (parentTrack) { var mapped = parentTrack.mapFromItem(compositionRoot, mouse.x, mouse.y).x root.mousePosChanged(Math.round(mapped / timeline.scaleFactor)) } } onWheel: zoomByWheel(wheel) } Rectangle { id: displayRect anchors.top: compositionRoot.top anchors.right: compositionRoot.right anchors.left: compositionRoot.left anchors.topMargin: displayHeight * 1.2 height: displayHeight * 1.3 color: selected ? 'mediumpurple' : Qt.darker('mediumpurple') border.color: selected ? root.selectionColor : grouped ? root.groupColor : borderColor border.width: isGrabbed ? 8 : 1.5 opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.5 : 1.0 Item { // clipping container id: container anchors.fill: displayRect anchors.margins:1.5 clip: true Rectangle { // text background id: labelRect color: compositionRoot.aTrack > -1 ? 'yellow' : 'lightgray' anchors.top: container.top width: label.width + 2 height: label.height Text { id: label text: clipName + (compositionRoot.aTrack > -1 ? ' > ' + timeline.getTrackNameFromMltIndex(compositionRoot.aTrack) : '') font.pixelSize: root.baseUnit anchors { top: labelRect.top left: labelRect.left topMargin: 1 leftMargin: 1 } color: 'black' } } KeyframeView { id: effectRow visible: compositionRoot.showKeyframes && compositionRoot.keyframeModel selected: compositionRoot.selected inPoint: 0 outPoint: compositionRoot.clipDuration masterObject: compositionRoot kfrModel: compositionRoot.keyframeModel } } /*Drag.active: mouseArea.drag.active Drag.proposedAction: Qt.MoveAction*/ states: [ State { name: 'normal' when: !compositionRoot.selected PropertyChanges { target: compositionRoot z: 0 } }, State { name: 'selected' when: compositionRoot.selected PropertyChanges { target: compositionRoot z: 1 color: 'mediumpurple' } } ] Rectangle { id: trimIn anchors.left: displayRect.left anchors.leftMargin: 0 height: displayRect.height width: root.baseUnit / 3 color: isAudio? 'green' : 'lawngreen' opacity: 0 Drag.active: trimInMouseArea.drag.active Drag.proposedAction: Qt.MoveAction enabled: !compositionRoot.grouped visible: trimInMouseArea.pressed || (root.activeTool === 0 && !dragProxyArea.drag.active && compositionRoot.width > 4 * width) MouseArea { id: trimInMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.SizeHorCursor drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false onPressed: { - root.stopScrolling = true + root.autoScrolling = false compositionRoot.originalX = compositionRoot.x compositionRoot.originalDuration = clipDuration parent.anchors.left = undefined } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll parent.anchors.left = displayRect.left compositionRoot.trimmedIn(compositionRoot) parent.opacity = 0 } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var delta = Math.round((trimIn.x) / timeScale) if (delta < -modelStart) { delta = -modelStart } if (delta !== 0) { var newDuration = compositionRoot.clipDuration - delta compositionRoot.trimmingIn(compositionRoot, newDuration, mouse) } } } onEntered: parent.opacity = 0.5 onExited: parent.opacity = 0 } } Rectangle { id: trimOut anchors.right: displayRect.right anchors.rightMargin: 0 height: displayRect.height width: root.baseUnit / 3 color: 'red' opacity: 0 Drag.active: trimOutMouseArea.drag.active Drag.proposedAction: Qt.MoveAction enabled: !compositionRoot.grouped visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !dragProxyArea.drag.active && compositionRoot.width > 4 * width) MouseArea { id: trimOutMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.SizeHorCursor drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false onPressed: { - root.stopScrolling = true + root.autoScrolling = false compositionRoot.originalDuration = clipDuration parent.anchors.right = undefined } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll parent.anchors.right = displayRect.right compositionRoot.trimmedOut(compositionRoot) } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var newDuration = Math.round((parent.x + parent.width) / timeScale) compositionRoot.trimmingOut(compositionRoot, newDuration, mouse) } } onEntered: parent.opacity = 0.5 onExited: parent.opacity = 0 } } } } diff --git a/src/timeline2/view/qml/KeyframeView.qml b/src/timeline2/view/qml/KeyframeView.qml index 3f65d5b9e..9257e883a 100644 --- a/src/timeline2/view/qml/KeyframeView.qml +++ b/src/timeline2/view/qml/KeyframeView.qml @@ -1,292 +1,292 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 . * ***************************************************************************/ import QtQuick 2.6 import QtQuick.Controls 1.4 import QtQml.Models 2.2 Rectangle { property alias kfrCount : keyframes.count anchors.fill: parent color: Qt.rgba(1,1,0.8, 0.3) id: keyframeContainer property int activeFrame property int activeIndex property int inPoint property int outPoint property bool selected property var masterObject property var kfrModel onKfrCountChanged: { keyframecanvas.requestPaint() } Keys.onShortcutOverride: { if (event.key == Qt.Key_Left) { if (event.modifiers & Qt.AltModifier) { activeFrame = keyframes.itemAt(Math.max(0, --activeIndex)).frame } else { var oldFrame = activeFrame activeFrame -= 1 if (activeFrame < 0) { activeFrame = 0 } else { timeline.updateEffectKeyframe(masterObject.clipId, oldFrame, activeFrame) } } event.accepted = true } else if (event.key == Qt.Key_Right) { if (event.modifiers & Qt.AltModifier) { activeFrame = keyframes.itemAt(Math.min(keyframes.count - 1, ++activeIndex)).frame } else { var oldFrame = activeFrame activeFrame += 1 activeFrame = Math.min(activeFrame, parent.width / timeScale) if (activeFrame > oldFrame) { timeline.updateEffectKeyframe(masterObject.clipId, oldFrame, activeFrame) } } event.accepted = true } else if (event.key == Qt.Key_Return || event.key == Qt.Key_Escape) { keyframeContainer.focus = false event.accepted = true } if ((event.key == Qt.Key_Plus) && !(event.modifiers & Qt.ControlModifier)) { var newVal = Math.min(keyframes.itemAt(activeIndex).value / parent.height + .05, 1) kfrModel.updateKeyframe(activeFrame, newVal) event.accepted = true } else if ((event.key == Qt.Key_Minus) && !(event.modifiers & Qt.ControlModifier)) { var newVal = Math.max(keyframes.itemAt(activeIndex).value / parent.height - .05, 0) kfrModel.updateKeyframe(activeFrame, newVal) event.accepted = true } else { event.accepted = false } } Repeater { id: keyframes model: kfrModel Rectangle { id: keyframe property int frame : model.frame property int frameType : model.type x: (model.frame - inPoint) * timeScale height: parent.height property int value: parent.height * model.normalizedValue property int tmpVal : keyframeVal.y + root.baseUnit / 2 property int tmpPos : x + keyframeVal.x + root.baseUnit / 2 property int dragPos : -1 anchors.bottom: parent.bottom onFrameTypeChanged: { keyframecanvas.requestPaint() } onValueChanged: { keyframecanvas.requestPaint() } width: Math.max(1, timeScale) color: kfMouseArea.containsMouse ? 'darkred' : 'transparent' visible: keyframeContainer.selected MouseArea { id: kfMouseArea anchors.fill: parent anchors.leftMargin: - root.baseUnit/3 anchors.rightMargin: - root.baseUnit/3 hoverEnabled: true cursorShape: Qt.SizeHorCursor drag.target: parent drag.smoothed: false drag.axis: Drag.XAxis onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll dragPos = -1 var newPos = Math.round(parent.x / timeScale) + inPoint if (frame != inPoint && newPos != frame) { if (mouse.modifiers & Qt.ShiftModifier) { // offset all subsequent keyframes // TODO: rewrite using timeline to ensure all kf parameters are updated timeline.offsetKeyframes(masterObject.clipId, frame, newPos) } else { timeline.updateEffectKeyframe(masterObject.clipId, frame, newPos) } } } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { if (frame == inPoint) { parent.x = inPoint * timeScale return } var newPos = Math.min(Math.round(parent.x / timeScale), Math.round(keyframeContainer.width / timeScale) - 1) if (newPos < 1) { newPos = 1 } if (newPos != dragPos) { dragPos = newPos parent.x = newPos * timeScale keyframecanvas.requestPaint() } else { parent.x = dragPos * timeScale } } } } Rectangle { id: keyframeVal x: - root.baseUnit / 2 y: keyframeContainer.height - keyframe.value - root.baseUnit / 2 width: root.baseUnit height: width radius: width / 2 color: keyframeContainer.activeFrame == keyframe.frame ? 'red' : kf1MouseArea.containsMouse || kf1MouseArea.pressed ? root.textColor : root.videoColor border.color: kf1MouseArea.containsMouse || kf1MouseArea.pressed ? activePalette.highlight : root.textColor MouseArea { id: kf1MouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor drag.target: parent drag.smoothed: false onPressed: { drag.axis = (mouse.modifiers & Qt.ShiftModifier) ? Drag.YAxis : Drag.XAndYAxis } onClicked: { keyframeContainer.activeFrame = frame keyframeContainer.activeIndex = index keyframeContainer.focus = true } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll var newPos = frame == inPoint ? inPoint : Math.round((keyframe.x + parent.x + root.baseUnit / 2) / timeScale) + inPoint if (newPos == frame && keyframe.value == keyframe.height - parent.y - root.baseUnit / 2) { var pos = masterObject.modelStart + frame - inPoint if (timeline.position != pos) { timeline.seekPosition = pos timeline.position = timeline.seekPosition } return } var newVal = (keyframeContainer.height - (parent.y + mouse.y)) / keyframeContainer.height if (newVal > 1.5 || newVal < -0.5) { if (frame != inPoint) { timeline.removeEffectKeyframe(masterObject.clipId, frame); } else { if (newVal < 0) { newVal = 0; } else if (newVal > 1) { newVal = 1; } timeline.updateEffectKeyframe(masterObject.clipId, frame, frame, newVal) } } else { if (newVal < 0) { newVal = 0; } else if (newVal > 1) { newVal = 1; } timeline.updateEffectKeyframe(masterObject.clipId, frame, frame == inPoint ? frame : newPos, newVal) } } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { if (frame == inPoint) { parent.x = - root.baseUnit / 2 } else { var newPos = Math.min(Math.round((parent.x + root.baseUnit / 2) / timeScale), Math.round(keyframeContainer.width / timeScale) - frame + inPoint - 1) if (frame + newPos <= inPoint) { newPos = inPoint + 1 - frame } if (newPos != dragPos) { dragPos = newPos parent.x = newPos * timeScale - root.baseUnit / 2 keyframecanvas.requestPaint() } else { parent.x = dragPos * timeScale - root.baseUnit / 2 } } keyframecanvas.requestPaint() } } onDoubleClicked: { timeline.removeEffectKeyframe(masterObject.clipId, frame); } } } } } Canvas { id: keyframecanvas anchors.fill: parent contextType: "2d" Component { id: comp PathCurve { } } Component { id: compline PathLine { } } property var paths : [] Path { id: myPath startX: 0; startY: parent.height } onPaint: { if (keyframes.count == 0) { return } context.beginPath() context.fillStyle = Qt.rgba(0,0,0.8, 0.4); paths = [] var xpos var ypos for(var i = 0; i < keyframes.count; i++) { var type = i > 0 ? keyframes.itemAt(i-1).frameType : keyframes.itemAt(i).frameType xpos = keyframes.itemAt(i).tmpPos if (type == 0) { // discrete paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} )) } ypos = keyframes.itemAt(i).tmpVal if (type < 2) { // linear paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} )) } else if (type == 2) { // curve paths.push(comp.createObject(keyframecanvas, {"x": xpos, "y": ypos} )) } } paths.push(compline.createObject(keyframecanvas, {"x": parent.width, "y": ypos} )) paths.push(compline.createObject(keyframecanvas, {"x": parent.width, "y": parent.height} )) myPath.pathElements = paths context.clearRect(0,0, width, height); context.path = myPath; context.closePath() context.fill() } } } diff --git a/src/timeline2/view/qml/TrackHead.qml b/src/timeline2/view/qml/TrackHead.qml index b449e782d..faf4d92cf 100644 --- a/src/timeline2/view/qml/TrackHead.qml +++ b/src/timeline2/view/qml/TrackHead.qml @@ -1,518 +1,518 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * * 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 . */ import QtQuick 2.6 import QtQuick.Controls 1.4 import QtQuick.Controls 2.2 as NEWQML import QtQuick.Controls.Styles 1.2 import QtQuick.Layouts 1.3 Rectangle { id: trackHeadRoot property string trackName property string effectNames property bool isStackEnabled property bool isDisabled property bool collapsed: false property int isComposite property bool isLocked: false property bool isActive: false property bool isAudio property bool showAudioRecord property bool current: false property int myTrackHeight property int trackId : -42 property int collapsedHeight: nameEdit.height + 2 property int iconSize: root.baseUnit * 2 property string trackTag property int thumbsFormat: 0 border.width: 1 border.color: root.frameColor signal clicked() function pulseLockButton() { flashLock.restart(); } color: getTrackColor(isAudio, true) //border.color: selected? 'red' : 'transparent' //border.width: selected? 1 : 0 clip: true state: 'normal' states: [ State { name: 'current' when: trackHeadRoot.current PropertyChanges { target: trackHeadRoot color: selectedTrackColor } }, State { when: !trackHeadRoot.current name: 'normal' PropertyChanges { target: trackHeadRoot color: getTrackColor(isAudio, true) } } ] Keys.onDownPressed: { root.moveSelectedTrack(1) } Keys.onUpPressed: { root.moveSelectedTrack(-1) } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: { parent.clicked() if (mouse.button == Qt.RightButton) { headerMenu.trackId = trackId headerMenu.thumbsFormat = thumbsFormat headerMenu.audioTrack = trackHeadRoot.isAudio headerMenu.recEnabled = trackHeadRoot.showAudioRecord headerMenu.popup() } } onClicked: { parent.forceActiveFocus() nameEdit.visible = false if (mouse.button == Qt.LeftButton) { timeline.showTrackAsset(trackId) } } } ColumnLayout { id: targetColumn width: root.baseUnit / 1.3 height: trackHeadRoot.height Item { width: parent.width Layout.fillHeight: true Layout.topMargin: 1 Layout.bottomMargin: 1 Layout.leftMargin: 1 Layout.alignment: Qt.AlignVCenter Rectangle { id: trackTarget color: 'grey' anchors.fill: parent width: height border.width: 0 visible: trackHeadRoot.isAudio ? timeline.hasAudioTarget : timeline.hasVideoTarget MouseArea { id: targetArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { if (trackHeadRoot.isAudio) { if (trackHeadRoot.trackId == timeline.audioTarget) { timeline.audioTarget = -1; } else if (timeline.hasAudioTarget) { timeline.audioTarget = trackHeadRoot.trackId; } } else { if (trackHeadRoot.trackId == timeline.videoTarget) { timeline.videoTarget = -1; } else if (timeline.hasVideoTarget) { timeline.videoTarget = trackHeadRoot.trackId; } } } } NEWQML.ToolTip { visible: targetArea.containsMouse font.pixelSize: root.baseUnit 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") } } state: 'normalTarget' states: [ State { name: 'target' when: (trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.audioTarget) || (!trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.videoTarget) PropertyChanges { target: trackTarget color: 'green' } }, State { name: 'inactiveTarget' when: (trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.lastAudioTarget) || (!trackHeadRoot.isAudio && trackHeadRoot.trackId == timeline.lastVideoTarget) PropertyChanges { target: trackTarget opacity: 0.3 color: activePalette.text } }, State { name: 'noTarget' when: !trackHeadRoot.isLocked && !trackHeadRoot.isDisabled PropertyChanges { target: trackTarget color: activePalette.base } } ] transitions: [ Transition { to: '*' ColorAnimation { target: trackTarget; duration: 300 } } ] } } } ColumnLayout { id: trackHeadColumn spacing: 0 anchors.fill: parent anchors.leftMargin: targetColumn.width anchors.topMargin: 0 RowLayout { spacing: 0 Layout.leftMargin: 2 ToolButton { id: expandButton implicitHeight: root.baseUnit * 2 implicitWidth: root.baseUnit * 2 iconName: trackHeadRoot.collapsed ? 'arrow-right' : 'arrow-down' onClicked: { trackHeadRoot.myTrackHeight = trackHeadRoot.collapsed ? Math.max(collapsedHeight * 1.5, controller.getTrackProperty(trackId, "kdenlive:trackheight")) : collapsedHeight } tooltip: trackLabel.visible? i18n("Minimize") : i18n("Expand") } Item { width: trackTag.contentWidth + 4 height: width Layout.topMargin: 1 Rectangle { id: trackLed color: Qt.darker(trackHeadRoot.color, 0.45) anchors.fill: parent width: height border.width: 0 radius: 2 Text { id: trackTag text: trackHeadRoot.trackTag anchors.fill: parent font.pixelSize: root.baseUnit * 1.5 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } MouseArea { id: tagMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { timeline.switchTrackActive(trackHeadRoot.trackId) } } NEWQML.ToolTip { visible: tagMouseArea.containsMouse font.pixelSize: root.baseUnit delay: 1500 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text text: i18n("Click to make track active/inactive. Active tracks will react to editing operations") } } state: 'normalled' states: [ State { name: 'locked' when: trackHeadRoot.isLocked PropertyChanges { target: trackLed color: 'red' } }, State { name: 'active' when: trackHeadRoot.isActive PropertyChanges { target: trackLed color: 'yellow' } }, State { name: 'mute' when: trackHeadRoot.isDisabled PropertyChanges { target: trackLed color: 'orange' } }, State { name: 'inactive' when: !trackHeadRoot.isLocked && !trackHeadRoot.isActive PropertyChanges { target: trackLed color: Qt.darker(trackHeadRoot.color, 0.45) } } ] transitions: [ Transition { to: '*' ColorAnimation { target: trackLed; duration: 300 } } ] } } Item { // Spacer Layout.fillWidth: true } ToolButton { iconName: 'tools-wizard' checkable: true enabled: trackHeadRoot.effectNames != '' checked: enabled && trackHeadRoot.isStackEnabled implicitHeight: trackHeadRoot.iconSize implicitWidth: trackHeadRoot.iconSize onClicked: { timeline.showTrackAsset(trackId) controller.setTrackStackEnabled(trackId, !isStackEnabled) } } ToolButton { id: muteButton implicitHeight: trackHeadRoot.iconSize implicitWidth: trackHeadRoot.iconSize iconName: isAudio ? (isDisabled ? 'kdenlive-hide-audio' : 'kdenlive-show-audio') : (isDisabled ? 'kdenlive-hide-video' : 'kdenlive-show-video') iconSource: isAudio ? (isDisabled ? 'qrc:///pics/kdenlive-hide-audio.svgz' : 'qrc:///pics/kdenlive-show-audio.svgz') : (isDisabled ? 'qrc:///pics/kdenlive-hide-video.svgz' : 'qrc:///pics/kdenlive-show-video.svgz') onClicked: controller.setTrackProperty(trackId, "hide", isDisabled ? (isAudio ? '1' : '2') : '3') tooltip: isAudio ? (isDisabled? i18n("Unmute") : i18n("Mute")) : (isDisabled? i18n("Show") : i18n("Hide")) } ToolButton { id: lockButton implicitHeight: trackHeadRoot.iconSize implicitWidth: trackHeadRoot.iconSize iconName: isLocked ? 'kdenlive-lock' : 'kdenlive-unlock' iconSource: isLocked ? 'qrc:///pics/kdenlive-lock.svg' : 'qrc:///pics/kdenlive-unlock.svg' onClicked: controller.setTrackLockedState(trackId, !isLocked) tooltip: isLocked? i18n("Unlock track") : i18n("Lock track") SequentialAnimation { id: flashLock loops: 1 ScaleAnimator { target: lockButton from: 1 to: 2 duration: 500 } ScaleAnimator { target: lockButton from: 2 to: 1 duration: 500 } } } Layout.rightMargin: 4 } RowLayout { id: recLayout Layout.maximumHeight: showAudioRecord ? -1 : 0 Loader { id: audioVuMeter Layout.fillWidth: true Layout.rightMargin: 2 Layout.leftMargin: 4 visible: showAudioRecord && (trackHeadRoot.height >= 2 * muteButton.height + resizer.height) source: isAudio && showAudioRecord ? "AudioLevels.qml" : "" onLoaded: item.trackId = trackId } } RowLayout { Rectangle { id: trackLabel color: 'transparent' Layout.fillWidth: true radius: 2 border.color: trackNameMouseArea.containsMouse ? activePalette.highlight : 'transparent' height: nameEdit.height visible: (trackHeadRoot.height >= trackLabel.height + muteButton.height + resizer.height + recLayout.height) MouseArea { id: trackNameMouseArea anchors.fill: parent hoverEnabled: true propagateComposedEvents: true onDoubleClicked: { nameEdit.visible = true nameEdit.focus = true nameEdit.selectAll() } onClicked: { trackHeadRoot.clicked() trackHeadRoot.focus = true } onEntered: { if (nameEdit.visible == false && trackName == '') { placeHolder.visible = true } } onExited: { if (placeHolder.visible == true) { placeHolder.visible = false } } } Label { text: trackName anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 4 elide: Qt.ElideRight font.pointSize: root.baseUnit * 0.9 } Label { id: placeHolder visible: false enabled: false text: i18n("Edit track name") anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 4 elide: Qt.ElideRight font.pointSize: root.baseUnit * 0.9 } TextField { id: nameEdit visible: false width: parent.width text: trackName font.pointSize: root.baseUnit * 0.9 style: TextFieldStyle { padding.top:0 padding.bottom: 0 background: Rectangle { color: activePalette.base anchors.fill: parent } } onEditingFinished: { controller.setTrackProperty(trackId, "kdenlive:track_name", text) visible = false } } } } Item { // Spacer id: spacer Layout.fillWidth: true Layout.fillHeight: true } } Rectangle { id: resizer height: 4 color: 'red' opacity: 0 Drag.active: trimInMouseArea.drag.active Drag.proposedAction: Qt.MoveAction width: trackHeadRoot.width y: trackHeadRoot.height - height MouseArea { id: trimInMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.SizeVerCursor drag.target: parent drag.axis: Drag.YAxis drag.minimumY: trackHeadRoot.collapsedHeight - resizer.height property double startY property double originalY drag.smoothed: false onPressed: { - root.stopScrolling = true + root.autoScrolling = false startY = mapToItem(null, x, y).y originalY = trackHeadRoot.height // reusing originalX to accumulate delta for bubble help } onReleased: { - root.stopScrolling = false + root.autoScrolling = timeline.autoScroll if (!trimInMouseArea.containsMouse) { parent.opacity = 0 } if (mouse.modifiers & Qt.ShiftModifier) { timeline.adjustAllTrackHeight(trackHeadRoot.trackId, trackHeadRoot.myTrackHeight) } } onEntered: parent.opacity = 0.3 onExited: parent.opacity = 0 onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { parent.opacity = 0.5 var newHeight = originalY + (mapToItem(null, x, y).y - startY) newHeight = Math.max(collapsedHeight, newHeight) trackHeadRoot.myTrackHeight = newHeight } } } } 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 } } }