Skip to content

Commit edb3cd6

Browse files
committed
- Improved Paste_Triggered to always get the cursor and track position of the mouse (no longer uses playhead position)
- Improved Keybinds for Copy/Paste - so they work correctly with newly refactored methods
1 parent 9ecc892 commit edb3cd6

File tree

2 files changed

+99
-83
lines changed

2 files changed

+99
-83
lines changed

src/windows/main_window.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,9 +1852,9 @@ def keyPressEvent(self, event):
18521852
self.timeline.Slice_Triggered(MenuSlice.KEEP_RIGHT, clip_ids, trans_ids, playhead_position)
18531853

18541854
elif key.matches(self.getShortcutByName("copyAll")) == QKeySequence.ExactMatch:
1855-
self.timeline.Copy_Triggered(MenuCopy.ALL, self.selected_clips, self.selected_transitions)
1855+
self.timeline.Copy_Triggered(MenuCopy.ALL, self.selected_clips, self.selected_transitions, [])
18561856
elif key.matches(self.getShortcutByName("pasteAll")) == QKeySequence.ExactMatch:
1857-
self.timeline.Paste_Triggered(MenuCopy.PASTE, float(playhead_position), -1, [], [])
1857+
self.timeline.Paste_Triggered(MenuCopy.PASTE, self.selected_clips, self.selected_transitions)
18581858
elif key.matches(self.getShortcutByName("nudgeLeft")) == QKeySequence.ExactMatch:
18591859
self.timeline.Nudge_Triggered(-1, self.selected_clips, self.selected_transitions)
18601860
elif key.matches(self.getShortcutByName("nudgeRight")) == QKeySequence.ExactMatch:
@@ -3192,14 +3192,13 @@ def playToggle(self):
31923192

31933193
def copyAll(self):
31943194
"""Handle Copy QShortcut (selected clips / transitions)"""
3195-
self.timeline.Copy_Triggered(MenuCopy.ALL, self.selected_clips, self.selected_transitions)
3195+
self.timeline.Copy_Triggered(MenuCopy.ALL, self.selected_clips, self.selected_transitions, [])
31963196

31973197
def pasteAll(self):
31983198
"""Handle Paste QShortcut (at timeline position, same track as original clip)"""
31993199
fps = get_app().project.get("fps")
32003200
fps_float = float(fps["num"]) / float(fps["den"])
3201-
playhead_position = float(self.preview_thread.current_frame - 1) / fps_float
3202-
self.timeline.Paste_Triggered(MenuCopy.PASTE, float(playhead_position), -1, [], [])
3201+
self.timeline.Paste_Triggered(MenuCopy.PASTE, self.selected_clips, self.selected_transitions)
32033202

