feat: per-tab LTX-2 mode toggle + [LTX2] badge (pipeline wiring in next stage)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3303,6 +3303,7 @@ class _PlaylistTabBar(QTabBar):
|
|||||||
pin_toggle_requested = pyqtSignal(int)
|
pin_toggle_requested = pyqtSignal(int)
|
||||||
tab_folder_toggle_requested = pyqtSignal(int)
|
tab_folder_toggle_requested = pyqtSignal(int)
|
||||||
duplicate_requested = pyqtSignal(int)
|
duplicate_requested = pyqtSignal(int)
|
||||||
|
mode_toggle_requested = pyqtSignal(int)
|
||||||
|
|
||||||
def mouseDoubleClickEvent(self, event):
|
def mouseDoubleClickEvent(self, event):
|
||||||
idx = self.tabAt(event.pos())
|
idx = self.tabAt(event.pos())
|
||||||
@@ -3329,6 +3330,9 @@ class _PlaylistTabBar(QTabBar):
|
|||||||
act_tabfolder.setChecked(bool(getattr(pw, "_tab_folder", False)))
|
act_tabfolder.setChecked(bool(getattr(pw, "_tab_folder", False)))
|
||||||
act_rename = menu.addAction("Rename…")
|
act_rename = menu.addAction("Rename…")
|
||||||
act_dup = menu.addAction("Duplicate tab")
|
act_dup = menu.addAction("Duplicate tab")
|
||||||
|
act_mode = menu.addAction("LTX-2 mode")
|
||||||
|
act_mode.setCheckable(True)
|
||||||
|
act_mode.setChecked(bool(getattr(pw, "_mode", "foley") == "ltx2"))
|
||||||
chosen = menu.exec(event.globalPos())
|
chosen = menu.exec(event.globalPos())
|
||||||
if chosen == act_pin:
|
if chosen == act_pin:
|
||||||
self.pin_toggle_requested.emit(idx)
|
self.pin_toggle_requested.emit(idx)
|
||||||
@@ -3338,6 +3342,8 @@ class _PlaylistTabBar(QTabBar):
|
|||||||
self._start_edit(idx)
|
self._start_edit(idx)
|
||||||
elif chosen == act_dup:
|
elif chosen == act_dup:
|
||||||
self.duplicate_requested.emit(idx)
|
self.duplicate_requested.emit(idx)
|
||||||
|
elif chosen == act_mode:
|
||||||
|
self.mode_toggle_requested.emit(idx)
|
||||||
|
|
||||||
def _start_edit(self, idx: int) -> None:
|
def _start_edit(self, idx: int) -> None:
|
||||||
editor = QLineEdit(self)
|
editor = QLineEdit(self)
|
||||||
@@ -3411,6 +3417,7 @@ class PlaylistWidget(QListWidget):
|
|||||||
self._pinned: bool = False # shown in the side-by-side view
|
self._pinned: bool = False # shown in the side-by-side view
|
||||||
self._tab_folder: bool = False # append this tab's name to export folder
|
self._tab_folder: bool = False # append this tab's name to export folder
|
||||||
self._dest_folder: str = "" # per-tab export destination
|
self._dest_folder: str = "" # per-tab export destination
|
||||||
|
self._mode: str = "foley" # export pipeline mode: "foley" | "ltx2"
|
||||||
self._label: str = "" # tab name (source of truth across views)
|
self._label: str = "" # tab name (source of truth across views)
|
||||||
self._visible: list[str | None] = [] # rows shown; None = separator row
|
self._visible: list[str | None] = [] # rows shown; None = separator row
|
||||||
self._selected_path: str | None = None
|
self._selected_path: str | None = None
|
||||||
@@ -3951,6 +3958,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._playlist_tabs.tabBar().tab_folder_toggle_requested.connect(
|
self._playlist_tabs.tabBar().tab_folder_toggle_requested.connect(
|
||||||
self._on_tab_folder_toggle)
|
self._on_tab_folder_toggle)
|
||||||
self._playlist_tabs.tabBar().duplicate_requested.connect(self._on_duplicate_tab)
|
self._playlist_tabs.tabBar().duplicate_requested.connect(self._on_duplicate_tab)
|
||||||
|
self._playlist_tabs.tabBar().mode_toggle_requested.connect(self._on_tab_mode_toggle)
|
||||||
self._playlist_tabs.tabCloseRequested.connect(self._on_close_tab)
|
self._playlist_tabs.tabCloseRequested.connect(self._on_close_tab)
|
||||||
self._playlist_tabs.currentChanged.connect(self._on_tab_changed)
|
self._playlist_tabs.currentChanged.connect(self._on_tab_changed)
|
||||||
self._btn_add_tab = QPushButton("+")
|
self._btn_add_tab = QPushButton("+")
|
||||||
@@ -5007,6 +5015,20 @@ class MainWindow(QMainWindow):
|
|||||||
self._save_playlist_tabs()
|
self._save_playlist_tabs()
|
||||||
self._show_status(f"Duplicated tab → {label}", 4000)
|
self._show_status(f"Duplicated tab → {label}", 4000)
|
||||||
|
|
||||||
|
def _on_tab_mode_toggle(self, idx: int) -> None:
|
||||||
|
pw = self._playlist_tabs.widget(idx)
|
||||||
|
if pw is None:
|
||||||
|
return
|
||||||
|
pw._mode = "ltx2" if getattr(pw, "_mode", "foley") != "ltx2" else "foley"
|
||||||
|
self._refresh_layout() # re-render tab titles (badge)
|
||||||
|
self._save_playlist_tabs()
|
||||||
|
self._show_status(f"{pw._label}: {pw._mode.upper()} mode", 3000)
|
||||||
|
|
||||||
|
def _tab_title(self, pw) -> str:
|
||||||
|
"""Displayed tab title — appends a [LTX2] badge for ltx2-mode tabs.
|
||||||
|
Does NOT mutate pw._label (the source of truth for export folders)."""
|
||||||
|
return f"{pw._label} [LTX2]" if getattr(pw, "_mode", "foley") == "ltx2" else pw._label
|
||||||
|
|
||||||
# ── File-list tabs ───────────────────────────────────────────
|
# ── File-list tabs ───────────────────────────────────────────
|
||||||
def _wire_pw(self, pw: "PlaylistWidget") -> None:
|
def _wire_pw(self, pw: "PlaylistWidget") -> None:
|
||||||
pw.file_selected.connect(self._load_file)
|
pw.file_selected.connect(self._load_file)
|
||||||
@@ -5068,7 +5090,7 @@ class MainWindow(QMainWindow):
|
|||||||
for pw in self._pws:
|
for pw in self._pws:
|
||||||
if not pw._pinned:
|
if not pw._pinned:
|
||||||
pw.setMinimumWidth(0)
|
pw.setMinimumWidth(0)
|
||||||
self._playlist_tabs.addTab(pw, pw._label)
|
self._playlist_tabs.addTab(pw, self._tab_title(pw))
|
||||||
splitter = QSplitter(Qt.Orientation.Horizontal)
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
splitter.setChildrenCollapsible(False)
|
splitter.setChildrenCollapsible(False)
|
||||||
for pw in pinned:
|
for pw in pinned:
|
||||||
@@ -5079,7 +5101,7 @@ class MainWindow(QMainWindow):
|
|||||||
header = QWidget()
|
header = QWidget()
|
||||||
hdr = QHBoxLayout(header)
|
hdr = QHBoxLayout(header)
|
||||||
hdr.setContentsMargins(2, 1, 2, 1)
|
hdr.setContentsMargins(2, 1, 2, 1)
|
||||||
lbl = QLabel(pw._label)
|
lbl = QLabel(self._tab_title(pw))
|
||||||
lbl.setStyleSheet("font-weight: bold;")
|
lbl.setStyleSheet("font-weight: bold;")
|
||||||
btn = QPushButton("✕")
|
btn = QPushButton("✕")
|
||||||
btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||||
@@ -5104,7 +5126,7 @@ class MainWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
for pw in self._pws:
|
for pw in self._pws:
|
||||||
pw.setMinimumWidth(200)
|
pw.setMinimumWidth(200)
|
||||||
self._playlist_tabs.addTab(pw, pw._label)
|
self._playlist_tabs.addTab(pw, self._tab_title(pw))
|
||||||
self._list_stack.setCurrentWidget(self._playlist_tabs)
|
self._list_stack.setCurrentWidget(self._playlist_tabs)
|
||||||
self._set_left_pane_width(220)
|
self._set_left_pane_width(220)
|
||||||
finally:
|
finally:
|
||||||
@@ -5302,6 +5324,7 @@ class MainWindow(QMainWindow):
|
|||||||
"pinned": pw._pinned,
|
"pinned": pw._pinned,
|
||||||
"tab_folder": pw._tab_folder,
|
"tab_folder": pw._tab_folder,
|
||||||
"export_folder": pw._dest_folder,
|
"export_folder": pw._dest_folder,
|
||||||
|
"mode": pw._mode,
|
||||||
} for pw in self._pws]
|
} for pw in self._pws]
|
||||||
cur = self._pws.index(self._active_pw) if self._active_pw in self._pws else 0
|
cur = self._pws.index(self._active_pw) if self._active_pw in self._pws else 0
|
||||||
data = {"tabs": tabs, "current": cur}
|
data = {"tabs": tabs, "current": cur}
|
||||||
@@ -5347,6 +5370,7 @@ class MainWindow(QMainWindow):
|
|||||||
pw._tab_folder = bool(t.get("tab_folder"))
|
pw._tab_folder = bool(t.get("tab_folder"))
|
||||||
pw._dest_folder = t.get("export_folder") or self._settings.value(
|
pw._dest_folder = t.get("export_folder") or self._settings.value(
|
||||||
"export_folder", str(Path.home()))
|
"export_folder", str(Path.home()))
|
||||||
|
pw._mode = t.get("mode", "foley")
|
||||||
cur = min(max(0, data.get("current", 0)), len(self._pws) - 1)
|
cur = min(max(0, data.get("current", 0)), len(self._pws) - 1)
|
||||||
finally:
|
finally:
|
||||||
self._loading_tabs = False
|
self._loading_tabs = False
|
||||||
|
|||||||
@@ -137,3 +137,24 @@ def test_duplicate_tab(win):
|
|||||||
assert dup._label == "AlexisCrystal copy"
|
assert dup._label == "AlexisCrystal copy"
|
||||||
# sibling, not a child: ".../alexis/" -> ".../alexis_copy" (not ".../alexis/_copy")
|
# sibling, not a child: ".../alexis/" -> ".../alexis_copy" (not ".../alexis/_copy")
|
||||||
assert dup._dest_folder == "/data/alexis_copy"
|
assert dup._dest_folder == "/data/alexis_copy"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tab_mode_defaults_foley(win):
|
||||||
|
# Fresh tabs use the Foley pipeline; sessions/tabs without a stored mode
|
||||||
|
# load unchanged.
|
||||||
|
assert win._pws
|
||||||
|
for pw in win._pws:
|
||||||
|
assert pw._mode == "foley"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tab_mode_toggle(win):
|
||||||
|
# Right-click → "LTX-2 mode" flips the per-tab mode and the displayed title
|
||||||
|
# gains a [LTX2] badge (without mutating pw._label). Suppress QSettings
|
||||||
|
# writes via _loading_tabs so the test can't touch the real session.
|
||||||
|
win._loading_tabs = True
|
||||||
|
try:
|
||||||
|
win._on_tab_mode_toggle(win._playlist_tabs.indexOf(win._pws[0]))
|
||||||
|
finally:
|
||||||
|
win._loading_tabs = False
|
||||||
|
assert win._pws[0]._mode == "ltx2"
|
||||||
|
assert win._tab_title(win._pws[0]).endswith("[LTX2]")
|
||||||
|
|||||||
Reference in New Issue
Block a user