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