Skip to content

Commit 4d57ec6

Browse files
committed
Large refactor of selectClip / selectTransition in JS, to allow for SHIFT+Click (ripple select), and added new keyboard shortcut for ripple select.
1 parent a1053bb commit 4d57ec6

File tree

5 files changed

+110
-96
lines changed

5 files changed

+110
-96
lines changed

doc/main_window.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Save Current Frame :kbd:`Ctrl+Shift+Y`
170170
Save Project :kbd:`Ctrl+S`
171171
Save Project As... :kbd:`Ctrl+Shift+S`
172172
Select All :kbd:`Ctrl+A`
173+
Select Item (Ripple) :kbd:`Ctrl+Alt+A` :kbd:`Shift+Click`
173174
Select None :kbd:`Ctrl+Shift+A`
174175
Show All Docks :kbd:`Ctrl+Shift+D`
175176
Simple View :kbd:`Alt+Shift+0`

src/settings/_default.settings

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,11 +1144,11 @@
11441144
},
11451145
{
11461146
"category": "Keyboard",
1147-
"title": "Ripple Select",
1147+
"title": "Select Item (Ripple)",
11481148
"restart": false,
11491149
"setting": "actionRippleSelect",
11501150
"value": "Ctrl+Alt+A",
1151-
"type": "hidden"
1151+
"type": "text"
11521152
},
11531153
{
11541154
"category": "Keyboard",

src/timeline/js/controllers.js

Lines changed: 90 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -578,110 +578,114 @@ App.controller("TimelineCtrl", function ($scope) {
578578
});
579579
};
580580

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

