Skip to content

Commit

Permalink
Large refactor of selectClip / selectTransition in JS, to allow for S…
Browse files Browse the repository at this point in the history
…HIFT+Click (ripple select), and added new keyboard shortcut for ripple select.
  • Loading branch information
jonoomph committed Sep 15, 2024
1 parent a1053bb commit 4d57ec6
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 96 deletions.
1 change: 1 addition & 0 deletions doc/main_window.rst
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ 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:`Ctrl+Alt+A` :kbd:`Shift+Click`
Select None :kbd:`Ctrl+Shift+A`
Show All Docks :kbd:`Ctrl+Shift+D`
Simple View :kbd:`Alt+Shift+0`
Expand Down
4 changes: 2 additions & 2 deletions src/settings/_default.settings
Original file line number Diff line number Diff line change
Expand Up @@ -1144,11 +1144,11 @@
},
{
"category": "Keyboard",
"title": "Ripple Select",
"title": "Select Item (Ripple)",
"restart": false,
"setting": "actionRippleSelect",
"value": "Ctrl+Alt+A",
"type": "hidden"
"type": "text"
},
{
"category": "Keyboard",
Expand Down
176 changes: 90 additions & 86 deletions src/timeline/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,110 +578,114 @@ App.controller("TimelineCtrl", function ($scope) {
});
};

