From f7051c9a73de1e1ead66d1e006b40e0d0d03d3d9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 17 Sep 2024 17:01:06 -0500 Subject: [PATCH 01/17] Remove Droppable Track from Angular timeline code. Replace with static function called on Draggable End (to update UI data). Allows for clips to be dragged to edge of timeline without resetting back to original positions. Also fixed a race condition (on Web Engine) that caused a ng-click to happen after a drag (randomly) clearing selections. --- src/timeline/index.html | 3 +- src/timeline/js/controllers.js | 11 +- src/timeline/js/directives/clip.js | 18 +-- src/timeline/js/directives/track.js | 189 ----------------------- src/timeline/js/directives/transition.js | 19 +-- src/timeline/js/functions.js | 129 ++++++++++++++++ 6 files changed, 143 insertions(+), 226 deletions(-) delete mode 100644 src/timeline/js/directives/track.js diff --git a/src/timeline/index.html b/src/timeline/index.html index 749aa882c..67d4c57f2 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -26,7 +26,6 @@ - @@ -78,7 +77,7 @@
-
+
diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index da847d400..27e8b2c22 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -400,12 +400,6 @@ App.controller("TimelineCtrl", function ($scope) { $scope.setSnappingMode = function (enable_snapping) { $scope.$apply(function () { $scope.enable_snapping = enable_snapping; - if (enable_snapping) { - $(".droppable").draggable("option", "snapTolerance", 20); - } - else { - $(".droppable").draggable("option", "snapTolerance", 0); - } }); }; @@ -580,6 +574,11 @@ App.controller("TimelineCtrl", function ($scope) { // Select item (either clip or transition) $scope.selectItem = function (item_id, item_type, clear_selections, event, force_ripple) { + if ($scope.dragging) { + timeline.qt_log("DEBUG", "Skip selection due to dragging..."); + return; + } + // Trim item_id var id = item_id.replace(`${item_type}_`, ""); diff --git a/src/timeline/js/directives/clip.js b/src/timeline/js/directives/clip.js index bf4c193d3..2dbe810f4 100644 --- a/src/timeline/js/directives/clip.js +++ b/src/timeline/js/directives/clip.js @@ -27,7 +27,7 @@ */ -/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box, drawAudio */ +/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box, drawAudio, updateDraggables */ // Init variables var dragging = false; var resize_disabled = false; @@ -323,9 +323,11 @@ App.directive("tlClip", function ($timeout) { // Hide snapline (if any) scope.hideSnapline(); + // Call the shared function for drag stop + updateDraggables(scope, ui, 'clip'); + // Clear previous drag position previous_drag_position = null; - scope.setDragging(false); }, drag: function (e, ui) { // Retrieve the initial cursor offset @@ -368,18 +370,6 @@ App.directive("tlClip", function ($timeout) { $(this).css("top", newY); } }); - }, - revert: function (valid) { - if (!valid) { - //the drop spot was invalid, so we're going to move all clips to their original position - $(".ui-selected").each(function () { - var oldY = start_clips[$(this).attr("id")]["top"]; - var oldX = start_clips[$(this).attr("id")]["left"]; - - $(this).css("left", oldX); - $(this).css("top", oldY); - }); - } } }); } diff --git a/src/timeline/js/directives/track.js b/src/timeline/js/directives/track.js deleted file mode 100644 index b4d847cce..000000000 --- a/src/timeline/js/directives/track.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * @file - * @brief Track directives (droppable functionality, etc...) - * @author Jonathan Thomas - * @author Cody Parker - * - * @section LICENSE - * - * Copyright (c) 2008-2018 OpenShot Studios, LLC - * . This file is part of - * OpenShot Video Editor, an open-source project dedicated to - * delivering high quality video editing and animation solutions to the - * world. For more information visit . - * - * OpenShot Video Editor 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. - * - * OpenShot Video Editor 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 OpenShot Library. If not, see . - */ - - -// Treats element as a track -// 1: allows clips, transitions, and effects to be dropped -/*global App, timeline, findTrackAtLocation*/ -App.directive("tlTrack", function ($timeout) { - return { - // A = attribute, E = Element, C = Class and M = HTML Comment - restrict: "A", - link: function (scope, element, attrs) { - - scope.$watch("project.layers.length", function (val) { - if (val) { - $timeout(function () { - // Update track indexes if tracks change - scope.updateLayerIndex(); - scope.playhead_height = $("#track-container").height(); - $(".playhead-line").height(scope.playhead_height); - }, 0); - - } - }); - - //make it accept drops - element.droppable({ - accept: ".droppable", - drop: function (event, ui) { - - // Disabling sorting (until all the updates are completed) - scope.enable_sorting = false; - - var scrolling_tracks = $("#scrolling_tracks"); - var vert_scroll_offset = scrolling_tracks.scrollTop(); - var horz_scroll_offset = scrolling_tracks.scrollLeft(); - - // Keep track of each dropped clip (to check for missing transitions below, after they have been dropped) - var dropped_clips = []; - var position_diff = 0; // the time diff to apply to multiple selections (if any) - var ui_selected = $(".ui-selected"); - var selected_item_count = ui_selected.length; - - // Arrays to collect updates for batch processing - var clip_updates = []; - var transition_updates = []; - - // Get uuid to group all these updates as a single transaction - var tid = uuidv4(); - var drop_track_num = -1; - - // with each dragged clip, find out which track they landed on - // Loop through each selected item, and remove the selection if multiple items are selected - // If only 1 item is selected, leave it selected - ui_selected.each(function (index) { - var item = $(this); - - // Determine type of item - var item_type = null; - if (item.hasClass("clip")) { - item_type = "clip"; - } else if (item.hasClass("transition")) { - item_type = "transition"; - } else { - // Unknown drop type - return; - } - - // get the item properties we need - var item_id = item.attr("id"); - var item_num = item_id.substr(item_id.indexOf("_") + 1); - var item_left = item.position().left; - - // Adjust top and left coordinates for scrollbars - item_left = parseFloat(item_left + horz_scroll_offset); - var item_top = parseFloat(item.position().top + vert_scroll_offset); - - // make sure the item isn't dropped off too far to the left - if (item_left < 0) { - item_left = 0; - } - - // get track the item was dropped on - let drop_track = findTrackAtLocation(scope, parseInt(item_top, 10)); - if (drop_track != null) { - // find the item in the json data - let item_data = null; - if (item_type === "clip") { - item_data = findElement(scope.project.clips, "id", item_num); - } else if (item_type === "transition") { - item_data = findElement(scope.project.effects, "id", item_num); - } - - // set time diff (if not already determined) - if (position_diff === 0.0) { - // once calculated, we want to apply the exact same time diff to each clip/trans - position_diff = (item_left / scope.pixelsPerSecond) - item_data.position; - } - - scope.$apply(function () { - //set track and position - item_data.layer = drop_track.number; - item_data.position += position_diff; - }); - scope.$apply(function () { - // Snap to FPS grid (must be done separately so Angular will actually refresh - // when extreme zooms are used (i.e. you drag a clip a partial frame, this causes it - // to jump back to it's original position correctly). - item_data.position = snapToFPSGridTime(scope, item_data.position); - }); - - // Resize timeline if it's too small to contain all clips - scope.resizeTimeline(); - - // Keep track of dropped clips (we'll check for missing transitions in a sec) - dropped_clips.push(item_data); - - // Collect updates for later batch processing - if (item_type === "clip") { - clip_updates.push(item_data); - } else if (item_type === "transition") { - transition_updates.push(item_data); - } - } - }); - - // Now fire all the Qt updates after all scope.$apply calls - // Update clips in Qt - clip_updates.forEach(function(item_data, index) { - var needs_refresh = (index === clip_updates.length - 1); - timeline.update_clip_data(JSON.stringify(item_data), true, true, !needs_refresh, tid); - }); - - // Update transitions in Qt - transition_updates.forEach(function(item_data, index) { - var needs_refresh = (index === transition_updates.length - 1); - timeline.update_transition_data(JSON.stringify(item_data), true, !needs_refresh, tid); - }); - - // Add missing transitions (if any) - if (dropped_clips.length === 1) { - // Hack to only add missing transitions if a single clip is being dropped - for (var clip_index = 0; clip_index < dropped_clips.length; clip_index++) { - var item_data = dropped_clips[clip_index]; - - // Check again for missing transitions - var missing_transition_details = scope.getMissingTransitions(item_data); - if (scope.Qt && missing_transition_details !== null) { - timeline.add_missing_transition(JSON.stringify(missing_transition_details)); - } - } - } - - // Clear dropped clips - dropped_clips = []; - - // Re-sort clips - scope.enable_sorting = true; - scope.sortItems(); - } - }); - } - }; -}); diff --git a/src/timeline/js/directives/transition.js b/src/timeline/js/directives/transition.js index fe36b22cb..b252ae340 100644 --- a/src/timeline/js/directives/transition.js +++ b/src/timeline/js/directives/transition.js @@ -27,7 +27,7 @@ */ -/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */ +/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box, updateDraggables */ // Init Variables var resize_disabled = false; var previous_drag_position = null; @@ -266,10 +266,11 @@ App.directive("tlTransition", function () { // Hide snapline (if any) scope.hideSnapline(); + // Call the shared function for drag stop + updateDraggables(scope, ui, 'transition'); + // Clear previous drag position previous_drag_position = null; - scope.setDragging(false); - }, drag: function (e, ui) { // Retrieve the initial cursor offset @@ -313,18 +314,6 @@ App.directive("tlTransition", function () { } }); - }, - revert: function (valid) { - if (!valid) { - // The drop spot was invalid, so we're going to move all transitions to their original position - $(".ui-selected").each(function () { - var oldY = start_transitions[$(this).attr("id")]["top"]; - var oldX = start_transitions[$(this).attr("id")]["left"]; - - $(this).css("left", oldX); - $(this).css("top", oldY); - }); - } } }); diff --git a/src/timeline/js/functions.js b/src/timeline/js/functions.js index 0ef6306e4..32749d7ce 100644 --- a/src/timeline/js/functions.js +++ b/src/timeline/js/functions.js @@ -538,3 +538,132 @@ function forceDrawRuler() { document.querySelector("#scrolling_tracks").scrollLeft = 10; document.querySelector("#scrolling_tracks").scrollLeft = scroll; } + +// Update the clip/transition data on Draggable stop (replaces the Track droppable) +function updateDraggables(scope, ui, itemType) { + scope.enable_sorting = false; + + var scrolling_tracks = $("#scrolling_tracks"); + var vert_scroll_offset = scrolling_tracks.scrollTop(); + var horz_scroll_offset = scrolling_tracks.scrollLeft(); + + // Track each dropped clip or transition + var dropped_clips = []; + var position_diff = 0; // The time difference for multiple selections (if any) + var ui_selected = $(".ui-selected"); + + // Arrays to collect updates for batch processing + var clip_updates = []; + var transition_updates = []; + + // UUID to group these updates as a single transaction + var tid = uuidv4(); + + // Loop through each selected item and remove the selection if multiple items are selected + ui_selected.each(function (index) { + var item = $(this); + + // Determine the type of item (clip or transition) + var item_type = itemType || null; + if (item.hasClass("clip")) { + item_type = "clip"; + } else if (item.hasClass("transition")) { + item_type = "transition"; + } else { + // Unknown drop type, skip it + return; + } + + // Get the item properties + var item_id = item.attr("id"); + var item_num = item_id.substr(item_id.indexOf("_") + 1); + var item_left = item.position().left; + + // Adjust for scrollbars + item_left = parseFloat(item_left + horz_scroll_offset); + var item_top = parseFloat(item.position().top + vert_scroll_offset); + + // Prevent items from being dropped too far to the left + if (item_left < 0) { + item_left = 0; + } + + // Get the track where the item was dropped + let drop_track = findTrackAtLocation(scope, parseInt(item_top, 10)); + if (drop_track != null) { + // Find the item in the project JSON data + let item_data = null; + if (item_type === "clip") { + item_data = findElement(scope.project.clips, "id", item_num); + } else if (item_type === "transition") { + item_data = findElement(scope.project.effects, "id", item_num); + } + + // Set the time difference (if not already calculated) + if (position_diff === 0.0) { + position_diff = (item_left / scope.pixelsPerSecond) - item_data.position; + } + + scope.$apply(function () { + // Set track and position + item_data.layer = drop_track.number; + item_data.position += position_diff; + }); + + scope.$apply(function () { + // Snap to FPS grid (if necessary) + item_data.position = snapToFPSGridTime(scope, item_data.position); + }); + + // Resize timeline if necessary + scope.resizeTimeline(); + + // Keep track of dropped clips/transitions + dropped_clips.push(item_data); + + // Collect updates for batch processing + if (item_type === "clip") { + clip_updates.push(item_data); + } else if (item_type === "transition") { + transition_updates.push(item_data); + } + } + }); + + // Batch process updates + clip_updates.forEach(function(item_data, index) { + var needs_refresh = (index === clip_updates.length - 1); + timeline.update_clip_data(JSON.stringify(item_data), true, true, !needs_refresh, tid); + }); + + transition_updates.forEach(function(item_data, index) { + var needs_refresh = (index === transition_updates.length - 1); + timeline.update_transition_data(JSON.stringify(item_data), true, !needs_refresh, tid); + }); + + // Add missing transitions (if any) + if (dropped_clips.length === 1) { + for (var clip_index = 0; clip_index < dropped_clips.length; clip_index++) { + var item_data = dropped_clips[clip_index]; + + // Check for missing transitions + var missing_transition_details = scope.getMissingTransitions(item_data); + if (scope.Qt && missing_transition_details !== null) { + timeline.add_missing_transition(JSON.stringify(missing_transition_details)); + } + } + } + + // Clear dropped clips + dropped_clips = []; + + // Re-enable sorting and sort items + scope.enable_sorting = true; + scope.sortItems(); + + // Delay clearing the dragging variable (to prevent an ng-click race condition + // which causes selections to be randomly cleared after a drag) + setTimeout(function() { + scope.setDragging(false); + }, 100); +} From 8f378dce621178d0ac1752c3ac8e2620e119d9ed Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 17 Sep 2024 17:07:57 -0500 Subject: [PATCH 02/17] Fixing Codacy nitpicks --- src/timeline/js/directives/clip.js | 2 +- src/timeline/js/directives/transition.js | 2 +- src/timeline/js/functions.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/timeline/js/directives/clip.js b/src/timeline/js/directives/clip.js index 2dbe810f4..6a51278e3 100644 --- a/src/timeline/js/directives/clip.js +++ b/src/timeline/js/directives/clip.js @@ -324,7 +324,7 @@ App.directive("tlClip", function ($timeout) { scope.hideSnapline(); // Call the shared function for drag stop - updateDraggables(scope, ui, 'clip'); + updateDraggables(scope, ui, "clip"); // Clear previous drag position previous_drag_position = null; diff --git a/src/timeline/js/directives/transition.js b/src/timeline/js/directives/transition.js index b252ae340..0fe755e14 100644 --- a/src/timeline/js/directives/transition.js +++ b/src/timeline/js/directives/transition.js @@ -267,7 +267,7 @@ App.directive("tlTransition", function () { scope.hideSnapline(); // Call the shared function for drag stop - updateDraggables(scope, ui, 'transition'); + updateDraggables(scope, ui, "transition"); // Clear previous drag position previous_drag_position = null; diff --git a/src/timeline/js/functions.js b/src/timeline/js/functions.js index 32749d7ce..ab62032e7 100644 --- a/src/timeline/js/functions.js +++ b/src/timeline/js/functions.js @@ -26,7 +26,7 @@ * along with OpenShot Library. If not, see . */ -/*global bounding_box, global_primes*/ +/*global bounding_box, global_primes, timeline*/ // Generate a UUID function uuidv4() { From ca35a9f9a0d3daf9139aeb12c9a0c87518187de6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 17 Sep 2024 18:16:19 -0500 Subject: [PATCH 03/17] Fixed playhead and ruler dragging to be global (i.e. you can drag outside the timeline without interrupting the drag operation). --- src/timeline/js/directives/playhead.js | 32 +++++++++----- src/timeline/js/directives/ruler.js | 59 +++++++++++++++----------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/timeline/js/directives/playhead.js b/src/timeline/js/directives/playhead.js index bb9282517..2aa4bc3ba 100644 --- a/src/timeline/js/directives/playhead.js +++ b/src/timeline/js/directives/playhead.js @@ -37,26 +37,24 @@ App.directive("tlPlayhead", function () { link: function (scope, element, attrs) { // get the default top position so we can lock it in place vertically playhead_y_max = element.position().top; + var isDragging = false; element.on("mousedown", function (e) { // Set bounding box for the playhead setBoundingBox(scope, $("#playhead"), "playhead"); - if (scope.Qt) { - // Disable caching thread during scrubbing - timeline.DisableCacheThread(); - } - }); - element.on("contextmenu", function (e) { + // Start dragging + isDragging = true; + if (scope.Qt) { - // Enable caching thread after scrubbing - timeline.EnableCacheThread(); + // Disable caching thread during scrubbing + timeline.DisableCacheThread(); } }); - // Move playhead to new position (if it's not currently being animated) - element.on("mousemove", function (e) { - if (e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button + // Global mousemove listener + $(document).on("mousemove", function (e) { + if (isDragging && e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button is held // Calculate the playhead bounding box movement and apply snapping rules let cursor_position = e.pageX - $("#ruler").offset().left; let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top, @@ -71,11 +69,23 @@ App.directive("tlPlayhead", function () { // Move playhead let playhead_seconds = snapToFPSGridTime(scope, pixelToTime(scope, new_position)); + playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration); scope.movePlayhead(playhead_seconds); scope.previewFrame(playhead_seconds); } }); + // Global mouseup listener to stop dragging + $(document).on("mouseup", function (e) { + if (isDragging) { + isDragging = false; + + if (scope.Qt) { + // Enable caching thread after scrubbing + timeline.EnableCacheThread(); + } + } + }); } }; }); diff --git a/src/timeline/js/directives/ruler.js b/src/timeline/js/directives/ruler.js index 28bf0bfc0..3bfe3217a 100644 --- a/src/timeline/js/directives/ruler.js +++ b/src/timeline/js/directives/ruler.js @@ -27,7 +27,7 @@ */ -/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */ +/*global App, timeline, secondsToTime, setSelections, setBoundingBox, moveBoundingBox, bounding_box */ // Variables for panning by middle click var is_scrolling = false; var starting_scrollbar = {x: 0, y: 0}; @@ -39,7 +39,6 @@ var scroll_left_pixels = 0; // This container allows for tracks to be scrolled (with synced ruler) // and allows for panning of the timeline with the middle mouse button -/*global App, timeline, secondsToTime*/ App.directive("tlScrollableTracks", function () { return { restrict: "A", @@ -154,11 +153,24 @@ App.directive("tlRuler", function ($timeout) { return { restrict: "A", link: function (scope, element, attrs) { - //on click of the ruler canvas, jump playhead to the clicked spot + var isDragging = false; + + // Start dragging when mousedown on the ruler element.on("mousedown", function (e) { + // Set bounding box for the playhead position + setBoundingBox(scope, $("#playhead"), "playhead"); + isDragging = true; + + if (scope.Qt) { + // Disable caching thread during scrubbing + timeline.DisableCacheThread(); + } + // Get playhead position var playhead_left = e.pageX - element.offset().left; var playhead_seconds = snapToFPSGridTime(scope, pixelToTime(scope, playhead_left)); + playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration); + var playhead_snapped_target = playhead_seconds * scope.pixelsPerSecond; // Immediately preview frame (don't wait for animated playhead) scope.previewFrame(playhead_seconds); @@ -170,8 +182,8 @@ App.directive("tlRuler", function ($timeout) { // Animate to new position (and then update scope) scope.playhead_animating = true; - $(".playhead-line").animate({left: playhead_left}, 200); - $(".playhead-top").animate({left: playhead_left}, 200, function () { + $(".playhead-line").animate({left: playhead_snapped_target}, 150); + $(".playhead-top").animate({left: playhead_snapped_target}, 150, function () { // Update playhead scope.movePlayhead(playhead_seconds); @@ -182,30 +194,14 @@ App.directive("tlRuler", function ($timeout) { }); }); - element.on("mousedown", function (e) { - // Set bounding box for the playhead position - setBoundingBox(scope, $("#playhead"), "playhead"); - if (scope.Qt) { - // Disable caching thread during scrubbing - timeline.DisableCacheThread(); - } - }); - - element.on("contextmenu", function (e) { - if (scope.Qt) { - // Enable caching thread after scrubbing - timeline.EnableCacheThread(); - } - }); - - // Move playhead to new position (if it's not currently being animated) - element.on("mousemove", function (e) { - if (e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button + // Global mousemove listener + $(document).on("mousemove", function (e) { + if (isDragging && e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button is held // Calculate the playhead bounding box movement let cursor_position = e.pageX - $("#ruler").offset().left; let new_position = cursor_position; if (e.shiftKey) { - // Only apply playhead shapping when SHIFT is pressed + // Only apply playhead snapping when SHIFT is pressed let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top, cursor_position - bounding_box.left, cursor_position - bounding_box.top, cursor_position, cursor_position, "playhead"); @@ -216,11 +212,24 @@ App.directive("tlRuler", function ($timeout) { // Move playhead let playhead_seconds = new_position / scope.pixelsPerSecond; + playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration); scope.movePlayhead(playhead_seconds); scope.previewFrame(playhead_seconds); } }); + // Global mouseup listener to stop dragging + $(document).on("mouseup", function (e) { + if (isDragging) { + isDragging = false; + + if (scope.Qt) { + // Enable caching thread after scrubbing + timeline.EnableCacheThread(); + } + } + }); + /** * Draw frame precision alternating banding on each track (when zoomed in) */ From 3ad246b793cdeb81f4d32f372bb0f5e2e02aa3cd Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 17 Sep 2024 18:22:59 -0500 Subject: [PATCH 04/17] Clicking the ruler time (top left of the timeline ruler) now jumps to the beginning of the timeline (moves the playhead and scrolls) --- src/timeline/index.html | 2 +- src/timeline/js/controllers.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/timeline/index.html b/src/timeline/index.html index 67d4c57f2..568b17d12 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -44,7 +44,7 @@
-
{{playheadTime.hour}}:{{playheadTime.min}}:{{playheadTime.sec}},{{playheadTime.frame}}
+
{{playheadTime.hour}}:{{playheadTime.min}}:{{playheadTime.sec}},{{playheadTime.frame}}
diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index 27e8b2c22..6bc927838 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -870,6 +870,20 @@ App.controller("TimelineCtrl", function ($scope) { return Math.max(min_value, $scope.project.duration * $scope.pixelsPerSecond); }; + // Seek to the beginning of the timeline + $scope.rulerTimeClick = function () { + $scope.movePlayhead(0.0); + $scope.previewFrame(0.0); + + // Force a scroll event (from 1 to 0, to send the geometry to zoom slider) + $("#scrolling_tracks").scrollLeft(1); + + // Scroll to top/left when loading a project + $("#scrolling_tracks").animate({ + scrollTop: 0, + scrollLeft: 0 + }, "slow"); + }; // Get Position of item (used by Qt), both the position and track number. /** From 233c600c1d1c488d7c6e4b1bda90aaf8bc7152e1 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 18 Sep 2024 15:56:04 -0500 Subject: [PATCH 05/17] Fix small regression on clip/transition selection when clicking or resizing an unselected clip/transition --- src/timeline/js/directives/clip.js | 12 +++++++----- src/timeline/js/directives/transition.js | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/timeline/js/directives/clip.js b/src/timeline/js/directives/clip.js index 6a51278e3..b6f637de2 100644 --- a/src/timeline/js/directives/clip.js +++ b/src/timeline/js/directives/clip.js @@ -54,11 +54,13 @@ App.directive("tlClip", function ($timeout) { minWidth: 1, maxWidth: scope.clip.length * scope.pixelsPerSecond, start: function (e, ui) { - scope.setDragging(true); - // Set selections setSelections(scope, element, $(this).attr("id")); + // Set dragging mode + scope.setDragging(true); + resize_disabled = false; + // Set bounding box setBoundingBox(scope, $(this), "trimming"); @@ -276,12 +278,12 @@ App.directive("tlClip", function ($timeout) { distance: 5, cancel: ".effect-container,.clip_menu,.point", start: function (event, ui) { - previous_drag_position = null; - scope.setDragging(true); - // Set selections setSelections(scope, element, $(this).attr("id")); + previous_drag_position = null; + scope.setDragging(true); + // Store initial cursor vs draggable offset var elementOffset = $(this).offset(); var cursorOffset = { diff --git a/src/timeline/js/directives/transition.js b/src/timeline/js/directives/transition.js index 0fe755e14..8c46cdde0 100644 --- a/src/timeline/js/directives/transition.js +++ b/src/timeline/js/directives/transition.js @@ -52,12 +52,13 @@ App.directive("tlTransition", function () { handles: "e, w", minWidth: 1, start: function (e, ui) { - scope.setDragging(true); - resize_disabled = false; - // Set selections setSelections(scope, element, $(this).attr("id")); + // Set dragging mode + scope.setDragging(true); + resize_disabled = false; + // Set bounding box setBoundingBox(scope, $(this), "trimming"); @@ -220,12 +221,13 @@ App.directive("tlTransition", function () { distance: 5, cancel: ".transition_menu, .point", start: function (event, ui) { - previous_drag_position = null; - scope.setDragging(true); - // Set selections setSelections(scope, element, $(this).attr("id")); + // Set dragging mode + previous_drag_position = null; + scope.setDragging(true); + // Store initial cursor vs draggable offset var elementOffset = $(this).offset(); var cursorOffset = { From 0eb5935ad4058aab3b8a84537b16139a29b29320 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 19 Sep 2024 20:58:15 -0500 Subject: [PATCH 06/17] Tons of fixes to Zoom Slider, no longer breaks when zooming too far out. Adding a new double click to reveal the entire timeline. Resizable tracks (right edge) to adjust project duration. Fixed cursor over timeline ruler time values. --- doc/main_window.rst | 176 ++++++++++++++-------------- src/settings/_default.settings | 8 ++ src/timeline/index.html | 3 +- src/timeline/js/controllers.js | 8 +- src/timeline/js/directives/track.js | 88 ++++++++++++++ src/timeline/js/functions.js | 4 +- src/timeline/media/css/main.css | 3 + src/windows/main_window.py | 3 + src/windows/views/timeline.py | 9 +- src/windows/views/zoom_slider.py | 56 ++++++++- 10 files changed, 253 insertions(+), 105 deletions(-) create mode 100644 src/timeline/js/directives/track.js diff --git a/doc/main_window.rst b/doc/main_window.rst index 3c847e9c6..0f54e8bbf 100644 --- a/doc/main_window.rst +++ b/doc/main_window.rst @@ -103,7 +103,7 @@ Timeline Toolbar Previous Marker Jump to the previous marker. This moves the playhead to the left, seeking to the next marker or important position (i.e. start / end positions of clips). Next Marker Jump to the next marker. This moves the playhead to the right, seeking to the next marker or important position (i.e. start / end positions of clips). Center Timeline on Playhead This centers the timeline on the playhead position. This can be useful if the playhead is not visible and you want to quickly scroll the timeline to that position. - Zoom Slider This controls the visible portion of the timeline. Adjusting the left/right handles will zoom in/out of your timeline, keeping a specific section of your project in view. + Zoom Slider This controls the visible portion of the timeline. Adjusting the left/right handles will zoom in/out of your timeline, keeping a specific section of your project in view. Double click to zoom to your entire timeline. =========================== ============ .. _keyboard_shortcut_ref: @@ -116,91 +116,95 @@ configure these shortcuts in the Preferences window, which is opened by selectin (On macOS, choose :guilabel:`OpenShot Video Editor→Preferences`.) Learning a few of these shortcuts can save you a bunch of time! -=================================== ======================= ========================== ==================== -Action Shortcut 1 Shortcut 2 Shortcut 3 -=================================== ======================= ========================== ==================== -About OpenShot :kbd:`Ctrl+H` -Add Marker :kbd:`M` -Add Track :kbd:`Ctrl+Y` -Add to Timeline :kbd:`Ctrl+Alt+A` -Advanced View :kbd:`Alt+Shift+1` -Animated Title :kbd:`Ctrl+Shift+T` -Ask a Question... :kbd:`F4` -Center on Playhead :kbd:`Shift+C` :kbd:`Alt+Up` -Choose Profile :kbd:`Ctrl+Alt+P` -Clear All Cache :kbd:`Ctrl+Shift+ESC` -Clear History :kbd:`Ctrl+Shift+H` -Clear Waveform Display Data :kbd:`Ctrl+Shift+W` -Copy :kbd:`Ctrl+C` -Cut :kbd:`Ctrl+X` -Delete Item :kbd:`Delete` :kbd:`Backspace` -Delete Item (Ripple) :kbd:`Shift+Delete` -Details View :kbd:`Ctrl+Page Up` -Donate :kbd:`F7` -Duplicate :kbd:`Ctrl+Shift+/` -Edit Title :kbd:`Alt+T` -Export Selected Files :kbd:`Ctrl+Shift+E` -Export Video / Media :kbd:`Ctrl+E` :kbd:`Ctrl+M` -Fast Forward :kbd:`L` -File Properties :kbd:`Alt+I` :kbd:`Ctrl+Double Click` -Freeze View :kbd:`Ctrl+F` -Fullscreen :kbd:`F11` -Import Files... :kbd:`Ctrl+I` -Insert Keyframe :kbd:`Alt+Shift+K` -Join our Community... :kbd:`F5` -Jump To End :kbd:`End` -Jump To Start :kbd:`Home` -Launch Tutorial :kbd:`F2` -New Project :kbd:`Ctrl+N` -Next Frame :kbd:`Right` :kbd:`.` -Next Marker :kbd:`Shift+M` :kbd:`Alt+Right` -Nudge left (1 Frame) :kbd:`Ctrl+Left` -Nudge left (5 Frames) :kbd:`Shift+Ctrl+Left` -Nudge right (1 Frame) :kbd:`Ctrl+Right` -Nudge right (5 Frames) :kbd:`Shift+Ctrl+Right` -Open Help Contents :kbd:`F1` -Open Project... :kbd:`Ctrl+O` -Paste :kbd:`Ctrl+V` -Play/Pause Toggle :kbd:`Space` :kbd:`Up` :kbd:`Down` -Preferences :kbd:`Ctrl+P` -Preview File :kbd:`Alt+P` :kbd:`Double Click` -Previous Frame :kbd:`Left` :kbd:`,` -Previous Marker :kbd:`Ctrl+Shift+M` :kbd:`Alt+Left` -Properties :kbd:`U` -Quit :kbd:`Ctrl+Q` -Razor Toggle :kbd:`C` :kbd:`B` :kbd:`R` -Redo :kbd:`Ctrl+Shift+Z` -Report a Bug... :kbd:`F3` -Rewind :kbd:`J` -Save Current Frame :kbd:`Ctrl+Shift+Y` -Save Current Frame :kbd:`Ctrl+Shift+Y` -Save Project :kbd:`Ctrl+S` -Save Project As... :kbd:`Ctrl+Shift+S` -Select All :kbd:`Ctrl+A` -Select Item (Ripple) :kbd:`Shift+A` :kbd:`Shift+Click` -Select None :kbd:`Ctrl+Shift+A` -Show All Docks :kbd:`Ctrl+Shift+D` -Simple View :kbd:`Alt+Shift+0` -Slice All: Keep Both Sides :kbd:`Ctrl+Shift+K` -Slice All: Keep Left Side :kbd:`Ctrl+Shift+J` -Slice All: Keep Right Side :kbd:`Ctrl+Shift+L` -Slice Selected: Keep Both Sides :kbd:`Ctrl+K` -Slice Selected: Keep Left Side :kbd:`Ctrl+J` -Slice Selected: Keep Right Side :kbd:`Ctrl+L` -Slice Selected: Keep Left (Ripple) :kbd:`W` -Slice Selected: Keep Right (Ripple) :kbd:`Q` -Snapping Toggle :kbd:`S` -Split File :kbd:`Alt+S` :kbd:`Shift+Double Click` -Thumbnail View :kbd:`Ctrl+Page Down` -Title :kbd:`Ctrl+T` -Transform :kbd:`Ctrl+Alt+T` -Translate this Application... :kbd:`F6` -Un-Freeze View :kbd:`Ctrl+Shift+F` -Undo :kbd:`Ctrl+Z` -View Toolbar :kbd:`Ctrl+Shift+B` -Zoom In :kbd:`=` :kbd:`Ctrl+=` -Zoom Out :kbd:`-` :kbd:`Ctrl+-` -=================================== ======================= ========================== ==================== +.. table:: + :widths: 35 20 20 20 + + =================================== ======================= ========================== ==================== + Action Shortcut 1 Shortcut 2 Shortcut 3 + =================================== ======================= ========================== ==================== + About OpenShot :kbd:`Ctrl+H` + Add Marker :kbd:`M` + Add Track :kbd:`Ctrl+Y` + Add to Timeline :kbd:`Ctrl+Alt+A` + Advanced View :kbd:`Alt+Shift+1` + Animated Title :kbd:`Ctrl+Shift+T` + Ask a Question... :kbd:`F4` + Center on Playhead :kbd:`Shift+C` :kbd:`Alt+Up` + Choose Profile :kbd:`Ctrl+Alt+P` + Clear All Cache :kbd:`Ctrl+Shift+ESC` + Clear History :kbd:`Ctrl+Shift+H` + Clear Waveform Display Data :kbd:`Ctrl+Shift+W` + Copy :kbd:`Ctrl+C` + Cut :kbd:`Ctrl+X` + Delete Item :kbd:`Delete` :kbd:`Backspace` + Delete Item (Ripple) :kbd:`Shift+Delete` + Details View :kbd:`Ctrl+Page Up` + Donate :kbd:`F7` + Duplicate :kbd:`Ctrl+Shift+/` + Edit Title :kbd:`Alt+T` + Export Selected Files :kbd:`Ctrl+Shift+E` + Export Video / Media :kbd:`Ctrl+E` :kbd:`Ctrl+M` + Fast Forward :kbd:`L` + File Properties :kbd:`Alt+I` :kbd:`Ctrl+Double Click` + Freeze View :kbd:`Ctrl+F` + Fullscreen :kbd:`F11` + Import Files... :kbd:`Ctrl+I` + Insert Keyframe :kbd:`Alt+Shift+K` + Join our Community... :kbd:`F5` + Jump To End :kbd:`End` + Jump To Start :kbd:`Home` + Launch Tutorial :kbd:`F2` + New Project :kbd:`Ctrl+N` + Next Frame :kbd:`Right` :kbd:`.` + Next Marker :kbd:`Shift+M` :kbd:`Alt+Right` + Nudge left (1 Frame) :kbd:`Ctrl+Left` + Nudge left (5 Frames) :kbd:`Shift+Ctrl+Left` + Nudge right (1 Frame) :kbd:`Ctrl+Right` + Nudge right (5 Frames) :kbd:`Shift+Ctrl+Right` + Open Help Contents :kbd:`F1` + Open Project... :kbd:`Ctrl+O` + Paste :kbd:`Ctrl+V` + Play/Pause Toggle :kbd:`Space` :kbd:`Up` :kbd:`Down` + Preferences :kbd:`Ctrl+P` + Preview File :kbd:`Alt+P` :kbd:`Double Click` + Previous Frame :kbd:`Left` :kbd:`,` + Previous Marker :kbd:`Ctrl+Shift+M` :kbd:`Alt+Left` + Properties :kbd:`U` + Quit :kbd:`Ctrl+Q` + Razor Toggle :kbd:`C` :kbd:`B` :kbd:`R` + Redo :kbd:`Ctrl+Shift+Z` + Report a Bug... :kbd:`F3` + Rewind :kbd:`J` + Save Current Frame :kbd:`Ctrl+Shift+Y` + Save Current Frame :kbd:`Ctrl+Shift+Y` + Save Project :kbd:`Ctrl+S` + Save Project As... :kbd:`Ctrl+Shift+S` + Select All :kbd:`Ctrl+A` + Select Item (Ripple) :kbd:`Shift+A` :kbd:`Shift+Click` + Select None :kbd:`Ctrl+Shift+A` + Show All Docks :kbd:`Ctrl+Shift+D` + Simple View :kbd:`Alt+Shift+0` + Slice All: Keep Both Sides :kbd:`Ctrl+Shift+K` + Slice All: Keep Left Side :kbd:`Ctrl+Shift+J` + Slice All: Keep Right Side :kbd:`Ctrl+Shift+L` + Slice Selected: Keep Both Sides :kbd:`Ctrl+K` + Slice Selected: Keep Left Side :kbd:`Ctrl+J` + Slice Selected: Keep Right Side :kbd:`Ctrl+L` + Slice Selected: Keep Left (Ripple) :kbd:`W` + Slice Selected: Keep Right (Ripple) :kbd:`Q` + Snapping Toggle :kbd:`S` + Split File :kbd:`Alt+S` :kbd:`Shift+Double Click` + Thumbnail View :kbd:`Ctrl+Page Down` + Title :kbd:`Ctrl+T` + Transform :kbd:`Ctrl+Alt+T` + Translate this Application... :kbd:`F6` + Un-Freeze View :kbd:`Ctrl+Shift+F` + Undo :kbd:`Ctrl+Z` + View Toolbar :kbd:`Ctrl+Shift+B` + Zoom In :kbd:`=` :kbd:`Ctrl+=` + Zoom Out :kbd:`-` :kbd:`Ctrl+-` + Zoom to Timeline :kbd:`\\` :kbd:`Shift+\\` :kbd:`Double Click` + =================================== ======================= ========================== ==================== Menu ---- diff --git a/src/settings/_default.settings b/src/settings/_default.settings index 1cebd6e4e..c752a9764 100644 --- a/src/settings/_default.settings +++ b/src/settings/_default.settings @@ -814,6 +814,14 @@ "value": "- | Ctrl+-", "type": "text" }, + { + "category": "Keyboard", + "title": "Zoom to Timeline", + "restart": false, + "setting": "actionZoomToTimeline", + "value": "\\ | Shift+\\", + "type": "text" + }, { "category": "Keyboard", "title": "Previous Frame", diff --git a/src/timeline/index.html b/src/timeline/index.html index 568b17d12..4ba058d2c 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -27,6 +27,7 @@ + @@ -77,7 +78,7 @@
-
+
diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index 6bc927838..b51340a55 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -732,10 +732,14 @@ App.controller("TimelineCtrl", function ($scope) { } } // Resize timeline - if (furthest_right_edge > $scope.project.duration - min_timeline_padding || furthest_right_edge < $scope.project.duration - max_timeline_padding) { + if (furthest_right_edge > $scope.project.duration) { if ($scope.Qt) { let new_timeline_length = Math.max(min_timeline_length, furthest_right_edge + min_timeline_padding); timeline.resizeTimeline(new_timeline_length); + // Apply the new duration to the scope + $scope.$apply(function () { + $scope.project.duration = new_timeline_length; + }); } } }; @@ -1559,8 +1563,6 @@ $scope.updateLayerIndex = function () { delete previous_object[current_key]; } } - // Resize timeline if it's too small to contain all clips - $scope.resizeTimeline(); // Re-sort clips and transitions array $scope.sortItems(); diff --git a/src/timeline/js/directives/track.js b/src/timeline/js/directives/track.js new file mode 100644 index 000000000..0692147a2 --- /dev/null +++ b/src/timeline/js/directives/track.js @@ -0,0 +1,88 @@ +/** + * @file + * @brief Track directives (resizable functionality) + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2024 OpenShot Studios, LLC + * . This file is part of + * OpenShot Video Editor, an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Video Editor 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. + * + * OpenShot Video Editor 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 OpenShot Library. If not, see . + */ + + +App.directive('tlTrack', function () { + return { + restrict: 'A', + link: function (scope, element, attrs) { + var newDuration = null; // Define the variable in a higher scope + var minimumWidth = 0; // Define the minimum width based on furthest right edge + + // Function to calculate the furthest right edge of any clip + var getFurthestRightEdge = function() { + var furthest_right_edge = 0; + + for (var clip_index = 0; clip_index < scope.project.clips.length; clip_index++) { + var clip = scope.project.clips[clip_index]; + var right_edge = clip.position + (clip.end - clip.start); + if (right_edge > furthest_right_edge) { + furthest_right_edge = right_edge; + } + } + + return furthest_right_edge; + }; + + // Make the track resizable using jQuery UI, but restrict resizing to the right side + element.resizable({ + handles: 'e', // right edge + minWidth: 0, // Set minimum width (optional) + distance: 5, // threshold for resizing to avoid small movements + + // Event triggered when resizing starts + start: function (event, ui) { + // Calculate the furthest right edge once, at the start of resizing + var furthestRightEdge = getFurthestRightEdge(); + minimumWidth = furthestRightEdge * scope.pixelsPerSecond; + }, + + // Event triggered while resizing + resize: function (event, ui) { + // Get the new width of the track in pixels + var newWidth = Math.max(ui.size.width, minimumWidth); + + // Update the track duration based on the new constrained width and pixels per second + newDuration = snapToFPSGridTime(scope, pixelToTime(scope, newWidth)); + + // Apply the new duration to the scope + scope.$apply(function () { + scope.project.duration = newDuration; + }); + }, + + // Event triggered when resizing ends (mouse released) + stop: function (event, ui) { + // Use the newDuration variable defined in the resize event + if (newDuration !== null) { + timeline.resizeTimeline(newDuration); + } + } + }); + } + }; +}); diff --git a/src/timeline/js/functions.js b/src/timeline/js/functions.js index ab62032e7..094b8eb09 100644 --- a/src/timeline/js/functions.js +++ b/src/timeline/js/functions.js @@ -615,9 +615,6 @@ function updateDraggables(scope, ui, itemType) { item_data.position = snapToFPSGridTime(scope, item_data.position); }); - // Resize timeline if necessary - scope.resizeTimeline(); - // Keep track of dropped clips/transitions dropped_clips.push(item_data); @@ -660,6 +657,7 @@ function updateDraggables(scope, ui, itemType) { // Re-enable sorting and sort items scope.enable_sorting = true; scope.sortItems(); + scope.resizeTimeline(); // Delay clearing the dragging variable (to prevent an ng-click race condition // which causes selections to be randomly cleared after a drag) diff --git a/src/timeline/media/css/main.css b/src/timeline/media/css/main.css index b570a56e3..31e2ac0d6 100644 --- a/src/timeline/media/css/main.css +++ b/src/timeline/media/css/main.css @@ -70,6 +70,7 @@ img { position: relative; line-height: 4px; height: 43px; + margin-right: 8px; /* Prevent playhead from becoming unaligned due to scrollbars */ } #scrolling_tracks { @@ -90,6 +91,7 @@ img { color: #999; padding-top: 12px; padding-left: 17px; + cursor: default; } #progress { @@ -122,6 +124,7 @@ img { font-size: 0.8em; position: absolute; transform: translate(-50%, 0); + cursor: default; } /* Tracks */ diff --git a/src/windows/main_window.py b/src/windows/main_window.py index b81e384bd..fe382fd0f 100644 --- a/src/windows/main_window.py +++ b/src/windows/main_window.py @@ -2155,6 +2155,9 @@ def actionRemoveMarker_trigger(self): # Remove track m.delete() + def actionZoomToTimeline(self): + self.sliderZoomWidget.zoomToTimeline() + def actionTimelineZoomIn_trigger(self): self.sliderZoomWidget.zoomIn() diff --git a/src/windows/views/timeline.py b/src/windows/views/timeline.py index b7a2b901b..2db060709 100644 --- a/src/windows/views/timeline.py +++ b/src/windows/views/timeline.py @@ -3144,13 +3144,8 @@ def ScrollbarChanged(self, new_positions): def resizeTimeline(self, new_duration): """Resize the duration of the timeline""" log.debug(f"Changing timeline to length: {new_duration}") - duration_diff = abs(get_app().project.get('duration') - new_duration) - if (duration_diff > 1.0): - log.debug("Updating duration") - get_app().updates.update_untracked(["duration"], new_duration) - get_app().window.TimelineResize.emit() - else: - log.debug("Duration unchanged. Not updating") + get_app().updates.update_untracked(["duration"], new_duration) + get_app().window.TimelineResize.emit() # Add Transition def addTransition(self, file_path, event_position): diff --git a/src/windows/views/zoom_slider.py b/src/windows/views/zoom_slider.py index 333367a3d..5f0981b10 100644 --- a/src/windows/views/zoom_slider.py +++ b/src/windows/views/zoom_slider.py @@ -24,6 +24,8 @@ You should have received a copy of the GNU General Public License along with OpenShot Library. If not, see . """ +import copy +import math from PyQt5.QtCore import ( Qt, QCoreApplication, QRectF, QTimer @@ -157,12 +159,10 @@ def paintEvent(self, event, *args): layers = Track.filter() # Wait for timeline object and valid scrollbar positions - # TODO: Fix commented out logic - if get_app().window.timeline: # and self.scrollbar_position[2] != 0.0: + if get_app().window.timeline: # Get max width of timeline project_duration = get_app().project.get("duration") pixels_per_second = event.rect().width() / project_duration - project_pixel_width = max(0, project_duration * pixels_per_second) scroll_width = (self.scrollbar_position[1] - self.scrollbar_position[0]) * event.rect().width() # Get FPS info @@ -217,7 +217,8 @@ def paintEvent(self, event, *args): painter.fillPath(left_handle_path, handle_color) # right handle - right_handle_x = (self.scrollbar_position[1] * event.rect().width()) - (handle_width/2.0) + right_handle_x = self.scroll_bar_rect.right() - (handle_width/2.0) + right_handle_x = min(right_handle_x, event.rect().width() - (handle_width/2.0)) self.right_handle_rect = QRectF(right_handle_x, event.rect().height() / 4.0, handle_width, event.rect().height() / 2.0) right_handle_path = QPainterPath() right_handle_path.addRoundedRect(self.right_handle_rect, handle_width, handle_width) @@ -231,6 +232,24 @@ def paintEvent(self, event, *args): # End painter painter.end() + def zoomToTimeline(self): + """Toggle between zooming to the entire timeline and the previous zoom""" + # Are we already zoomed complete out? + if math.isclose(self.scrollbar_position[0], 0.0, abs_tol=1e-9) and math.isclose(self.scrollbar_position[1], 1.0, abs_tol=1e-9): + # Restore previous zoom + self.scrollbar_position[0] = self.scrollbar_zoom_previous[0] + self.scrollbar_position[1] = self.scrollbar_zoom_previous[1] + else: + # Zoom out to reveal the entire timeline + self.scrollbar_zoom_previous = copy.deepcopy(self.scrollbar_position) + self.scrollbar_position[0] = 0.0 + self.scrollbar_position[1] = 1.0 + self.delayed_resize_callback() + + def mouseDoubleClickEvent(self, event): + super(ZoomSlider, self).mouseDoubleClickEvent(event) + self.zoomToTimeline() + def mousePressEvent(self, event): """Capture mouse press event""" event.accept() @@ -412,6 +431,19 @@ def resizeEvent(self, event): self.delayed_size = self.size() self.delayed_resize_timer.start() + def get_scroll_width(self): + """Calculate the width of the scrollbar handle (i.e. selection width)""" + # Get max width of timeline + project_duration = get_app().project.get("duration") + + # Calculate scroll bar / selection width + timeline_pixels_per_second = 100.0 / get_app().project.get("scale") + timeline_project_width = project_duration * timeline_pixels_per_second + scroll_ratio = self.scrollbar_position[3] / timeline_project_width + scroll_width = scroll_ratio * self.width() + scroll_width = min(scroll_width, self.width()) + return scroll_width, scroll_ratio + def delayed_resize_callback(self): """Callback for resize event timer (to delay the resize event, and prevent lots of similar resize events)""" # Get max width of timeline @@ -446,6 +478,14 @@ def setZoomFactor(self, zoom_factor): get_app().window.TimelineZoom.emit(self.zoom_factor) get_app().window.TimelineCenter.emit() + # Prevent scrollbars from exceeding 100% of zoomslider + # This is caused by the scrollbars stopping events once zoomed out too much + scroll_width, scroll_ratio = self.get_scroll_width() + if scroll_ratio >= 1.0 and self.scrollbar_position: + # Set to left/right edge - max + self.scrollbar_position[0] = 0.0 + self.scrollbar_position[1] = 1.0 + # Force re-paint self.repaint() @@ -496,6 +536,11 @@ def handle_selection(self): self.changed(None) self.repaint() + def timeline_resized(self): + # Force recalculation of clips and repaint + self.repaint() + self.delayed_resize_timer.start() + def update_playhead_pos(self, currentFrame): """Callback when position is changed""" self.current_frame = currentFrame @@ -538,6 +583,7 @@ def __init__(self, *args): self.zoom_factor = 15.0 self.scrollbar_position = [0.0, 0.0, 0.0, 0.0] self.scrollbar_position_previous = [0.0, 0.0, 0.0, 0.0] + self.scrollbar_zoom_previous = [0.0, 0.2, 0.0, 0.0] self.left_handle_rect = QRectF() self.left_handle_dragging = False self.right_handle_rect = QRectF() @@ -573,7 +619,7 @@ def __init__(self, *args): # Connect zoom functionality self.win.TimelineScrolled.connect(self.update_scrollbars) - self.win.TimelineResize.connect(self.delayed_resize_callback) + self.win.TimelineResize.connect(self.timeline_resized) self.win.IgnoreUpdates.connect(self.ignore_updates_callback) # Connect Selection signals From a4319ba722b3d7b1731ec240a5b9276dd7d11b7c Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 19 Sep 2024 22:14:22 -0500 Subject: [PATCH 07/17] Fixed bug on double click when restoring previous scrollbar, that caused the current selection position to jump on mouse release --- src/windows/views/zoom_slider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/windows/views/zoom_slider.py b/src/windows/views/zoom_slider.py index 5f0981b10..af98d23ca 100644 --- a/src/windows/views/zoom_slider.py +++ b/src/windows/views/zoom_slider.py @@ -247,8 +247,9 @@ def zoomToTimeline(self): self.delayed_resize_callback() def mouseDoubleClickEvent(self, event): - super(ZoomSlider, self).mouseDoubleClickEvent(event) self.zoomToTimeline() + self.mouse_dragging = True # Prevent mouseReleaseEvent from moving selection + event.accept() def mousePressEvent(self, event): """Capture mouse press event""" From 011890f1529882f7317170e36c49a1f30d9d9403 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 19 Sep 2024 22:19:52 -0500 Subject: [PATCH 08/17] Fixed bug when single click jumping the zoom slider to a new position near the left/right edge, it would shrink the zoom selection (i.e. zoom in unexpectedly) --- src/windows/views/zoom_slider.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/windows/views/zoom_slider.py b/src/windows/views/zoom_slider.py index af98d23ca..c574cc2ba 100644 --- a/src/windows/views/zoom_slider.py +++ b/src/windows/views/zoom_slider.py @@ -269,10 +269,27 @@ def mouseReleaseEvent(self, event): click_pos = event.pos().x() / self.width() selection_width = self.scrollbar_position[1] - self.scrollbar_position[0] half_width = selection_width / 2 - new_left_pos = max(0.0, click_pos - half_width) - new_right_pos = min(1.0, click_pos + half_width) - self.scrollbar_position = [new_left_pos, new_right_pos, self.scrollbar_position[2], - self.scrollbar_position[3]] + + # Calculate new left / right handles + new_left_pos = click_pos - half_width + new_right_pos = click_pos + half_width + + # If the new left position is less than 0, adjust both sides to fit within bounds + if new_left_pos < 0.0: + diff = -new_left_pos + new_left_pos = 0.0 + new_right_pos = min(1.0, new_right_pos + diff) + + # If the new right position is greater than 1, adjust both sides to fit within bounds + if new_right_pos > 1.0: + diff = new_right_pos - 1.0 + new_right_pos = 1.0 + new_left_pos = max(0.0, new_left_pos - diff) + + # Update the scrollbar position to the newly calculated values + self.scrollbar_position = [new_left_pos, new_right_pos, self.scrollbar_position[2], self.scrollbar_position[3]] + + # Trigger the resize and update self.delayed_resize_timer.start() self.update() From c7d98a2255566e3b6710b4d60ec345a55be8dc0b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 20 Sep 2024 14:45:14 -0500 Subject: [PATCH 09/17] Fixed many issues with track resizing, and playhead becoming detacthed from playhead line. Also, added a new snap target for end of timeline. --- src/timeline/index.html | 7 ++- src/timeline/js/controllers.js | 5 ++ src/timeline/js/directives/clip.js | 2 +- src/timeline/js/directives/track.js | 88 +++++++++++++++-------------- src/timeline/media/css/main.css | 16 +++++- 5 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/timeline/index.html b/src/timeline/index.html index 4ba058d2c..1c6b6e51f 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -52,7 +52,7 @@
-
+
@@ -78,10 +78,11 @@
-
+
-
+
+
diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index b51340a55..1475c7f7c 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -1339,6 +1339,11 @@ $scope.updateLayerIndex = function () { } } + // Add end of timeline position + var end_of_track = $scope.project.duration * $scope.pixelsPerSecond; + var end_of_track_diff = position - end_of_track; + diffs.push({"diff": end_of_track_diff, "position": end_of_track}); + // Loop through diffs (and find the smallest one) for (var diff_index = 0; diff_index < diffs.length; diff_index++) { var diff = diffs[diff_index].diff; diff --git a/src/timeline/js/directives/clip.js b/src/timeline/js/directives/clip.js index b6f637de2..309a5f10f 100644 --- a/src/timeline/js/directives/clip.js +++ b/src/timeline/js/directives/clip.js @@ -394,7 +394,7 @@ App.directive("tlMultiSelectable", function () { element.selectable({ filter: ".droppable", distance: 0, - cancel: ".effect-container,.transition_menu,.clip_menu,.point", + cancel: ".effect-container,.transition_menu,.clip_menu,.point,.resize-handle", selected: function (event, ui) { // Identify the selected ID and TYPE var id = ui.selected.id; diff --git a/src/timeline/js/directives/track.js b/src/timeline/js/directives/track.js index 0692147a2..9fef98f19 100644 --- a/src/timeline/js/directives/track.js +++ b/src/timeline/js/directives/track.js @@ -29,60 +29,64 @@ App.directive('tlTrack', function () { return { restrict: 'A', - link: function (scope, element, attrs) { - var newDuration = null; // Define the variable in a higher scope - var minimumWidth = 0; // Define the minimum width based on furthest right edge + link: function (scope, element) { + var startX, startWidth, isResizing = false, newDuration, minimumWidth; // Function to calculate the furthest right edge of any clip var getFurthestRightEdge = function() { - var furthest_right_edge = 0; + return scope.project.clips.reduce((max, clip) => + Math.max(max, clip.position + (clip.end - clip.start)), 0); + }; - for (var clip_index = 0; clip_index < scope.project.clips.length; clip_index++) { - var clip = scope.project.clips[clip_index]; - var right_edge = clip.position + (clip.end - clip.start); - if (right_edge > furthest_right_edge) { - furthest_right_edge = right_edge; - } - } + // Delegate the mousedown event to the parent element for dynamically created resize-handle + element.on('mousedown', '.resize-handle', function(event) { + // Start resizing logic + isResizing = true; + startX = event.pageX; + startWidth = element.width(); - return furthest_right_edge; - }; + // Calculate the minimum width based on the furthest right edge of clips + minimumWidth = getFurthestRightEdge() * scope.pixelsPerSecond; + + // Attach document-wide mousemove and mouseup events + $(document).on('mousemove', resizeTrack); + $(document).on('mouseup', stopResizing); + event.preventDefault(); + }); + + // Function to handle resizing as mouse moves + function resizeTrack(event) { + if (!isResizing) return; - // Make the track resizable using jQuery UI, but restrict resizing to the right side - element.resizable({ - handles: 'e', // right edge - minWidth: 0, // Set minimum width (optional) - distance: 5, // threshold for resizing to avoid small movements + // Calculate the new width (ensure it doesn't go below the minimum width) + var newWidth = Math.max(startWidth + (event.pageX - startX), minimumWidth); - // Event triggered when resizing starts - start: function (event, ui) { - // Calculate the furthest right edge once, at the start of resizing - var furthestRightEdge = getFurthestRightEdge(); - minimumWidth = furthestRightEdge * scope.pixelsPerSecond; - }, + // Update the track's new duration based on the resized width + newDuration = snapToFPSGridTime(scope, pixelToTime(scope, newWidth)); - // Event triggered while resizing - resize: function (event, ui) { - // Get the new width of the track in pixels - var newWidth = Math.max(ui.size.width, minimumWidth); + // Update the element's width dynamically + element.width(newWidth); - // Update the track duration based on the new constrained width and pixels per second - newDuration = snapToFPSGridTime(scope, pixelToTime(scope, newWidth)); + // Apply the new duration to the scope + scope.$apply(function () { + scope.project.duration = newDuration; + }); + } - // Apply the new duration to the scope - scope.$apply(function () { - scope.project.duration = newDuration; - }); - }, + // Function to stop resizing when the mouse button is released + function stopResizing() { + if (!isResizing) return; + isResizing = false; - // Event triggered when resizing ends (mouse released) - stop: function (event, ui) { - // Use the newDuration variable defined in the resize event - if (newDuration !== null) { - timeline.resizeTimeline(newDuration); - } + // Clean up the document-wide event listeners + $(document).off('mousemove', resizeTrack); + $(document).off('mouseup', stopResizing); + + // Finalize the new duration on the timeline (if valid) + if (newDuration !== null) { + timeline.resizeTimeline(newDuration); } - }); + } } }; }); diff --git a/src/timeline/media/css/main.css b/src/timeline/media/css/main.css index 31e2ac0d6..6fb324e62 100644 --- a/src/timeline/media/css/main.css +++ b/src/timeline/media/css/main.css @@ -193,13 +193,25 @@ img { border-top: 1px solid #4b92ad; border-bottom: 1px solid #4b92ad; border-right: 1px solid #4b92ad; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; box-shadow: 0 0 10px #000; position: relative; z-index: 1; } +.resize-handle { + position: absolute; + right: -8px; /* To the right of the track right edge */ + top: 0; + width: 8px; + height: 100%; + cursor: ew-resize; + background-color: silver; +} + +.resize-handle:hover { + background-color: red; +} + .banding-overlay { position: absolute; top: 0; From 2109d0611d9e88208ea356c0b43159a7e5b4e537 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 20 Sep 2024 15:15:15 -0500 Subject: [PATCH 10/17] Renaming .resize-handle to .track-resize-handle for clarity. Adding styles for each theme. --- src/themes/cosmic/theme.py | 13 ++++++++++++- src/themes/humanity/theme.py | 14 ++++++++++---- src/timeline/index.html | 2 +- src/timeline/js/directives/clip.js | 2 +- src/timeline/js/directives/track.js | 2 +- src/timeline/media/css/main.css | 15 ++++++++++----- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/themes/cosmic/theme.py b/src/themes/cosmic/theme.py index a333435f7..cd3aa5a99 100644 --- a/src/themes/cosmic/theme.py +++ b/src/themes/cosmic/theme.py @@ -188,7 +188,7 @@ def __init__(self, app): } QPushButton:hover { - background-color: #283241 + background-color: #283241; } QWidget#settingsContainer { @@ -574,6 +574,17 @@ def apply_theme(self): border-radius: 0px; height: 48px; } + .track-resize-handle { + background-color: #1B222CFF; + border-top: 1px solid #1B222CFF; + border-bottom: 1px solid #1B222CFF; + border-right: 1px solid #1B222CFF; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + } + .track-resize-handle:hover { + background-color: #333F51FF; + } .transition { height: 48px; min-height: 48px; diff --git a/src/themes/humanity/theme.py b/src/themes/humanity/theme.py index 3904152bb..211f3e485 100644 --- a/src/themes/humanity/theme.py +++ b/src/themes/humanity/theme.py @@ -32,10 +32,10 @@ class HumanityDarkTheme(BaseTheme): def __init__(self, app): super().__init__(app) self.style_sheet = """ -QToolTip { - color: #ffffff; - background-color: #2a82da; - border: 0px solid white; +QToolTip { + color: #ffffff; + background-color: #2a82da; + border: 0px solid white; } QComboBox::item { @@ -133,6 +133,12 @@ def apply_theme(self): background: #e5e7ea; box-shadow: none; } + .track-resize-handle { + background-color: #BEBFC1; + } + .track-resize-handle:hover { + background-color: #F7F8FA; + } .transition_top { background: none; border-radius: 0px; diff --git a/src/timeline/index.html b/src/timeline/index.html index 1c6b6e51f..348d07f87 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -82,7 +82,7 @@
-
+
diff --git a/src/timeline/js/directives/clip.js b/src/timeline/js/directives/clip.js index 309a5f10f..605d7e20b 100644 --- a/src/timeline/js/directives/clip.js +++ b/src/timeline/js/directives/clip.js @@ -394,7 +394,7 @@ App.directive("tlMultiSelectable", function () { element.selectable({ filter: ".droppable", distance: 0, - cancel: ".effect-container,.transition_menu,.clip_menu,.point,.resize-handle", + cancel: ".effect-container,.transition_menu,.clip_menu,.point,.track-resize-handle", selected: function (event, ui) { // Identify the selected ID and TYPE var id = ui.selected.id; diff --git a/src/timeline/js/directives/track.js b/src/timeline/js/directives/track.js index 9fef98f19..45c815353 100644 --- a/src/timeline/js/directives/track.js +++ b/src/timeline/js/directives/track.js @@ -39,7 +39,7 @@ App.directive('tlTrack', function () { }; // Delegate the mousedown event to the parent element for dynamically created resize-handle - element.on('mousedown', '.resize-handle', function(event) { + element.on('mousedown', '.track-resize-handle', function(event) { // Start resizing logic isResizing = true; startX = event.pageX; diff --git a/src/timeline/media/css/main.css b/src/timeline/media/css/main.css index 6fb324e62..ba3d5587e 100644 --- a/src/timeline/media/css/main.css +++ b/src/timeline/media/css/main.css @@ -198,18 +198,23 @@ img { z-index: 1; } -.resize-handle { +.track-resize-handle { position: absolute; right: -8px; /* To the right of the track right edge */ - top: 0; + top: -1px; width: 8px; height: 100%; cursor: ew-resize; - background-color: silver; + background-color: #2c2c2c; + border-top: 1px solid #4b92ad; + border-bottom: 1px solid #4b92ad; + border-right: 1px solid #4b92ad; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; } -.resize-handle:hover { - background-color: red; +.track-resize-handle:hover { + background-color: #4C4C4CFF; } .banding-overlay { From 347296f7152c6fe951595fc431df067f7faa0afd Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 21 Sep 2024 23:32:58 -0500 Subject: [PATCH 11/17] When extremely zoomed in on the right edge of the timeline, keep the right edge aligned to the right (not center). Also, don't center unless zoomIn and zoomOut are used - all other zoomSlider functions should not try and center on playhead (it causes flickering) --- src/timeline/js/controllers.js | 11 ++++++++++- src/windows/views/zoom_slider.py | 9 +++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index 1475c7f7c..0c0b635c7 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -347,10 +347,19 @@ App.controller("TimelineCtrl", function ($scope) { var scrolling_tracks = $("#scrolling_tracks"); var scrollingTracksWidth = scrolling_tracks.width(); - // Calculate the position to scroll the timeline to to center on the requested time + // Get the total width of the timeline (the entire scrollable content width) + var totalTimelineWidth = $scope.getTimelineWidth(0); + + // Calculate the position to scroll the timeline to center on the requested time var pixelToCenterOn = parseFloat(centerTime) * $scope.pixelsPerSecond; var scrollPosition = Math.max(pixelToCenterOn - (scrollingTracksWidth / 2.0), 0); + // Condition: Check if we are zoomed into the very right edge of the timeline + if (scrollPosition + scrollingTracksWidth >= totalTimelineWidth) { + // We are near the right edge, so align the right edge with the right of the screen + scrollPosition = totalTimelineWidth - scrollingTracksWidth; + } + // Scroll the timeline using JQuery scrolling_tracks.scrollLeft(Math.floor(scrollPosition + 0.5)); }; diff --git a/src/windows/views/zoom_slider.py b/src/windows/views/zoom_slider.py index c574cc2ba..8a607e703 100644 --- a/src/windows/views/zoom_slider.py +++ b/src/windows/views/zoom_slider.py @@ -487,14 +487,15 @@ def wheelEvent(self, event): # Repaint widget on zoom self.repaint() - def setZoomFactor(self, zoom_factor): + def setZoomFactor(self, zoom_factor, center=False): """Set the current zoom factor""" # Force recalculation of clips self.zoom_factor = zoom_factor # Emit zoom signal get_app().window.TimelineZoom.emit(self.zoom_factor) - get_app().window.TimelineCenter.emit() + if center: + get_app().window.TimelineCenter.emit() # Prevent scrollbars from exceeding 100% of zoomslider # This is caused by the scrollbars stopping events once zoomed out too much @@ -517,7 +518,7 @@ def zoomIn(self): new_factor = self.zoom_factor * 0.8 # Emit zoom signal - self.setZoomFactor(new_factor) + self.setZoomFactor(new_factor, center=True) def zoomOut(self): """Zoom out of timeline""" @@ -530,7 +531,7 @@ def zoomOut(self): new_factor = min(self.zoom_factor * 1.25, 4.0) # Emit zoom signal - self.setZoomFactor(new_factor) + self.setZoomFactor(new_factor, center=True) def update_scrollbars(self, new_positions): """Consume the current scroll bar positions from the webview timeline""" From b8f560b56e4f85fb5c03783009d501204e87a3d4 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 22 Sep 2024 22:20:13 -0500 Subject: [PATCH 12/17] Keep the right edge of the timeline stuck on the right side of the screen, when zooming in, and don't allow timeline to be middle-button panned away from the edge. This prevents the playhead from detaching, and keeps the UI stable when zooming into the far right edge of the timeline. --- src/timeline/index.html | 2 +- src/timeline/js/directives/ruler.js | 43 +++++++++++++++-------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/timeline/index.html b/src/timeline/index.html index 348d07f87..532587440 100644 --- a/src/timeline/index.html +++ b/src/timeline/index.html @@ -52,7 +52,7 @@
-
+
diff --git a/src/timeline/js/directives/ruler.js b/src/timeline/js/directives/ruler.js index 3bfe3217a..03e8bce60 100644 --- a/src/timeline/js/directives/ruler.js +++ b/src/timeline/js/directives/ruler.js @@ -69,40 +69,43 @@ App.directive("tlScrollableTracks", function () { // Sync ruler to track scrolling element.on("scroll", function () { - //set amount scrolled - scroll_left_pixels = element.scrollLeft(); + var scrollLeft = element.scrollLeft(); + var timelineWidth = scope.getTimelineWidth(0); // Full width of the timeline + var maxScrollLeft = timelineWidth - element.width(); // Max horizontal scroll + // Clamp to right edge + element.scrollLeft(Math.min(scrollLeft, maxScrollLeft)); + + // Sync the ruler and other components $("#track_controls").scrollTop(element.scrollTop()); - $("#scrolling_ruler").scrollLeft(element.scrollLeft()); - $("#progress_container").scrollLeft(element.scrollLeft()); + $("#scrolling_ruler, #progress_container").scrollLeft(scrollLeft); - // Send scrollbar position to Qt + // Send scrollbar position to Qt if available if (scope.Qt) { - // Calculate scrollbar positions (left and right edge of scrollbar) - var timeline_length = scope.getTimelineWidth(0); - var left_scrollbar_edge = scroll_left_pixels / timeline_length; - var right_scrollbar_edge = (scroll_left_pixels + element.width()) / timeline_length; + // Create variables first and pass them as arguments + var leftScrollbarEdge = scrollLeft / timelineWidth; // Use the full timeline width + var rightScrollbarEdge = (scrollLeft + element.width()) / timelineWidth; // Use the full timeline width - // Send normalized scrollbar positions to Qt - timeline.ScrollbarChanged([left_scrollbar_edge, right_scrollbar_edge, timeline_length, element.width()]); + // Pass the variables as a JavaScript array (interpreted as a PyQt list) + timeline.ScrollbarChanged([leftScrollbarEdge, rightScrollbarEdge, timelineWidth, element.width()]); } - scope.$apply( () => { - scope.scrollLeft = element[0].scrollLeft; - }) - + // Update scrollLeft in scope + scope.$apply(() => scope.scrollLeft = scrollLeft); }); - // Pans the timeline (on middle mouse clip and drag) + // Pans the timeline (on middle mouse click and drag) element.on("mousemove", function (e) { if (is_scrolling) { - // Calculate difference from last position + console.log("scope.getTimelineWidth(0): " + scope.getTimelineWidth(0)); var difference = {x: starting_mouse_position.x - e.pageX, y: starting_mouse_position.y - e.pageY}; - var newPos = { x: starting_scrollbar.x + difference.x, y: starting_scrollbar.y + difference.y}; + var newPos = { + x: Math.max(0, Math.min(starting_scrollbar.x + difference.x, scope.getTimelineWidth(0) - element.width())), + y: Math.max(0, Math.min(starting_scrollbar.y + difference.y, $("#scrolling_tracks")[0].scrollHeight - element.height())) + }; // Scroll the tracks div - element.scrollLeft(newPos.x); - element.scrollTop(newPos.y); + element.scrollLeft(newPos.x).scrollTop(newPos.y); } }); From c50f3c9241b6ac6c241770559cc82f40e11fbb85 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 24 Sep 2024 13:53:24 -0500 Subject: [PATCH 13/17] Adding new Advanced export option (Export Entire Timeline), which adjusts the end frame to include all frames, even ones that extend past the final clip. Also added this to the documentation, and updated the translation POT. --- doc/export.rst | 15 +-- src/language/OpenShot/OpenShot.pot | 178 +++++++++++++++-------------- src/windows/export.py | 11 +- src/windows/ui/export.ui | 29 +++-- 4 files changed, 131 insertions(+), 102 deletions(-) diff --git a/doc/export.rst b/doc/export.rst index b2094e387..c642cbadf 100644 --- a/doc/export.rst +++ b/doc/export.rst @@ -65,13 +65,14 @@ Advanced Options .. table:: :widths: 10 30 - ================== ============ - Advanced Setting Description - ================== ============ - Export To Export both `video & audio`, `only audio`, `only video`, or an `image sequence` - Start Frame The first frame to export (default is 1) - End Frame The final frame to export (default is the last frame in your project to contain a clip) - ================== ============ + ======================= ============ + Advanced Setting Description + ======================= ============ + Export To Export both `video & audio`, `only audio`, `only video`, or an `image sequence` + Start Frame The first frame to export (default is 1) + End Frame The final frame to export (default is the last frame in your project to contain a clip) + Export Entire Timeline Export all frames in your project (which can extend past your final clip). The project duration can be adjusted by dragging the right edge of the tracks. Checking this option will update the End Frame in this dialog. + ======================= ============ Profile ^^^^^^^ diff --git a/src/language/OpenShot/OpenShot.pot b/src/language/OpenShot/OpenShot.pot index 716e5ff90..d36be1dc2 100644 --- a/src/language/OpenShot/OpenShot.pot +++ b/src/language/OpenShot/OpenShot.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: OpenShot Video Editor (version: 3.2.1-dev)\n" "Report-Msgid-Bugs-To: Jonathan Thomas \n" -"POT-Creation-Date: 2024-09-17 12:16:02.880382\n" +"POT-Creation-Date: 2024-09-24 13:45:59.746176\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jonathan Thomas \n" "Language-Team: https://translations.launchpad.net/+groups/launchpad-translators\n" @@ -48,9 +48,9 @@ msgid "Reset Zoom" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:91 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:850 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:861 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1141 Settings for +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:855 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:866 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1146 Settings for #: actionExportVideo #: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:20 msgid "Export Video" @@ -78,40 +78,40 @@ msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:161 #: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:658 #: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:756 -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2559 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2562 #: /home/jonathan/apps/openshot-qt/src/classes/exporters/edl.py:56 #: /home/jonathan/apps/openshot-qt/src/classes/exporters/final_cut_pro.py:90 msgid "Untitled Project" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:515 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:858 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:944 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:958 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:520 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:963 msgid "Video & Audio" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:515 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:858 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:944 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:520 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 msgid "Video Only" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:516 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:858 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:958 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:972 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:521 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:963 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:977 msgid "Audio Only" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:516 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:818 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:894 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:944 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:521 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:823 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:899 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 msgid "Image Sequence" msgstr "" @@ -145,69 +145,69 @@ msgstr "" msgid "Surround (7.1 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:259 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:260 #: /home/jonathan/apps/openshot-qt/src/presets/format_mp4_x264_hw.xml msgid "All Formats" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:422 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:427 #: /home/jonathan/apps/openshot-qt/src/presets/format_mp4_x264.xml msgid "MP4 (h.264)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:452 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:457 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:161 #: libopenshot (Clip Properties) msgid "No" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:453 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:458 msgid "Yes Top field first" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:454 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:459 msgid "Yes Bottom field first" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:529 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:537 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:605 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:610 msgid "Low" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:529 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:537 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:607 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:612 msgid "Med" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:529 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:537 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:609 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:614 msgid "High" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:671 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:676 msgid "Choose a Folder..." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:798 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1098 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:803 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1103 msgid "Export Error" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:799 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:804 msgid "Sorry, please select a valid range of frames to export" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:851 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:856 #, python-format msgid "" "%s is an input file.\n" "Please choose a different name." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:862 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:867 #: /home/jonathan/apps/openshot-qt/src/windows/title_editor.py:716 #, python-format msgid "" @@ -215,18 +215,18 @@ msgid "" "Do you want to replace it?" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1026 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1031 msgid "Finalizing video export, please wait..." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1099 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1104 #, python-format msgid "" "Sorry, there was an error exporting your video: \n" "%s" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1142 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1147 msgid "Are you sure you want to cancel the export?" msgstr "" @@ -1061,7 +1061,7 @@ msgid "" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/preferences.py:266 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1004 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1019 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:117 msgid "Browse..." msgstr "" @@ -1253,51 +1253,51 @@ msgstr "" msgid "Track Name:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2318 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2321 msgid "Docks" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2512 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2515 msgid "Enter caption text..." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2730 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2733 msgid "Recent Projects" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2739 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2742 msgid "No Recent Projects" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2795 -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2811 -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2829 -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2839 -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3407 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2798 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2814 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2832 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2842 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3410 #: /home/jonathan/apps/openshot-qt/src/windows/ui/main-window.ui:432 msgid "Filter" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2854 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2857 msgid "Caption Toolbar" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2926 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2929 #: /home/jonathan/apps/openshot-qt/src/windows/ui/main-window.ui:1452 #: /home/jonathan/apps/openshot-qt/src/windows/ui/main-window.ui:1455 msgid "Update Available" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2927 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2930 #, python-format msgid "Update Available: %s" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3364 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3367 msgid "Error starting local HTTP server" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3365 +#: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:3368 msgid "Failed multiple attempts to start server:" msgstr "" @@ -1315,7 +1315,7 @@ msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/models/titles_model.py:91 #: /home/jonathan/apps/openshot-qt/src/windows/models/emoji_model.py:77 #: /home/jonathan/apps/openshot-qt/src/windows/models/effects_model.py:102 -#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:177 +#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:184 msgid "Name" msgstr "" @@ -1435,16 +1435,16 @@ msgstr "" msgid "SAR" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:177 +#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:184 msgid "Tags" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:366 +#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:377 #, python-format msgid "Importing %(count)d / %(total)d" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:389 +#: /home/jonathan/apps/openshot-qt/src/windows/models/files_model.py:410 #, python-format msgid "Imported %(count)d files" msgstr "" @@ -1560,7 +1560,7 @@ msgid "" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/title_editor.py:332 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:970 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:985 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:43 msgid "File Name:" msgstr "" @@ -3013,6 +3013,10 @@ msgstr "" msgid "Undo" msgstr "" +#: Settings for actionZoomToTimeline +msgid "Zoom to Timeline" +msgstr "" + #: Settings for seekPreviousFrame msgid "Previous Frame" msgstr "" @@ -3386,19 +3390,19 @@ msgid "YourAnimation" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:165 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:606 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:613 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:386 msgid "Frame Rate:" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:235 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:442 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:449 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:468 msgid "Width:" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:273 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:475 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:482 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:222 msgid "Height:" msgstr "" @@ -3479,7 +3483,7 @@ msgid "Select a Profile to start:" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:107 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:415 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:422 msgid "Profile:" msgstr "" @@ -3521,84 +3525,88 @@ msgstr "" msgid "End Frame:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:401 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:384 +msgid "Export Entire Timeline" +msgstr "" + +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:408 msgid "Profile" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:508 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:515 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:264 msgid "Aspect Ratio:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:557 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:564 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:325 msgid "Pixel Ratio:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:658 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:665 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:438 msgid "Interlaced:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:686 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:693 msgid "Image Sequence Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:700 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:707 msgid "Image Format:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:721 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:728 msgid "Video Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:735 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:742 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:525 msgid "Video Format:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:755 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:762 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:555 msgid "Video Codec:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:778 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:921 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:785 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:936 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:588 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:711 msgid "Bit Rate / Quality:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:791 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:806 msgid "Audio Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:805 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:820 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:681 msgid "Audio Codec:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:825 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:840 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:639 msgid "Sample Rate:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:858 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:873 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:747 msgid "# of Channels:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:894 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:909 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:792 msgid "Channel Layout:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:977 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:992 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:50 msgid "YourVideoName" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:994 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1009 msgid "Folder Path:" msgstr "" diff --git a/src/windows/export.py b/src/windows/export.py index 55aeda5b3..f1f948ed3 100644 --- a/src/windows/export.py +++ b/src/windows/export.py @@ -47,7 +47,7 @@ QMessageBox, QDialog, QFileDialog, QDialogButtonBox, QPushButton ) from PyQt5.QtGui import QIcon - +from functools import partial from classes import info from classes import ui_util from classes import openshot_rc # noqa @@ -200,6 +200,7 @@ def __init__(self, *args, **kwargs): self.cboChannelLayout.currentIndexChanged.connect(self.updateChannels) self.ExportFrame.connect(self.updateProgressBar) self.btnBrowseProfiles.clicked.connect(self.btnBrowseProfiles_clicked) + self.checkboxExportEntireTimeline.toggled.connect(partial(self.updateFrameRate, True)) # ********* Advanced Profile List ********** # Loop through profiles @@ -344,8 +345,12 @@ def updateFrameRate(self, set_limits=True): self.timeline.info.has_audio = True if set_limits: - # Determine max frame (based on clips) - self.timeline_length_int = self.timeline.GetMaxFrame() + if self.checkboxExportEntireTimeline.isChecked(): + # Set the end frame to the full timeline length if the checkbox is checked + self.timeline_length_int = self.timeline.info.video_length + else: + # Set the end frame to the furthest right clip edge (existing behavior) + self.timeline_length_int = self.timeline.GetMaxFrame() # Set the min and max frame numbers for this project self.txtStartFrame.setValue(1) diff --git a/src/windows/ui/export.ui b/src/windows/ui/export.ui index 086efc1c1..94a9b8831 100644 --- a/src/windows/ui/export.ui +++ b/src/windows/ui/export.ui @@ -277,7 +277,7 @@ 0 0 480 - 122 + 119 @@ -378,6 +378,13 @@ + + + + Export Entire Timeline + + + @@ -387,8 +394,8 @@ 0 0 - 466 - 269 + 263 + 262 @@ -678,8 +685,8 @@ 0 0 - 480 - 68 + 151 + 47 @@ -713,8 +720,8 @@ 0 0 - 480 - 120 + 151 + 117 @@ -787,6 +794,14 @@ + + + 0 + 0 + 212 + 189 + + Audio Settings From 70c59e5e87304bb57c974f1477f2618e017cc0f9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 30 Sep 2024 14:53:48 -0500 Subject: [PATCH 14/17] Adding export settings to a project, so the export dialog will always use the last entered settings. It is saved/loaded in a project. Also adding a "Restore Defaults" button to reset the export dialog. --- src/classes/timeline.py | 3 +- src/settings/_default.project | 1 + src/windows/export.py | 89 ++++++++++++++++++++++++++++++++++- src/windows/preview_thread.py | 3 +- src/windows/ui/export.ui | 35 ++++++++++++-- 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/classes/timeline.py b/src/classes/timeline.py index aaf8267cd..6f2b9ce66 100644 --- a/src/classes/timeline.py +++ b/src/classes/timeline.py @@ -74,8 +74,7 @@ def changed(self, action): """ This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) """ # Ignore changes that don't affect libopenshot - if action and len(action.key) >= 1 and action.key[0].lower() in ["files", "history", "markers", - "layers", "scale", "profile"]: + if action and len(action.key) >= 1 and action.key[0].lower() in ["files", "history", "markers", "layers", "scale", "profile", "export_settings"]: return # Disable video caching temporarily diff --git a/src/settings/_default.project b/src/settings/_default.project index dbdb60cc4..72f288a7d 100644 --- a/src/settings/_default.project +++ b/src/settings/_default.project @@ -26,6 +26,7 @@ "tick_pixels": 100, "playhead_position": 0, "profile": "HD 720p 30 fps", + "export_settings": null, "layers": [ { "id": "L1", diff --git a/src/windows/export.py b/src/windows/export.py index f1f948ed3..5eee48ba5 100644 --- a/src/windows/export.py +++ b/src/windows/export.py @@ -44,7 +44,7 @@ from PyQt5.QtCore import Qt, QCoreApplication, QTimer, QSize, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( - QMessageBox, QDialog, QFileDialog, QDialogButtonBox, QPushButton + QMessageBox, QDialog, QFileDialog, QDialogButtonBox, QPushButton, QWidget, QLineEdit, QComboBox, QSpinBox, QCheckBox ) from PyQt5.QtGui import QIcon from functools import partial @@ -91,6 +91,9 @@ def __init__(self, *args, **kwargs): self.export_button = QPushButton(_('Export Video')) self.export_button.setObjectName("acceptButton") self.close_button = QPushButton(_('Done')) + self.restoring_defaults = False + self.restore_defaults_button.clicked.connect(self.restore_defaults) + self.buttonBox.addButton(self.close_button, QDialogButtonBox.RejectRole) self.buttonBox.addButton(self.export_button, QDialogButtonBox.AcceptRole) self.buttonBox.addButton(self.cancel_button, QDialogButtonBox.RejectRole) @@ -284,6 +287,27 @@ def __init__(self, *args, **kwargs): # Determine the length of the timeline (in frames) self.updateFrameRate() + # Load previous settings (if any) + self.load_settings() + + def restore_defaults(self): + """ + Restore defaults by closing and reopening the dialog. + """ + # Clear the saved settings + get_app().updates.ignore_history = True + get_app().updates.update(["export_settings"], None) + get_app().updates.ignore_history = False + + log.info("Cleared last-export_settings.") + + # Close the current dialog + self.restoring_defaults = True + self.close() + + # Re-open the export dialog (calling the dialog anew) + QTimer.singleShot(0, get_app().window.actionExportVideo.trigger) + def getProfilePath(self, profile_name): """Get the profile path that matches the name""" for profile, path in self.profile_paths.items(): @@ -774,6 +798,8 @@ def enableControls(self): def accept(self): """ Start exporting video """ + # Save export settings + self.save_settings() # Build the export window title def titlestring(sec, fps, mess): @@ -1137,7 +1163,68 @@ def titlestring(sec, fps, mess): # Accept dialog super(Export, self).accept() + def save_settings(self): + if self.restoring_defaults: + return # Ignore saving if we are actively restoring defaults + + # Create a list to store the settings in order + settings = [] + + # Iterate over all children in the dialog in the order they are defined + for child in self.findChildren(QWidget): + setting = {} + if isinstance(child, QLineEdit): + setting['name'] = child.objectName() + setting['type'] = 'QLineEdit' + setting['value'] = child.text() + elif isinstance(child, QComboBox): + setting['name'] = child.objectName() + setting['type'] = 'QComboBox' + setting['value'] = child.currentIndex() + elif isinstance(child, QSpinBox): + setting['name'] = child.objectName() + setting['type'] = 'QSpinBox' + setting['value'] = child.value() + elif isinstance(child, QCheckBox): + setting['name'] = child.objectName() + setting['type'] = 'QCheckBox' + setting['value'] = child.isChecked() + # Append the setting to the list + if setting: + settings.append(setting) + + # Save all settings as a JSON string + get_app().updates.ignore_history = True + get_app().updates.update(["export_settings"], settings) + get_app().updates.ignore_history = False + + log.info("Export settings saved: %s", settings) + + def load_settings(self): + # Load the JSON string from settings + settings = get_app().project.get("export_settings") + + if not settings: + log.info("No saved settings found.") + return + + # Iterate over the list of settings and apply them in order + for setting in settings: + widget = self.findChild(QWidget, setting['name']) + if widget: + if setting['type'] == 'QLineEdit': + widget.setText(setting.get('value', '')) + elif setting['type'] == 'QComboBox': + widget.setCurrentIndex(setting.get('value', 0)) + elif setting['type'] in ['QSpinBox', 'QDoubleSpinBox']: + widget.setValue(setting.get('value', widget.minimum())) + elif setting['type'] == 'QCheckBox': + widget.setChecked(setting.get('value', False)) + log.info("Export settings loaded: %s", settings) + def reject(self): + self.save_settings() + if self.exporting and not self.close_button.isVisible(): # Show confirmation dialog _ = get_app()._tr diff --git a/src/windows/preview_thread.py b/src/windows/preview_thread.py index 3988e7432..d7823af80 100644 --- a/src/windows/preview_thread.py +++ b/src/windows/preview_thread.py @@ -45,8 +45,7 @@ def changed(self, action): """ This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) """ # Ignore changes that don't affect libopenshot - if action and len(action.key) >= 1 and action.key[0].lower() in ["files", "history", "markers", "layers", - "scale", "profile", "sample_rate"]: + if action and len(action.key) >= 1 and action.key[0].lower() in ["files", "history", "markers", "layers", "scale", "profile", "sample_rate", "export_settings"]: return try: diff --git a/src/windows/ui/export.ui b/src/windows/ui/export.ui index 94a9b8831..b36a510b0 100644 --- a/src/windows/ui/export.ui +++ b/src/windows/ui/export.ui @@ -41,11 +41,36 @@ - - - QDialogButtonBox::NoButton - - + + + + + + Restore Defaults + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::NoButton + + + + From 39be79ec9d00179363efbf4d7d1f50852b542386 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 30 Sep 2024 16:20:23 -0500 Subject: [PATCH 15/17] Rename "Export Entire Timeline" option to "End at Last Clip", and add a similar "Start at first Clip" option. Removed internal QLineEdits from save_settings (i.e. children of spinners) --- src/windows/export.py | 26 ++++++++++----- src/windows/ui/export.ui | 71 ++++++++++++++++++++++------------------ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/windows/export.py b/src/windows/export.py index 5eee48ba5..b5e2b1c28 100644 --- a/src/windows/export.py +++ b/src/windows/export.py @@ -203,7 +203,8 @@ def __init__(self, *args, **kwargs): self.cboChannelLayout.currentIndexChanged.connect(self.updateChannels) self.ExportFrame.connect(self.updateProgressBar) self.btnBrowseProfiles.clicked.connect(self.btnBrowseProfiles_clicked) - self.checkboxExportEntireTimeline.toggled.connect(partial(self.updateFrameRate, True)) + self.checkStartFirstClip.toggled.connect(partial(self.updateFrameRate, True)) + self.checkEndLastClip.toggled.connect(partial(self.updateFrameRate, True)) # ********* Advanced Profile List ********** # Loop through profiles @@ -369,16 +370,23 @@ def updateFrameRate(self, set_limits=True): self.timeline.info.has_audio = True if set_limits: - if self.checkboxExportEntireTimeline.isChecked(): - # Set the end frame to the full timeline length if the checkbox is checked - self.timeline_length_int = self.timeline.info.video_length + if self.checkEndLastClip.isChecked(): + # Set end frame to last clip (right edge) + timeline_length_int = self.timeline.GetMaxFrame() else: - # Set the end frame to the furthest right clip edge (existing behavior) - self.timeline_length_int = self.timeline.GetMaxFrame() + # Set end frame to project length (full timeline) + timeline_length_int = self.timeline.info.video_length + + if self.checkStartFirstClip.isChecked(): + # Set the start frame to the first clip position + timeline_start_int = self.timeline.GetMinFrame() + else: + # Set the start frame to the start of the project (0.0) + timeline_start_int = 1 # Set the min and max frame numbers for this project - self.txtStartFrame.setValue(1) - self.txtEndFrame.setValue(self.timeline_length_int) + self.txtStartFrame.setValue(timeline_start_int) + self.txtEndFrame.setValue(timeline_length_int) # Calculate differences between editing/preview FPS and export FPS current_fps = get_app().project.get("fps") @@ -1172,6 +1180,8 @@ def save_settings(self): # Iterate over all children in the dialog in the order they are defined for child in self.findChildren(QWidget): + if child.objectName().startswith("qt_"): + continue setting = {} if isinstance(child, QLineEdit): setting['name'] = child.objectName() diff --git a/src/windows/ui/export.ui b/src/windows/ui/export.ui index b36a510b0..5eab14f8f 100644 --- a/src/windows/ui/export.ui +++ b/src/windows/ui/export.ui @@ -40,36 +40,35 @@ - - + - - - - Restore Defaults - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QDialogButtonBox::NoButton - - - + + + + Restore Defaults + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::NoButton + + + @@ -370,6 +369,13 @@ + + + + Start at first Clip + + + @@ -404,9 +410,12 @@ - + - Export Entire Timeline + End at Last Clip + + + true From 6dc43641572277853b036de4c9e16b307faaa81b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 30 Sep 2024 16:39:35 -0500 Subject: [PATCH 16/17] Update translations for new checkboxes on Export dialog, and update Export.rst documentation. --- doc/export.rst | 3 +- src/language/OpenShot/OpenShot.pot | 196 +++++++++++++++-------------- src/windows/ui/export.ui | 2 +- 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/doc/export.rst b/doc/export.rst index c642cbadf..2e8e0e9f3 100644 --- a/doc/export.rst +++ b/doc/export.rst @@ -71,7 +71,8 @@ Advanced Options Export To Export both `video & audio`, `only audio`, `only video`, or an `image sequence` Start Frame The first frame to export (default is 1) End Frame The final frame to export (default is the last frame in your project to contain a clip) - Export Entire Timeline Export all frames in your project (which can extend past your final clip). The project duration can be adjusted by dragging the right edge of the tracks. Checking this option will update the End Frame in this dialog. + Start at First Clip This checkbox will toggle the **Start Frame** between `0.0` and the `start` of the first clip/transition position. + End at Last Clip This checkbox will toggle the **End Frame** between the `end` of the furthest clip/transition and the full `project duration`. The project duration can be adjusted by dragging the right edge of any track. You will need to zoom out (:guilabel:`Ctrl+Scroll Wheel`) of the timeline before you can drag the right edge of a track. ======================= ============ Profile diff --git a/src/language/OpenShot/OpenShot.pot b/src/language/OpenShot/OpenShot.pot index d36be1dc2..0b3224f40 100644 --- a/src/language/OpenShot/OpenShot.pot +++ b/src/language/OpenShot/OpenShot.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: OpenShot Video Editor (version: 3.2.1-dev)\n" "Report-Msgid-Bugs-To: Jonathan Thomas \n" -"POT-Creation-Date: 2024-09-24 13:45:59.746176\n" +"POT-Creation-Date: 2024-09-30 16:30:18.199948\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Jonathan Thomas \n" "Language-Team: https://translations.launchpad.net/+groups/launchpad-translators\n" @@ -48,9 +48,9 @@ msgid "Reset Zoom" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/export.py:91 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:855 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:866 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1146 Settings for +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:889 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:900 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1243 Settings for #: actionExportVideo #: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:20 msgid "Export Video" @@ -62,11 +62,11 @@ msgstr "" msgid "Done" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:144 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:147 msgid "Project Data Error" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:145 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:148 #, python-format msgid "" "Sorry, an error was encountered while parsing your project data: \n" @@ -75,7 +75,7 @@ msgid "" "Please save your project and inspect in a JSON editor to repair." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:161 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:164 #: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:658 #: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:756 #: /home/jonathan/apps/openshot-qt/src/windows/main_window.py:2562 @@ -84,130 +84,130 @@ msgstr "" msgid "Untitled Project" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:520 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:963 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:176 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:552 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:897 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:983 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:997 msgid "Video & Audio" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:520 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:176 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:552 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:897 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:983 msgid "Video Only" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:521 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:863 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:963 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:977 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:176 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:553 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:897 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:997 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1011 msgid "Audio Only" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:173 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:521 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:823 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:899 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:949 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:176 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:553 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:857 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:933 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:983 msgid "Image Sequence" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:180 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:183 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:143 Settings #: for default-channellayout msgid "Mono (1 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:181 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:184 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:144 Settings #: for default-channellayout msgid "Stereo (2 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:182 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:185 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:145 Settings #: for default-channellayout msgid "Surround (3 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:183 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:186 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:146 Settings #: for default-channellayout msgid "Surround (5.1 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:184 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:187 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:147 Settings #: for default-channellayout msgid "Surround (7.1 Channel)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:260 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:264 #: /home/jonathan/apps/openshot-qt/src/presets/format_mp4_x264_hw.xml msgid "All Formats" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:427 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:459 #: /home/jonathan/apps/openshot-qt/src/presets/format_mp4_x264.xml msgid "MP4 (h.264)" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:457 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:489 #: /home/jonathan/apps/openshot-qt/src/windows/file_properties.py:161 #: libopenshot (Clip Properties) msgid "No" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:458 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:490 msgid "Yes Top field first" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:459 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:491 msgid "Yes Bottom field first" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:610 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:566 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:574 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:642 msgid "Low" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:612 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:566 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:574 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:644 msgid "Med" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:534 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:542 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:614 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:566 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:574 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:646 msgid "High" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:676 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:708 msgid "Choose a Folder..." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:803 -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1103 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:837 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1137 msgid "Export Error" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:804 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:838 msgid "Sorry, please select a valid range of frames to export" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:856 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:890 #, python-format msgid "" "%s is an input file.\n" "Please choose a different name." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:867 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:901 #: /home/jonathan/apps/openshot-qt/src/windows/title_editor.py:716 #, python-format msgid "" @@ -215,18 +215,18 @@ msgid "" "Do you want to replace it?" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1031 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1065 msgid "Finalizing video export, please wait..." msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1104 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1138 #, python-format msgid "" "Sorry, there was an error exporting your video: \n" "%s" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1147 +#: /home/jonathan/apps/openshot-qt/src/windows/export.py:1244 msgid "Are you sure you want to cancel the export?" msgstr "" @@ -1061,13 +1061,13 @@ msgid "" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/preferences.py:266 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1019 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1053 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:117 msgid "Browse..." msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/preferences.py:305 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:179 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:203 msgid "Search Profiles" msgstr "" @@ -1089,7 +1089,7 @@ msgid "Select executable file" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/preferences.py:721 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/preferences.ui:48 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:48 msgid "Restore Defaults" msgstr "" @@ -1548,11 +1548,11 @@ msgstr "" msgid "Please choose a region at the beginning of the clip" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/preview_thread.py:95 +#: /home/jonathan/apps/openshot-qt/src/windows/preview_thread.py:94 msgid "Audio Error" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/preview_thread.py:95 +#: /home/jonathan/apps/openshot-qt/src/windows/preview_thread.py:94 #, python-format msgid "" "Please fix the following error and restart OpenShot\n" @@ -1560,7 +1560,7 @@ msgid "" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/title_editor.py:332 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:985 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1019 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:43 msgid "File Name:" msgstr "" @@ -3390,19 +3390,19 @@ msgid "YourAnimation" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:165 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:613 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:647 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:386 msgid "Frame Rate:" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:235 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:449 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:483 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:468 msgid "Width:" msgstr "" #: /home/jonathan/apps/openshot-qt/src/windows/ui/animation.ui:273 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:482 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:516 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:222 msgid "Height:" msgstr "" @@ -3474,139 +3474,143 @@ msgstr "" msgid "Play" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:69 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:93 msgid "Simple" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:83 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:107 msgid "Select a Profile to start:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:107 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:422 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:131 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:456 msgid "Profile:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:128 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:152 msgid "Select from the following options:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:156 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:180 msgid "Target:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:201 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:225 msgid "Video Profile:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:218 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:242 msgid "Quality:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:251 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:275 msgid "Advanced" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:284 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:308 msgid "Advanced Options" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:301 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:325 msgid "Export To:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:328 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:352 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:876 msgid "Start Frame:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:361 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:375 +msgid "Start at First Clip" +msgstr "" + +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:392 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:837 msgid "End Frame:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:384 -msgid "Export Entire Timeline" +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:415 +msgid "End at Last Clip" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:408 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:442 msgid "Profile" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:515 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:549 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:264 msgid "Aspect Ratio:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:564 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:598 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:325 msgid "Pixel Ratio:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:665 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:699 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:438 msgid "Interlaced:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:693 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:727 msgid "Image Sequence Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:707 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:741 msgid "Image Format:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:728 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:762 msgid "Video Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:742 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:776 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:525 msgid "Video Format:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:762 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:796 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:555 msgid "Video Codec:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:785 -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:936 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:819 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:970 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:588 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:711 msgid "Bit Rate / Quality:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:806 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:840 msgid "Audio Settings" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:820 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:854 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:681 msgid "Audio Codec:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:840 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:874 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:639 msgid "Sample Rate:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:873 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:907 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:747 msgid "# of Channels:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:909 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:943 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:792 msgid "Channel Layout:" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:992 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1026 #: /home/jonathan/apps/openshot-qt/src/windows/ui/file-properties.ui:50 msgid "YourVideoName" msgstr "" -#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1009 +#: /home/jonathan/apps/openshot-qt/src/windows/ui/export.ui:1043 msgid "Folder Path:" msgstr "" diff --git a/src/windows/ui/export.ui b/src/windows/ui/export.ui index 5eab14f8f..996116f61 100644 --- a/src/windows/ui/export.ui +++ b/src/windows/ui/export.ui @@ -372,7 +372,7 @@ - Start at first Clip + Start at First Clip From a2051cab57dede443e7c7b410a12e3219cf4e2a2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 30 Sep 2024 17:49:34 -0500 Subject: [PATCH 17/17] Fixing Codacy nitpicks in JS --- src/timeline/js/directives/ruler.js | 1 - src/timeline/js/directives/track.js | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/timeline/js/directives/ruler.js b/src/timeline/js/directives/ruler.js index 03e8bce60..01231ec7c 100644 --- a/src/timeline/js/directives/ruler.js +++ b/src/timeline/js/directives/ruler.js @@ -97,7 +97,6 @@ App.directive("tlScrollableTracks", function () { // Pans the timeline (on middle mouse click and drag) element.on("mousemove", function (e) { if (is_scrolling) { - console.log("scope.getTimelineWidth(0): " + scope.getTimelineWidth(0)); var difference = {x: starting_mouse_position.x - e.pageX, y: starting_mouse_position.y - e.pageY}; var newPos = { x: Math.max(0, Math.min(starting_scrollbar.x + difference.x, scope.getTimelineWidth(0) - element.width())), diff --git a/src/timeline/js/directives/track.js b/src/timeline/js/directives/track.js index 45c815353..35ac4f341 100644 --- a/src/timeline/js/directives/track.js +++ b/src/timeline/js/directives/track.js @@ -25,10 +25,10 @@ * along with OpenShot Library. If not, see . */ - -App.directive('tlTrack', function () { +/* global App, timeline, snapToFPSGridTime pixelToTime */ +App.directive("tlTrack", function () { return { - restrict: 'A', + restrict: "A", link: function (scope, element) { var startX, startWidth, isResizing = false, newDuration, minimumWidth; @@ -39,7 +39,7 @@ App.directive('tlTrack', function () { }; // Delegate the mousedown event to the parent element for dynamically created resize-handle - element.on('mousedown', '.track-resize-handle', function(event) { + element.on("mousedown", ".track-resize-handle", function(event) { // Start resizing logic isResizing = true; startX = event.pageX; @@ -49,8 +49,8 @@ App.directive('tlTrack', function () { minimumWidth = getFurthestRightEdge() * scope.pixelsPerSecond; // Attach document-wide mousemove and mouseup events - $(document).on('mousemove', resizeTrack); - $(document).on('mouseup', stopResizing); + $(document).on("mousemove", resizeTrack); + $(document).on("mouseup", stopResizing); event.preventDefault(); }); @@ -75,12 +75,12 @@ App.directive('tlTrack', function () { // Function to stop resizing when the mouse button is released function stopResizing() { - if (!isResizing) return; + if (!isResizing) {return;} isResizing = false; // Clean up the document-wide event listeners - $(document).off('mousemove', resizeTrack); - $(document).off('mouseup', stopResizing); + $(document).off("mousemove", resizeTrack); + $(document).off("mouseup", stopResizing); // Finalize the new duration on the timeline (if valid) if (newDuration !== null) {