586-
// Is CTRL pressed?
586+
// Check for modifier keys
587587
var is_ctrl = event && event.ctrlKey;
588-
589-
// Clear transitions selection if needed
590-
if (id !== "" && clear_selections && !is_ctrl) {
591-
$scope.selectTransition("", true);
588+
var is_shift = event && event.shiftKey;
589+
590+
// If no ID is provided (id == ""), unselect all items
591+
if (id === "") {
592+
if (clear_selections) {
593+
// Unselect all clips
594+
$scope.project.clips.forEach(function (clip) {
595+
clip.selected = false;
596+
if ($scope.Qt) timeline.removeSelection(clip.id, "clip");
597+
});
598+
// Unselect all transitions
599+
$scope.project.effects.forEach(function (transition) {
600+
transition.selected = false;
601+
if ($scope.Qt) timeline.removeSelection(transition.id, "transition");
602+
});
603+
}
604+
return; // Exit after clearing all selections
592605
}
593-
// Call slice method and exit (don't actually select the clip)
594-
if (id !== "" && $scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {
606+
607+
// Razor mode check
608+
if ($scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {
595609
var cursor_seconds = $scope.getJavaScriptPosition(event.clientX, null).position;
596-
timeline.RazorSliceAtCursor(id, "", cursor_seconds);
610+
timeline.RazorSliceAtCursor(item_type === "clip" ? id : "", item_type === "transition" ? id : "", cursor_seconds);
611+
return; // Don't select if razor mode is enabled
612+
}
597613

598-
// Don't actually select clip
599-
return;
614+
// Clear all selections if necessary (no CTRL modifier)
615+
if (clear_selections && !is_ctrl) {
616+
// Unselect all clips
617+
$scope.project.clips.forEach(function (clip) {
618+
clip.selected = false;
619+
if ($scope.Qt) timeline.removeSelection(clip.id, "clip");
620+
});
621+
// Unselect all transitions
622+
$scope.project.effects.forEach(function (transition) {
623+
transition.selected = false;
624+
if ($scope.Qt) timeline.removeSelection(transition.id, "transition");
625+
});
600626
}
601627

602-
// Update selection for clips
603-
for (var clip_index = 0; clip_index < $scope.project.clips.length; clip_index++) {
604-
if ($scope.project.clips[clip_index].id === id) {
605-
// Invert selection if CTRL is pressed and not forced add and already selected
606-
if (is_ctrl && clear_selections && ($scope.project.clips[clip_index].selected === true)) {
607-
$scope.project.clips[clip_index].selected = false;
608-
if ($scope.Qt) {
609-
timeline.removeSelection($scope.project.clips[clip_index].id, "clip");
628+
// Get the correct array based on item_type
629+
var items = item_type === "clip" ? $scope.project.clips : $scope.project.effects;
630+
631+
// Handle ripple selection (SHIFT key) for both clips and transitions
632+
if (is_shift || force_ripple) {
633+
var selected_item = items.find(item => item.id === id);
634+
if (selected_item) {
635+
var selected_layer = selected_item.layer;
636+
var selected_position = selected_item.position;
637+
638+
// Select all clips and transitions to the right on the same layer
639+
$scope.project.clips.forEach(function (clip) {
640+
if (clip.layer === selected_layer && clip.position >= selected_position) {
641+
clip.selected = true;
642+
if ($scope.Qt) timeline.addSelection(clip.id, "clip", false);
610643
}
611-
}
612-
else {
613-
$scope.project.clips[clip_index].selected = true;
614-
if ($scope.Qt) {
615-
// Do not clear selection if CTRL is pressed
616-
if (is_ctrl) {
617-
timeline.addSelection(id, "clip", false);
618-
}
619-
else {
620-
timeline.addSelection(id, "clip", clear_selections);
621-
}
644+
});
645+
$scope.project.effects.forEach(function (transition) {
646+
if (transition.layer === selected_layer && transition.position >= selected_position) {
647+
transition.selected = true;
648+
if ($scope.Qt) timeline.addSelection(transition.id, "transition", false);
622649
}
623-
}
650+
});
624651
}
625-
else if (clear_selections && !is_ctrl) {
626-
$scope.project.clips[clip_index].selected = false;
627-
if ($scope.Qt) {
628-
timeline.removeSelection($scope.project.clips[clip_index].id, "clip");
652+
return; // No need to do normal selection logic after ripple select
653+
}
654+
655+
// Update selection for clips or transitions
656+
for (var i = 0; i < items.length; i++) {
657+
var item = items[i];
658+
if (item.id === id) {
659+
// Invert selection if CTRL is pressed and item is already selected
660+
if (is_ctrl && clear_selections && item.selected) {
661+
item.selected = false;
662+
if ($scope.Qt) timeline.removeSelection(item.id, item_type);
663+
} else {
664+
item.selected = true;
665+
if ($scope.Qt) timeline.addSelection(item.id, item_type, !is_ctrl && clear_selections);
629666
}
630667
}
631668
}
632669
};
633670

634-
// Select transition in scope
635-
$scope.selectTransition = function (tran_id, clear_selections, event) {
636-
// Trim tran_id
637-
var id = tran_id.replace("transition_", "");
638-
639-
// Is CTRL pressed?
640-
var is_ctrl = event && event.ctrlKey;
671+
// Wrapper for ripple selecting clips
672+
$scope.selectClipRipple = function (clip_id, clear_selections, event) {
673+
$scope.selectItem(clip_id, "clip", clear_selections, event, true);
674+
};
641675

642-
// Clear clips selection if needed
643-
if (id !== "" && clear_selections && !is_ctrl) {
644-
$scope.selectClip("", true);
645-
}
646-
// Call slice method and exit (don't actually select the transition)
647-
if (id !== "" && $scope.enable_razor && $scope.Qt && typeof event !== 'undefined') {
648-
var cursor_seconds = $scope.getJavaScriptPosition(event.clientX, null).position;
649-
timeline.RazorSliceAtCursor("", id, cursor_seconds);
676+
// Wrapper for ripple selecting transitions
677+
$scope.selectTransitionRipple = function (tran_id, clear_selections, event) {
678+
$scope.selectItem(tran_id, "transition", clear_selections, event, true);
679+
};
650680

651-
// Don't actually select transition
652-
return;
653-
}
681+
// Wrapper for selecting clips
682+
$scope.selectClip = function (clip_id, clear_selections, event) {
683+
$scope.selectItem(clip_id, "clip", clear_selections, event, false);
684+
};
654685

655-
// Update selection for transitions
656-
for (var tran_index = 0; tran_index < $scope.project.effects.length; tran_index++) {
657-
if ($scope.project.effects[tran_index].id === id) {
658-
// Invert selection if CTRL is pressed and not forced add and already selected
659-
if (is_ctrl && clear_selections && ($scope.project.effects[tran_index].selected === true)) {
660-
$scope.project.effects[tran_index].selected = false;
661-
if ($scope.Qt) {
662-
timeline.removeSelection($scope.project.effects[tran_index].id, "transition");
663-
}
664-
}
665-
else {
666-
$scope.project.effects[tran_index].selected = true;
667-
if ($scope.Qt) {
668-
// Do not clear selection if CTRL is pressed
669-
if (is_ctrl) {
670-
timeline.addSelection(id, "transition", false);
671-
}
672-
else {
673-
timeline.addSelection(id, "transition", clear_selections);
674-
}
675-
}
676-
}
677-
}
678-
else if (clear_selections && !is_ctrl) {
679-
$scope.project.effects[tran_index].selected = false;
680-
if ($scope.Qt) {
681-
timeline.removeSelection($scope.project.effects[tran_index].id, "transition");
682-
}
683-
}
684-
}
686+
// Wrapper for selecting transitions
687+
$scope.selectTransition = function (tran_id, clear_selections, event) {
688+
$scope.selectItem(tran_id, "transition", clear_selections, event, false);
685689
};
686690

687691
// Format the thumbnail path: http://127.0.0.1:8081/thumbnails/FILE-ID/FRAME-NUMBER/

src/windows/main_window.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,25 +1969,28 @@ def ripple_delete_gap(self, ripple_start, layer, total_gap):
19691969
trans.data["position"] -= total_gap
19701970
trans.save()
19711971

1972-
# def actionInsertKeyframePosition_Triggered(self):
1972+
# def actionInsertKeyframePosition(self):
19731973
# """Insert a 'Location' / 'Position' keyframe"""
19741974
# log.info("Inserting keyframe for position")
19751975
#
1976-
# def actionInsertKeyframeScale_Triggered(self):
1976+
# def actionInsertKeyframeScale(self):
19771977
# """Insert a 'Scale' keyframe"""
19781978
# log.info("Inserting keyframe for scale")
19791979
#
1980-
# def actionInsertKeyframeRotation_Triggered(self):
1980+
# def actionInsertKeyframeRotation(self):
19811981
# """Insert a 'Rotation' keyframe"""
19821982
# log.info("Inserting keyframe for rotation")
19831983
#
1984-
# def actionInsertKeyframeAlpha_Triggered(self):
1984+
# def actionInsertKeyframeAlpha(self):
19851985
# """Insert an 'Alpha' keyframe"""
19861986
# log.info("Inserting keyframe for alpha (opacity)")
1987-
#
1988-
# def actionRippleSelect_Triggered(self):
1989-
# """Selects ALL clips or transitions to the right of the current selected item"""
1990-
# log.info("Selecting clips for ripple editing")
1987+
1988+
def actionRippleSelect(self):
1989+
"""Selects ALL clips or transitions to the right of the current selected item"""
1990+
for clip_id in self.selected_clips:
1991+
self.timeline.addRippleSelection(clip_id, "clip")
1992+
for tran_id in self.selected_transitions:
1993+
self.timeline.addRippleSelection(tran_id, "transition")
19911994

19921995
def actionRippleSliceKeepLeft(self):
19931996
"""Slice and keep the left side of a clip/transition, and then ripple the position change to the right."""

src/windows/views/timeline.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2928,6 +2928,12 @@ def addSelection(self, item_id, item_type, clear_existing=False):
29282928
""" Add the selected item to the current selection """
29292929
self.window.SelectionAdded.emit(item_id, item_type, clear_existing)
29302930

2931+
def addRippleSelection(self, item_id, item_type):
2932+
if item_type == "clip":
2933+
self.run_js(JS_SCOPE_SELECTOR + ".selectClipRipple('{}', false, null);".format(item_id))
2934+
elif item_type == "transition":
2935+
self.run_js(JS_SCOPE_SELECTOR + ".selectTransitionRipple('{}', false, null);".format(item_id))
2936+
29312937
@pyqtSlot(str, str)
29322938
def removeSelection(self, item_id, item_type):
29332939
""" Remove the selected clip from the selection """

0 commit comments

Comments
 (0)