Warning, /multimedia/kdenlive/src/monitor/view/MonitorRuler.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2017 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 import QtQuick.Controls 2.15
0007 import Kdenlive.Controls 1.0
0008 import QtQuick 2.15
0009 import org.kde.kdenlive 1.0 as Kdenlive
0010 
0011     // Monitor ruler
0012 Rectangle {
0013     id: ruler
0014     color: activePalette.base
0015     property bool containsMouse: rulerMouseArea.containsMouse
0016     property bool seekingFinished : controller.seekFinished
0017     // The width of the visible part
0018     property double rulerZoomWidth: root.zoomFactor * width
0019     // The pixel offset
0020     property double rulerZoomOffset: root.zoomStart * width / root.zoomFactor
0021     
0022     property int playheadPosition: controller.position
0023     Rectangle {
0024         color: activePalette.light
0025         width: parent.width
0026         height: 1
0027     }
0028     
0029     Timer {
0030         id: scrollTimer
0031         interval: 200; running: false;
0032         onTriggered: {
0033             if (rulerMouseArea.pressed) {
0034                 // Check if seeking ruler
0035                 var pos = Math.max(rulerMouseArea.mouseX, 0)
0036                 root.mouseRulerPos = pos
0037                 controller.position = Math.min((pos + ruler.rulerZoomOffset) / root.timeScale, root.duration);
0038             } else if (root.showAudiothumb) {
0039                 // Check if seeking audio thumbnail zone
0040                 root.updateScrolling()
0041             }
0042         }
0043     }
0044     
0045     onPlayheadPositionChanged: {
0046         if (root.zoomFactor == 1) {
0047             return
0048         }
0049         var scaledPosition = ruler.playheadPosition * root.timeScale - ruler.rulerZoomOffset
0050         if (scaledPosition < root.baseUnit) {
0051             if (scaledPosition < 0) {
0052                 root.zoomStart = Math.max(0, (rulerZoomOffset + scaledPosition) * root.zoomFactor - (rulerZoomWidth / 2)) / ruler.width
0053             } else {
0054                 root.zoomStart = Math.max(0, (rulerZoomOffset - root.baseUnit) * root.zoomFactor) / ruler.width
0055                 scrollTimer.start()
0056             }
0057         } else if (scaledPosition > ruler.width - root.baseUnit) {
0058             if (scaledPosition > ruler.width) {
0059                 root.zoomStart = Math.min(ruler.width - rulerZoomWidth, (rulerZoomOffset + scaledPosition) * root.zoomFactor - (rulerZoomWidth / 2)) / ruler.width
0060             } else {
0061                 root.zoomStart = Math.min(ruler.width - rulerZoomWidth, (rulerZoomOffset + root.baseUnit) * root.zoomFactor) / ruler.width
0062                 scrollTimer.start()
0063             }
0064         }
0065     }
0066 
0067     function zoomInRuler(xPos)
0068     {
0069         root.showZoomBar = true
0070         var currentX = playhead.x
0071         var currentCursor = playhead.x + playhead.width / 2 + ruler.rulerZoomOffset
0072         
0073         // Adjust zoom factor
0074         root.zoomFactor = Math.min(1, root.zoomFactor / 1.2)
0075         if (root.zoomFactor * ruler.width < root.baseUnit / 2) {
0076             // Don't allow too large zoom
0077             root.zoomFactor = root.baseUnit / 2 / ruler.width
0078         }
0079         // Always try to have cursor pos centered in zoom
0080         var cursorPos = Math.max(0, controller.position / root.duration - root.zoomFactor / 2)
0081         if (cursorPos + root.zoomFactor > 1) {
0082             cursorPos = 1 - root.zoomFactor
0083         }
0084         root.zoomStart = cursorPos
0085     }
0086     
0087     function zoomOutRuler(xPos)
0088     {
0089         root.zoomFactor = Math.min(1, root.zoomFactor * 1.2)
0090         if (root.zoomFactor == 1) {
0091             root.zoomStart = 0
0092             root.showZoomBar = false
0093         } else {
0094             // Always try to have cursor pos centered in zoom
0095             var cursorPos = Math.max(0, controller.position / root.duration - root.zoomFactor / 2)
0096             if (cursorPos + root.zoomFactor > 1) {
0097                 cursorPos = 1 - root.zoomFactor
0098             }
0099             root.zoomStart = cursorPos
0100         }
0101     }
0102 
0103     // Zoom bar container
0104     Kdenlive.ZoomBar {
0105         id: horZoomBar
0106         visible: root.showZoomBar
0107         onVisibleChanged: {
0108             root.zoomOffset = visible ? height : 0
0109         }
0110         toolTipText: controller.toTimecode((root.duration + 1 )* root.zoomFactor)
0111         anchors {
0112             left: parent.left
0113             right: parent.right
0114             bottom: parent.top
0115         }
0116         height: root.baseUnit
0117         fitsZoom: root.zoomFactor === 1 && root.zoomStart === 0
0118         zoomFactor: root.zoomFactor
0119         onProposeZoomFactor: (proposedValue) => {root.zoomFactor = proposedValue}
0120         contentPos: root.zoomStart
0121         onProposeContentPos: (proposedValue) => {root.zoomStart = proposedValue}
0122         onZoomByWheel: wheel => {
0123             if (wheel.angleDelta.y < 0) {
0124                 // zoom out
0125                 zoomOutRuler(wheel.x)
0126             } else {
0127                 // zoom in
0128                 zoomInRuler(wheel.x)
0129             }
0130         }
0131         onFitZoom: {
0132             root.zoomFactor = 1
0133             root.zoomStart = 0
0134         }
0135     }
0136 
0137     onSeekingFinishedChanged : {
0138         playhead.opacity = seekingFinished ? 1 : 0.5
0139     }
0140 
0141     onRulerZoomWidthChanged: {
0142         updateRuler()
0143     }
0144 
0145     Timer {
0146         id: zoneToolTipTimer
0147         interval: 3000; running: false;
0148     }
0149     function forceRepaint()
0150     {
0151         ruler.color = activePalette.base
0152         // Enforce repaint
0153         rulerTicks.model = 0
0154         rulerTicks.model = ruler.rulerZoomWidth / frameSize + 2
0155         playhead.fillColor = activePalette.windowText
0156     }
0157 
0158     function updateRuler()
0159     {
0160         var projectFps = controller.fps()
0161         root.timeScale = ruler.width / (root.duration + 1) / root.zoomFactor
0162         var displayedLength = root.duration * root.zoomFactor / projectFps;
0163         if (displayedLength < 3 ) {
0164             // 1 frame tick
0165             root.frameSize = root.timeScale
0166         } else if (displayedLength < 30) {
0167             // 1 second tick
0168             frameSize = projectFps * root.timeScale
0169         } else if (displayedLength < 150) {
0170             // 5 second tick
0171             frameSize = 5 * projectFps * root.timeScale
0172         } else if (displayedLength < 300) {
0173             // 10 second tick
0174             frameSize = 10 * projectFps * root.timeScale
0175         } else if (displayedLength < 900) {
0176             // 30 second tick
0177             frameSize = 30 * projectFps * root.timeScale
0178         } else if (displayedLength < 1800) {
0179             // 1 min. tick
0180             frameSize = 60 * projectFps * root.timeScale
0181         } else if (displayedLength < 9000) {
0182             // 5 min tick
0183             frameSize = 300 * projectFps * root.timeScale
0184         } else if (displayedLength < 18000) {
0185             // 10 min tick
0186             frameSize = 600 * projectFps * root.timeScale
0187         } else {
0188             // 30 min tick
0189             frameSize = 18000 * projectFps * root.timeScale
0190         }
0191     }
0192 
0193     // Ruler zone
0194     Rectangle {
0195         id: zone
0196         visible: controller.zoneOut >= controller.zoneIn
0197         color: activePalette.highlight
0198         x: controller.zoneIn * root.timeScale - ruler.rulerZoomOffset
0199         width: (controller.zoneOut - controller.zoneIn) * root.timeScale
0200         property bool zoneHovered: rulerMouseArea.pressed == false && controller.zoneOut >= controller.zoneIn && ((rulerMouseArea.containsMouse && rulerMouseArea.mouseX >= zone.x && rulerMouseArea.mouseX < zone.x + zone.width) || trimOutMouseArea.containsMouse || trimOutMouseArea.pressed || trimInMouseArea.containsMouse)
0201         anchors.bottom: parent.bottom
0202         height: ruler.height / 2
0203         opacity: 0.8
0204         onXChanged: {
0205             if (zone.visible) {
0206                 zoneToolTipTimer.start()
0207             }
0208         }
0209         onWidthChanged: {
0210             if (zone.visible) {
0211                 zoneToolTipTimer.start()
0212             }
0213         }
0214     }
0215 
0216     // frame ticks
0217     Repeater {
0218         id: rulerTicks
0219         model: ruler.width / frameSize + 2
0220         Rectangle {
0221             x: index * frameSize - (ruler.rulerZoomOffset % frameSize)
0222             anchors.bottom: ruler.bottom
0223             height: (index % 5) ? ruler.height / 4 : ruler.height / 2
0224             width: 1
0225             color: activePalette.windowText
0226             opacity: 0.5
0227         }
0228     }
0229     MouseArea {
0230         id: rulerMouseArea
0231         anchors.fill: parent
0232         //propagateComposedEvents: true
0233         hoverEnabled: true
0234         onPressed: mouse => {
0235             root.captureRightClick = true
0236             if (mouse.buttons === Qt.LeftButton) {
0237                 var pos = Math.max(mouseX, 0)
0238                 controller.position = Math.min((pos + ruler.rulerZoomOffset) / root.timeScale, root.duration);
0239                 mouse.accepted = true
0240             }
0241         }
0242         onReleased: mouse => {
0243             root.updateClickCapture()
0244         }
0245         onPositionChanged: mouse => {
0246             if (mouse.buttons === Qt.LeftButton) {
0247                 var pos = Math.max(mouseX, 0)
0248                 root.mouseRulerPos = pos
0249                 if (pressed) {
0250                     controller.position = Math.min((pos + ruler.rulerZoomOffset) / root.timeScale, root.duration);
0251                 }
0252             }
0253         }
0254         onWheel: wheel => {
0255             if (wheel.modifiers & Qt.ControlModifier) {
0256                 if (wheel.angleDelta.y < 0) {
0257                     // zoom out
0258                     zoomOutRuler(wheel.x)
0259                 } else {
0260                     // zoom in
0261                     zoomInRuler(wheel.x)
0262                 }
0263             } else {
0264                 wheel.accepted = false
0265             }
0266         }
0267         onEntered: {
0268             controller.setWidgetKeyBinding(xi18nc("@info:whatsthis", "<shortcut>Wheel</shortcut> or <shortcut>arrows</shortcut> to seek 1 frame, <shortcut>Shift</shortcut> to seek 1 second, <shortcut>Alt</shortcut> to seek to marker, <shortcut>Home</shortcut> / <shortcut>End</shortcut> to go to first / last frame"));
0269         }
0270         onExited: {
0271             controller.setWidgetKeyBinding();
0272         }
0273     }
0274     // Zone duration indicator
0275     Rectangle {
0276         visible: zone.zoneHovered || zoneToolTipTimer.running
0277         width: inLabel.contentWidth + 4
0278         height: inLabel.contentHeight + 2
0279         property int centerPos: zone.x + zone.width / 2 - inLabel.contentWidth / 2
0280         x: centerPos < 0 ? 0 : centerPos > ruler.width - inLabel.contentWidth ? ruler.width - inLabel.contentWidth - 2 : centerPos
0281         color: activePalette.alternateBase
0282         anchors.bottom: ruler.top
0283         Label {
0284             id: inLabel
0285             anchors.fill: parent
0286             horizontalAlignment: Text.AlignHCenter
0287             verticalAlignment: Text.AlignBottom
0288             text: trimInMouseArea.containsMouse || trimInMouseArea.pressed ? controller.toTimecode(controller.zoneIn) + '>' + controller.toTimecode(controller.zoneOut - controller.zoneIn) : trimOutMouseArea.containsMouse || trimOutMouseArea.pressed ? controller.toTimecode(controller.zoneOut - controller.zoneIn) + '<' + controller.toTimecode(controller.zoneOut - 1) : controller.toTimecode(controller.zoneOut - controller.zoneIn)
0289             font: fixedFont
0290             color: activePalette.text
0291         }
0292     }
0293     TimelinePlayhead {
0294         id: playhead
0295         visible: controller.position > -1
0296         height: ruler.height * 0.5
0297         width: ruler.height * 1
0298         opacity: 1
0299         anchors.top: ruler.top
0300         fillColor: activePalette.windowText
0301         x: controller.position * root.timeScale - ruler.rulerZoomOffset - (width / 2)
0302     }
0303     MouseArea {
0304         id: trimInMouseArea
0305         x: zone.x - root.baseUnit * 0.4
0306         y: zone.y
0307         height: zone.height
0308         width: root.baseUnit * .8
0309         hoverEnabled: true
0310         cursorShape: Qt.SizeHorCursor
0311         drag {
0312             target: trimInMouseArea
0313             axis: Drag.XAxis
0314             smoothed: false
0315             minimumX: 0
0316             maximumX: ruler.width
0317             threshold: 1
0318         }
0319         onPressed: {
0320             // break binding
0321             root.captureRightClick = true
0322             x = x
0323             controller.startZoneMove()
0324         }
0325         onReleased: {
0326             root.updateClickCapture()
0327             x = Qt.binding(function() { return zone.x - root.baseUnit * .4 })
0328             controller.endZoneMove()
0329         }
0330         onPositionChanged: mouse => {
0331             if (mouse.buttons === Qt.LeftButton) {
0332                 controller.zoneIn = Math.max(0, Math.round((x + (root.baseUnit * .4) + ruler.rulerZoomOffset) / root.timeScale))
0333                 if (mouse.modifiers & Qt.ShiftModifier) {
0334                     controller.position = controller.zoneIn
0335                 }
0336             }
0337         }
0338         onEntered: {
0339             controller.setWidgetKeyBinding(xi18nc("@info:whatsthis", "<shortcut>Drag</shortcut> to set zone in point, <shortcut>Shift+Drag</shortcut> to seek while adjusting zone in"));
0340         }
0341         onExited: {
0342             controller.setWidgetKeyBinding();
0343         }
0344         Rectangle {
0345             id: trimIn
0346             anchors.fill: parent
0347             anchors.leftMargin: root.baseUnit * .4
0348             color: 'white'
0349             opacity: zone.zoneHovered || trimInMouseArea.containsMouse || trimInMouseArea.drag.active ? 0.6 : 0
0350         }
0351     }
0352     MouseArea {
0353         id: trimOutMouseArea
0354         x: zone.x + zone.width - (root.baseUnit * .4)
0355         y: zone.y
0356         width: root.baseUnit * .8
0357         height: zone.height
0358         hoverEnabled: true
0359         cursorShape: Qt.SizeHorCursor
0360         drag {
0361             target: trimOutMouseArea
0362             axis: Drag.XAxis
0363             smoothed: false
0364             minimumX: 0
0365             maximumX: ruler.width - trimOut.width
0366             threshold: 1
0367         }
0368         onPressed: {
0369             // Break binding
0370             root.captureRightClick = true
0371             x = x
0372             controller.startZoneMove()
0373         }
0374         onReleased: {
0375             root.updateClickCapture()
0376             x = Qt.binding(function() { return zone.x + zone.width - (root.baseUnit * .4) })
0377             controller.endZoneMove()
0378         }
0379         onPositionChanged: mouse => {
0380             if (mouse.buttons === Qt.LeftButton) {
0381                 controller.zoneOut = Math.round((x + (root.baseUnit * .4) + ruler.rulerZoomOffset) / root.timeScale)
0382                 if (mouse.modifiers & Qt.ShiftModifier) {
0383                     controller.position = controller.zoneOut
0384                 }
0385             }
0386         }
0387         onEntered: {
0388             controller.setWidgetKeyBinding(xi18nc("@info:whatsthis", "<shortcut>Drag</shortcut> to set zone out point, <shortcut>Shift+Drag</shortcut> to seek while adjusting zone out"));
0389         }
0390         onExited: {
0391             controller.setWidgetKeyBinding();
0392         }
0393         Rectangle {
0394             id: trimOut
0395             anchors.fill: parent
0396             anchors.rightMargin: root.baseUnit * .4
0397             color: 'white'
0398             opacity: zone.zoneHovered || trimOutMouseArea.containsMouse || trimOutMouseArea.drag.active ? 0.6 : 0
0399         }
0400     }
0401 
0402     // markers
0403     Repeater {
0404         model: markersModel
0405         delegate:
0406         Item {
0407             anchors.fill: parent
0408             Rectangle {
0409                 id: markerBase
0410                 width: 1
0411                 height: parent.height
0412                 x: (model.frame) * root.timeScale - ruler.rulerZoomOffset;
0413                 color: model.color
0414             }
0415             Rectangle {
0416                 id: markerTooltip
0417                 visible: !rulerMouseArea.pressed && (guideArea.containsMouse || (rulerMouseArea.containsMouse && Math.abs(rulerMouseArea.mouseX - markerBase.x) < 4))
0418                 property int guidePos: markerBase.x - mlabel.contentWidth / 2
0419                 x: guidePos < 0 ? 0 : (guidePos > (parent.width - mlabel.contentWidth) ? parent.width - mlabel.contentWidth : guidePos)
0420                 radius: 2
0421                 width: Math.max(mlabel.contentWidth, imageTooltip.width + 2)
0422                 height: mlabel.contentHeight + imageTooltip.height
0423                 anchors {
0424                     bottom: parent.top
0425                 }
0426                 color: model.color
0427                 Image {
0428                     id: imageTooltip
0429                     visible: markerTooltip.visible && root.baseThumbPath != undefined
0430                     source: visible ? root.baseThumbPath + model.frame : ''
0431                     asynchronous: true
0432                     height: visible ? 4 * mlabel.height : 0
0433                     fillMode: Image.PreserveAspectFit
0434                     anchors {
0435                         horizontalCenter: markerTooltip.horizontalCenter
0436                         top: parent.top
0437                         topMargin: 1
0438                     }
0439                 }
0440                 Text {
0441                     id: mlabel
0442                     text: model.comment
0443                     font: fixedFont
0444                     verticalAlignment: Text.AlignVCenter
0445                     horizontalAlignment: Text.AlignHCenter
0446                     anchors {
0447                         bottom: parent.bottom
0448                         left: parent.left
0449                         right: parent.right
0450                     }
0451                     color: '#000'
0452                 }
0453                 MouseArea {
0454                     z: 10
0455                     id: guideArea
0456                     anchors.fill: parent
0457                     acceptedButtons: Qt.LeftButton
0458                     cursorShape: Qt.PointingHandCursor
0459                     hoverEnabled: true
0460                     //onDoubleClicked: timeline.editMarker(clipRoot.binId, model.frame)
0461                     onClicked: {
0462                         controller.position = model.frame
0463                     }
0464                 }
0465             }
0466         }
0467     }
0468 }
0469