feat: right-click "Duplicate tab" — clone files into a new tab with adapted name + own folder
New tab copies the source tab's video list + separators, gets a unique
"<name> copy" label and an adapted own export folder ("<folder>_copy"), and
inherits the tab-named-folder flag. No files are moved or copied — you export
into the new tab's folder. Keeps Foley/variant datasets separate without the
file-shuffling that a misexport used to require.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3302,6 +3302,7 @@ class _PlaylistTabBar(QTabBar):
|
||||
tab_renamed = pyqtSignal(int, str)
|
||||
pin_toggle_requested = pyqtSignal(int)
|
||||
tab_folder_toggle_requested = pyqtSignal(int)
|
||||
duplicate_requested = pyqtSignal(int)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
idx = self.tabAt(event.pos())
|
||||
@@ -3327,6 +3328,7 @@ class _PlaylistTabBar(QTabBar):
|
||||
act_tabfolder.setCheckable(True)
|
||||
act_tabfolder.setChecked(bool(getattr(pw, "_tab_folder", False)))
|
||||
act_rename = menu.addAction("Rename…")
|
||||
act_dup = menu.addAction("Duplicate tab")
|
||||
chosen = menu.exec(event.globalPos())
|
||||
if chosen == act_pin:
|
||||
self.pin_toggle_requested.emit(idx)
|
||||
@@ -3334,6 +3336,8 @@ class _PlaylistTabBar(QTabBar):
|
||||
self.tab_folder_toggle_requested.emit(idx)
|
||||
elif chosen == act_rename:
|
||||
self._start_edit(idx)
|
||||
elif chosen == act_dup:
|
||||
self.duplicate_requested.emit(idx)
|
||||
|
||||
def _start_edit(self, idx: int) -> None:
|
||||
editor = QLineEdit(self)
|
||||
@@ -3946,6 +3950,7 @@ class MainWindow(QMainWindow):
|
||||
self._playlist_tabs.tabBar().pin_toggle_requested.connect(self._on_pin_toggle)
|
||||
self._playlist_tabs.tabBar().tab_folder_toggle_requested.connect(
|
||||
self._on_tab_folder_toggle)
|
||||
self._playlist_tabs.tabBar().duplicate_requested.connect(self._on_duplicate_tab)
|
||||
self._playlist_tabs.tabCloseRequested.connect(self._on_close_tab)
|
||||
self._playlist_tabs.currentChanged.connect(self._on_tab_changed)
|
||||
self._btn_add_tab = QPushButton("+")
|
||||
@@ -4974,6 +4979,32 @@ class MainWindow(QMainWindow):
|
||||
self._refresh_playlist_checks()
|
||||
self._update_next_label()
|
||||
|
||||
def _on_duplicate_tab(self, idx: int) -> None:
|
||||
"""Clone a tab's file list into a new tab with an adapted name and its
|
||||
own (adapted) export folder. No files are moved or copied — the new tab
|
||||
just targets a separate dataset folder you export into."""
|
||||
src = self._playlist_tabs.widget(idx)
|
||||
if src is None:
|
||||
return
|
||||
base = f"{src._label} copy"
|
||||
label, n = base, 2
|
||||
existing = {pw._label for pw in self._pws}
|
||||
while label in existing:
|
||||
label = f"{base} {n}"
|
||||
n += 1
|
||||
pw = self._add_playlist_tab(
|
||||
label=label,
|
||||
files=list(src._paths),
|
||||
separators=sorted(src._separators_before),
|
||||
select=True,
|
||||
)
|
||||
src_folder = getattr(src, "_dest_folder", "")
|
||||
pw._dest_folder = (src_folder + "_copy") if src_folder else ""
|
||||
pw._tab_folder = getattr(src, "_tab_folder", False)
|
||||
self._sync_folder_field_to_tab()
|
||||
self._save_playlist_tabs()
|
||||
self._show_status(f"Duplicated tab → {label}", 4000)
|
||||
|
||||
# ── File-list tabs ───────────────────────────────────────────
|
||||
def _wire_pw(self, pw: "PlaylistWidget") -> None:
|
||||
pw.file_selected.connect(self._load_file)
|
||||
|
||||
@@ -107,3 +107,22 @@ def test_side_by_side_menu_pins_third_panel(win):
|
||||
win._deck_loading = False
|
||||
assert win._tab_crop._pinned is True
|
||||
assert len(_split_columns(win)) == 3
|
||||
|
||||
|
||||
def test_duplicate_tab(win):
|
||||
# Right-click → Duplicate tab: clones files into a new tab with an adapted
|
||||
# name + adapted own folder, no file moves. Suppress QSettings writes via
|
||||
# _loading_tabs so the test can't touch the real session.
|
||||
win._loading_tabs = True
|
||||
try:
|
||||
src = win._pws[0]
|
||||
src._label = "AlexisCrystal"
|
||||
src._dest_folder = "/data/alexis"
|
||||
n_before = len(win._pws)
|
||||
win._on_duplicate_tab(win._playlist_tabs.indexOf(src))
|
||||
finally:
|
||||
win._loading_tabs = False
|
||||
assert len(win._pws) == n_before + 1
|
||||
dup = win._pws[-1]
|
||||
assert dup._label == "AlexisCrystal copy"
|
||||
assert dup._dest_folder == "/data/alexis_copy"
|
||||
|
||||
Reference in New Issue
Block a user