32043203
def eventFilter(self, obj, event):
32053204
"""Filter out certain QShortcuts - for example, arrow keys used

src/windows/views/timeline.py

Lines changed: 95 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def ShowTimelineMenu(self, position, layer_number):
543543
Paste_Clip = menu.addAction(_("Paste"))
544544
Paste_Clip.setShortcut(QKeySequence(self.window.getShortcutByName("pasteAll")))
545545
Paste_Clip.triggered.connect(
546-
partial(self.Paste_Triggered, MenuCopy.PASTE, float(position), int(layer_number), [], [])
546+
partial(self.Paste_Triggered, MenuCopy.PASTE, [], [])
547547
)
548548

549549
# Show menu
@@ -637,7 +637,7 @@ def ShowClipMenu(self, clip_id=None):
637637
if has_clipboard:
638638
# Paste Menu (Only show if partial clipboard available)
639639
Paste_Clip = menu.addAction(_("Paste"))
640-
Paste_Clip.triggered.connect(partial(self.Paste_Triggered, MenuCopy.PASTE, 0.0, 0, clip_ids, []))
640+
Paste_Clip.triggered.connect(partial(self.Paste_Triggered, MenuCopy.PASTE, clip_ids, []))
641641

642642
menu.addSeparator()
643643

@@ -1678,89 +1678,106 @@ def RemoveAllGaps_Triggered(self, found_start, layer_number):
16781678
# Clear transaction id
16791679
get_app().updates.transaction_id = None
16801680

1681-
def Paste_Triggered(self, action, position, layer_id, clip_ids, tran_ids):
1681+
def Paste_Triggered(self, action, clip_ids, tran_ids):
16821682
"""Callback for paste context menus"""
16831683
log.debug(action)
16841684

1685-
# Start transaction
1686-
tid = self.get_uuid()
1687-
get_app().updates.transaction_id = tid
1688-
1689-
# Get clipboard
1690-
copied_object = ClipboardManager.from_mime(get_app().clipboard().mimeData())
1691-
if not copied_object:
1692-
get_app().updates.transaction_id = None
1693-
return
1694-
1695-
print(f"Copied object found: {type(copied_object).__name__}")
1696-
1697-
# Adjust positions and layers for lists of objects
1698-
def adjust_positions_and_layers(objects, position, layer_id):
1699-
left_most_position = min(obj.data['position'] for obj in objects)
1700-
top_most_layer = max(obj.data['layer'] for obj in objects)
1701-
position_diff = position - left_most_position
1702-
layer_diff = layer_id - top_most_layer if layer_id != -1 else 0
1703-
1704-
for obj in objects:
1705-
obj.type = 'insert'
1706-
obj.data.pop('id', None)
1707-
obj.id = None
1708-
if 'effects' in obj.data:
1709-
obj.data['effects'] = [
1710-
{k: (get_app().project.generate_id() if k == 'id' else v) for k, v in effect.items()}
1711-
for effect in obj.data['effects']
1712-
]
1713-
obj.data['position'] += position_diff
1714-
obj.data['layer'] += layer_diff
1715-
obj.save()
1716-
1717-
# Apply clipboard data to target object, merging effects
1718-
def apply_clipboard_data(target_obj, clipboard_data, excluded_keys=None):
1719-
excluded_keys = excluded_keys or []
1720-
for k, v in clipboard_data.items():
1721-
if k in excluded_keys:
1722-
continue
1723-
if k == 'effects' and isinstance(v, list):
1724-
existing_effects = target_obj.data.setdefault('effects', [])
1725-
effect_map = {effect['class_name']: effect for effect in existing_effects}
1726-
1727-
for effect in v:
1728-
effect_type = effect.get('class_name')
1729-
effect['id'] = get_app().project.generate_id()
1730-
if effect_type in effect_map:
1731-
effect_map[effect_type].update(effect)
1732-
else:
1733-
existing_effects.append(effect)
1685+
# Get global mouse position
1686+
global_mouse_pos = QCursor.pos()
1687+
local_mouse_pos = self.mapFromGlobal(global_mouse_pos)
17341688

1735-
target_obj.data['effects'] = existing_effects
1736-
else:
1737-
target_obj.data[k] = v
1738-
target_obj.save()
1739-
1740-
# If a single clip/transition is copied with no target, add to a list (for inserting)
1741-
if len(clip_ids + tran_ids) == 0 and \
1742-
(isinstance(copied_object, Clip) or isinstance(copied_object, Transition)):
1743-
copied_object = [copied_object]
1689+
# Callback function, to actually add the clip object
1690+
def callback(self, clip_ids, tran_ids, callback_data):
1691+
position = callback_data.get('position', 0.0)
1692+
layer_id = callback_data.get('track', 0)
17441693

1745-
# Handle list of objects (adjust positions and layers)
1746-
if isinstance(copied_object, list):
1747-
adjust_positions_and_layers(copied_object, position, layer_id)
1694+
# Start transaction
1695+
tid = self.get_uuid()
1696+
get_app().updates.transaction_id = tid
17481697

1749-
# Handle individual objects (Clip, Transition, Effect)
1750-
for clip_id in clip_ids:
1751-
clip = Clip.get(id=clip_id)
1752-
if clip and isinstance(copied_object, Clip):
1753-
apply_clipboard_data(clip, copied_object.data, excluded_keys=['id', 'position', 'layer', 'start', 'end'])
1754-
if clip and isinstance(copied_object, Effect):
1755-
apply_clipboard_data(clip, {"effects": [copied_object.data]}, excluded_keys=['id'])
1698+
# Get clipboard
1699+
copied_object = ClipboardManager.from_mime(get_app().clipboard().mimeData())
1700+
if not copied_object:
1701+
get_app().updates.transaction_id = None
1702+
return
17561703

1757-
for tran_id in tran_ids:
1758-
tran = Transition.get(id=tran_id)
1759-
if tran and isinstance(copied_object, Transition):
1760-
apply_clipboard_data(tran, copied_object.data, excluded_keys=['id', 'position', 'layer', 'start', 'end'])
1704+
# Remove copied ids from targets
1705+
if isinstance(copied_object, Clip):
1706+
clip_ids = [id for id in clip_ids if id != copied_object.id]
1707+
if isinstance(copied_object, Transition):
1708+
tran_ids = [id for id in tran_ids if id != copied_object.id]
1709+
1710+
# Adjust positions and layers for lists of objects
1711+
def adjust_positions_and_layers(objects, position, layer_id):
1712+
left_most_position = min(obj.data['position'] for obj in objects)
1713+
top_most_layer = max(obj.data['layer'] for obj in objects)
1714+
position_diff = position - left_most_position
1715+
layer_diff = layer_id - top_most_layer if layer_id != -1 else 0
1716+
1717+
for obj in objects:
1718+
obj.type = 'insert'
1719+
obj.data.pop('id', None)
1720+
obj.id = None
1721+
if 'effects' in obj.data:
1722+
obj.data['effects'] = [
1723+
{k: (get_app().project.generate_id() if k == 'id' else v) for k, v in effect.items()}
1724+
for effect in obj.data['effects']
1725+
]
1726+
obj.data['position'] += position_diff
1727+
obj.data['layer'] += layer_diff
1728+
obj.save()
1729+
1730+
# Apply clipboard data to target object, merging effects
1731+
def apply_clipboard_data(target_obj, clipboard_data, excluded_keys=None):
1732+
excluded_keys = excluded_keys or []
1733+
for k, v in clipboard_data.items():
1734+
if k in excluded_keys:
1735+
continue
1736+
if k == 'effects' and isinstance(v, list):
1737+
existing_effects = target_obj.data.setdefault('effects', [])
1738+
effect_map = {effect['class_name']: effect for effect in existing_effects}
1739+
1740+
for effect in v:
1741+
effect_type = effect.get('class_name')
1742+
effect['id'] = get_app().project.generate_id()
1743+
if effect_type in effect_map:
1744+
effect_map[effect_type].update(effect)
1745+
else:
1746+
existing_effects.append(effect)
1747+
1748+
target_obj.data['effects'] = existing_effects
1749+
else:
1750+
target_obj.data[k] = v
1751+
target_obj.save()
1752+
1753+
# If a single clip/transition is copied with no target, add to a list (for inserting)
1754+
if len(clip_ids + tran_ids) == 0 and \
1755+
(isinstance(copied_object, Clip) or isinstance(copied_object, Transition)):
1756+
copied_object = [copied_object]
1757+
1758+
# Handle list of objects (adjust positions and layers)
1759+
if isinstance(copied_object, list):
1760+
adjust_positions_and_layers(copied_object, position, layer_id)
1761+
1762+
# Handle individual objects (Clip, Transition, Effect)
1763+
for clip_id in clip_ids:
1764+
clip = Clip.get(id=clip_id)
1765+
if clip and isinstance(copied_object, Clip):
1766+
apply_clipboard_data(clip, copied_object.data, excluded_keys=['id', 'position', 'layer', 'start', 'end'])
1767+
if clip and isinstance(copied_object, Effect):
1768+
apply_clipboard_data(clip, {"effects": [copied_object.data]}, excluded_keys=['id'])
1769+
1770+
for tran_id in tran_ids:
1771+
tran = Transition.get(id=tran_id)
1772+
if tran and isinstance(copied_object, Transition):
1773+
apply_clipboard_data(tran, copied_object.data, excluded_keys=['id', 'position', 'layer', 'start', 'end'])
1774+
1775+
# End transaction
1776+
get_app().updates.transaction_id = None
17611777

1762-
# End transaction
1763-
get_app().updates.transaction_id = None
1778+
# Find position from javascript
1779+
self.run_js(JS_SCOPE_SELECTOR + ".getJavaScriptPosition({}, {});"
1780+
.format(local_mouse_pos.x(), local_mouse_pos.y()), partial(callback, self, clip_ids, tran_ids))
17641781

17651782
def Nudge_Triggered(self, action, clip_ids, tran_ids):
17661783
"""Callback for clip nudges"""
@@ -2722,7 +2739,7 @@ def ShowTransitionMenu(self, tran_id=None):
27222739
if has_clipboard:
27232740
# Paste Menu (Only show when partial transition clipboard available)
27242741
Paste_Tran = menu.addAction(_("Paste"))
2725-
Paste_Tran.triggered.connect(partial(self.Paste_Triggered, MenuCopy.PASTE, 0.0, 0, [], tran_ids))
2742+
Paste_Tran.triggered.connect(partial(self.Paste_Triggered, MenuCopy.PASTE, [], tran_ids))
27262743

27272744
menu.addSeparator()
27282745

0 commit comments

Comments
 (0)