// Select clip in scope
$scope.selectClip = function (clip_id, clear_selections, event) {
// Trim clip_id
var id = clip_id.replace("clip_", "");
// Select item (either clip or transition)
$scope.selectItem = function (item_id, item_type, clear_selections, event, force_ripple) {
// Trim item_id
var id = item_id.replace(`${item_type}_`, "");

// Is CTRL pressed?
// Check for modifier keys
var is_ctrl = event && event.ctrlKey;

// Clear transitions selection if needed
if (id !== "" && clear_selections && !is_ctrl) {
$scope.selectTransition("", true);
var is_shift = event && event.shiftKey;

// If no ID is provided (id == ""), unselect all items
if (id === "") {
if (clear_selections) {
// Unselect all clips
$scope.project.clips.forEach(function (clip) {
clip.selected = false;
if ($scope.Qt) timeline.removeSelection(clip.id, "clip");
});
// Unselect all transitions
$scope.project.effects.forEach(function (transition) {
transition.selected = false;
if ($scope.Qt) timeline.removeSelection(transition.id, "transition");
});
}
return; // Exit after clearing all selections
}
// Call slice method and exit (don't actually select the clip)
if (id !== "" && $scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {

// Razor mode check
if ($scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {
var cursor_seconds = $scope.getJavaScriptPosition(event.clientX, null).position;
timeline.RazorSliceAtCursor(id, "", cursor_seconds);
timeline.RazorSliceAtCursor(item_type === "clip" ? id : "", item_type === "transition" ? id : "", cursor_seconds);
return; // Don't select if razor mode is enabled
}

// Don't actually select clip
return;
// Clear all selections if necessary (no CTRL modifier)
if (clear_selections && !is_ctrl) {
// Unselect all clips
$scope.project.clips.forEach(function (clip) {
clip.selected = false;
if ($scope.Qt) timeline.removeSelection(clip.id, "clip");
});
// Unselect all transitions
$scope.project.effects.forEach(function (transition) {
transition.selected = false;
if ($scope.Qt) timeline.removeSelection(transition.id, "transition");
});
}

// Update selection for clips
for (var clip_index = 0; clip_index < $scope.project.clips.length; clip_index++) {
if ($scope.project.clips[clip_index].id === id) {
// Invert selection if CTRL is pressed and not forced add and already selected
if (is_ctrl && clear_selections && ($scope.project.clips[clip_index].selected === true)) {
$scope.project.clips[clip_index].selected = false;
if ($scope.Qt) {
timeline.removeSelection($scope.project.clips[clip_index].id, "clip");
// Get the correct array based on item_type
var items = item_type === "clip" ? $scope.project.clips : $scope.project.effects;

// Handle ripple selection (SHIFT key) for both clips and transitions
if (is_shift || force_ripple) {
var selected_item = items.find(item => item.id === id);
if (selected_item) {
var selected_layer = selected_item.layer;
var selected_position = selected_item.position;

// Select all clips and transitions to the right on the same layer
$scope.project.clips.forEach(function (clip) {
if (clip.layer === selected_layer && clip.position >= selected_position) {
clip.selected = true;
if ($scope.Qt) timeline.addSelection(clip.id, "clip", false);
}
}
else {
$scope.project.clips[clip_index].selected = true;
if ($scope.Qt) {
// Do not clear selection if CTRL is pressed
if (is_ctrl) {
timeline.addSelection(id, "clip", false);
}
else {
timeline.addSelection(id, "clip", clear_selections);
}
});
$scope.project.effects.forEach(function (transition) {
if (transition.layer === selected_layer && transition.position >= selected_position) {
transition.selected = true;
if ($scope.Qt) timeline.addSelection(transition.id, "transition", false);
}
}
});
}
else if (clear_selections && !is_ctrl) {
$scope.project.clips[clip_index].selected = false;
if ($scope.Qt) {
timeline.removeSelection($scope.project.clips[clip_index].id, "clip");
return; // No need to do normal selection logic after ripple select
}

// Update selection for clips or transitions
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.id === id) {
// Invert selection if CTRL is pressed and item is already selected
if (is_ctrl && clear_selections && item.selected) {
item.selected = false;
if ($scope.Qt) timeline.removeSelection(item.id, item_type);
} else {
item.selected = true;
if ($scope.Qt) timeline.addSelection(item.id, item_type, !is_ctrl && clear_selections);
}
}
}
};

// Select transition in scope
$scope.selectTransition = function (tran_id, clear_selections, event) {
// Trim tran_id
var id = tran_id.replace("transition_", "");

// Is CTRL pressed?
var is_ctrl = event && event.ctrlKey;
// Wrapper for ripple selecting clips
$scope.selectClipRipple = function (clip_id, clear_selections, event) {
$scope.selectItem(clip_id, "clip", clear_selections, event, true);
};

// Clear clips selection if needed
if (id !== "" && clear_selections && !is_ctrl) {
$scope.selectClip("", true);
}
// Call slice method and exit (don't actually select the transition)
if (id !== "" && $scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {
var cursor_seconds = $scope.getJavaScriptPosition(event.clientX, null).position;
timeline.RazorSliceAtCursor("", id, cursor_seconds);
// Wrapper for ripple selecting transitions
$scope.selectTransitionRipple = function (tran_id, clear_selections, event) {
$scope.selectItem(tran_id, "transition", clear_selections, event, true);
};

// Don't actually select transition
return;
}
// Wrapper for selecting clips
$scope.selectClip = function (clip_id, clear_selections, event) {
$scope.selectItem(clip_id, "clip", clear_selections, event, false);
};

// Update selection for transitions
for (var tran_index = 0; tran_index < $scope.project.effects.length; tran_index++) {
if ($scope.project.effects[tran_index].id === id) {
// Invert selection if CTRL is pressed and not forced add and already selected
if (is_ctrl && clear_selections && ($scope.project.effects[tran_index].selected === true)) {
$scope.project.effects[tran_index].selected = false;
if ($scope.Qt) {
timeline.removeSelection($scope.project.effects[tran_index].id, "transition");
}
}
else {
$scope.project.effects[tran_index].selected = true;
if ($scope.Qt) {
// Do not clear selection if CTRL is pressed
if (is_ctrl) {
timeline.addSelection(id, "transition", false);
}
else {
timeline.addSelection(id, "transition", clear_selections);
}
}
}
}
else if (clear_selections && !is_ctrl) {
$scope.project.effects[tran_index].selected = false;
if ($scope.Qt) {
timeline.removeSelection($scope.project.effects[tran_index].id, "transition");
}
}
}
// Wrapper for selecting transitions
$scope.selectTransition = function (tran_id, clear_selections, event) {
$scope.selectItem(tran_id, "transition", clear_selections, event, false);
};

// Format the thumbnail path: http://127.0.0.1:8081/thumbnails/FILE-ID/FRAME-NUMBER/
Expand Down
19 changes: 11 additions & 8 deletions src/windows/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -1969,25 +1969,28 @@ def ripple_delete_gap(self, ripple_start, layer, total_gap):
trans.data["position"] -= total_gap
trans.save()

# def actionInsertKeyframePosition_Triggered(self):
# def actionInsertKeyframePosition(self):
# """Insert a 'Location' / 'Position' keyframe"""
# log.info("Inserting keyframe for position")
#
# def actionInsertKeyframeScale_Triggered(self):
# def actionInsertKeyframeScale(self):
# """Insert a 'Scale' keyframe"""
# log.info("Inserting keyframe for scale")
#
# def actionInsertKeyframeRotation_Triggered(self):
# def actionInsertKeyframeRotation(self):
# """Insert a 'Rotation' keyframe"""
# log.info("Inserting keyframe for rotation")
#
# def actionInsertKeyframeAlpha_Triggered(self):
# def actionInsertKeyframeAlpha(self):
# """Insert an 'Alpha' keyframe"""
# log.info("Inserting keyframe for alpha (opacity)")
#
# def actionRippleSelect_Triggered(self):
# """Selects ALL clips or transitions to the right of the current selected item"""
# log.info("Selecting clips for ripple editing")

def actionRippleSelect(self):
"""Selects ALL clips or transitions to the right of the current selected item"""
for clip_id in self.selected_clips:
self.timeline.addRippleSelection(clip_id, "clip")
for tran_id in self.selected_transitions:
self.timeline.addRippleSelection(tran_id, "transition")

def actionRippleSliceKeepLeft(self):
"""Slice and keep the left side of a clip/transition, and then ripple the position change to the right."""
Expand Down
6 changes: 6 additions & 0 deletions src/windows/views/timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2928,6 +2928,12 @@ def addSelection(self, item_id, item_type, clear_existing=False):
""" Add the selected item to the current selection """
self.window.SelectionAdded.emit(item_id, item_type, clear_existing)

def addRippleSelection(self, item_id, item_type):
if item_type == "clip":
self.run_js(JS_SCOPE_SELECTOR + ".selectClipRipple('{}', false, null);".format(item_id))
elif item_type == "transition":
self.run_js(JS_SCOPE_SELECTOR + ".selectTransitionRipple('{}', false, null);".format(item_id))

@pyqtSlot(str, str)
def removeSelection(self, item_id, item_type):
""" Remove the selected clip from the selection """
Expand Down

0 comments on commit 4d57ec6

Please sign in to comment.