Add CSV mapping rule and Undo last rename feature
Made-with: Cursor
This commit is contained in:
+44
-2
@@ -26,7 +26,7 @@ from PyQt6.QtWidgets import (
|
||||
from PyQt6.QtCore import Qt, QDir, QItemSelectionModel
|
||||
from PyQt6.QtGui import QFont, QColor
|
||||
|
||||
from engine.pipeline import compute_preview, perform_renames
|
||||
from engine.pipeline import compute_preview, perform_renames, save_undo_log, load_undo_log, perform_undo
|
||||
from engine.rules import Rule
|
||||
from .rule_widgets import (
|
||||
ReplaceRuleWidget,
|
||||
@@ -37,6 +37,7 @@ from .rule_widgets import (
|
||||
NumberingRuleWidget,
|
||||
EpisodeRenumberRuleWidget,
|
||||
PrefixSuffixRuleWidget,
|
||||
CsvMappingRuleWidget,
|
||||
)
|
||||
|
||||
|
||||
@@ -85,6 +86,7 @@ class MainWindow(QMainWindow):
|
||||
NumberingRuleWidget(),
|
||||
EpisodeRenumberRuleWidget(),
|
||||
PrefixSuffixRuleWidget(),
|
||||
CsvMappingRuleWidget(),
|
||||
]
|
||||
rule_titles = [
|
||||
"1. Replace",
|
||||
@@ -95,6 +97,7 @@ class MainWindow(QMainWindow):
|
||||
"6. Numbering",
|
||||
"7. Episode renumber",
|
||||
"8. Prefix / Suffix",
|
||||
"9. CSV mapping",
|
||||
]
|
||||
for title, w in zip(rule_titles, self._rule_widgets):
|
||||
w.enabled_cb.toggled.connect(self._refresh_preview) # always refresh when checkbox toggled
|
||||
@@ -130,10 +133,16 @@ class MainWindow(QMainWindow):
|
||||
self.preview_status = QLabel("Add a folder to see files.")
|
||||
right_layout.addWidget(self.preview_status)
|
||||
|
||||
btn_layout = QHBoxLayout()
|
||||
apply_btn = QPushButton("Apply renames")
|
||||
apply_btn.setMinimumHeight(36)
|
||||
apply_btn.clicked.connect(self._apply_renames)
|
||||
right_layout.addWidget(apply_btn)
|
||||
undo_btn = QPushButton("Undo last rename")
|
||||
undo_btn.setMinimumHeight(36)
|
||||
undo_btn.clicked.connect(self._undo_renames)
|
||||
btn_layout.addWidget(apply_btn)
|
||||
btn_layout.addWidget(undo_btn)
|
||||
right_layout.addLayout(btn_layout)
|
||||
split.addWidget(right)
|
||||
|
||||
split.setSizes([320, 680])
|
||||
@@ -289,5 +298,38 @@ class MainWindow(QMainWindow):
|
||||
msg += f"\n... and {len(errors) - 10} more."
|
||||
QMessageBox.warning(self, "Rename errors", msg)
|
||||
else:
|
||||
save_undo_log(self._base_dir, renames)
|
||||
QMessageBox.information(self, "Done", f"Renamed {len(results)} file(s).")
|
||||
self._on_dir_changed()
|
||||
|
||||
def _undo_renames(self):
|
||||
if not self._base_dir:
|
||||
QMessageBox.warning(self, "No folder", "Select a folder first.")
|
||||
return
|
||||
renames = load_undo_log(self._base_dir)
|
||||
if not renames:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"No undo data",
|
||||
f"No undo data for this folder.\n\nUndo is available after you run \"Apply renames\" here.",
|
||||
)
|
||||
return
|
||||
ok = QMessageBox.question(
|
||||
self,
|
||||
"Undo last rename",
|
||||
f"Revert {len(renames)} file(s) in\n{self._base_dir}\nback to their previous names?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if ok != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
results = perform_undo(self._base_dir)
|
||||
errors = [r for r in results if r[2]]
|
||||
if errors:
|
||||
msg = "\n".join(f"{r[0]}: {r[2]}" for r in errors[:10])
|
||||
if len(errors) > 10:
|
||||
msg += f"\n... and {len(errors) - 10} more."
|
||||
QMessageBox.warning(self, "Undo errors", msg)
|
||||
else:
|
||||
QMessageBox.information(self, "Undone", f"Reverted {len(results)} file(s).")
|
||||
self._on_dir_changed()
|
||||
|
||||
@@ -13,6 +13,8 @@ from PyQt6.QtWidgets import (
|
||||
QGroupBox,
|
||||
QFormLayout,
|
||||
QStackedWidget,
|
||||
QPushButton,
|
||||
QFileDialog,
|
||||
)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
|
||||
@@ -25,6 +27,7 @@ from engine.rules import (
|
||||
EpisodeRenumberRule,
|
||||
RegexRule,
|
||||
PrefixSuffixRule,
|
||||
CsvMappingRule,
|
||||
)
|
||||
|
||||
|
||||
@@ -325,3 +328,45 @@ class PrefixSuffixRuleWidget(QWidget):
|
||||
r = PrefixSuffixRule(prefix=self.prefix.text(), suffix=self.suffix.text())
|
||||
r.enabled = self.enabled_cb.isChecked()
|
||||
return r
|
||||
|
||||
|
||||
class CsvMappingRuleWidget(QWidget):
|
||||
ruleChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
layout = QFormLayout(self)
|
||||
self.enabled_cb = QCheckBox("Use this rule")
|
||||
self.enabled_cb.setChecked(False)
|
||||
self.enabled_cb.toggled.connect(self._emit)
|
||||
layout.addRow(self.enabled_cb)
|
||||
path_row = QHBoxLayout()
|
||||
self.path_edit = QLineEdit()
|
||||
self.path_edit.setPlaceholderText("Path to CSV file…")
|
||||
self.path_edit.textChanged.connect(self._emit)
|
||||
browse_btn = QPushButton("Browse…")
|
||||
browse_btn.clicked.connect(self._browse)
|
||||
path_row.addWidget(self.path_edit, 1)
|
||||
path_row.addWidget(browse_btn)
|
||||
layout.addRow("CSV file:", path_row)
|
||||
info = QLabel('CSV must have columns "Original Name" and "Target Name". Lookup is by current filename.')
|
||||
info.setWordWrap(True)
|
||||
layout.addRow(info)
|
||||
|
||||
def _browse(self):
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Select CSV",
|
||||
"",
|
||||
"CSV (*.csv);;All files (*)",
|
||||
)
|
||||
if path:
|
||||
self.path_edit.setText(path)
|
||||
|
||||
def _emit(self):
|
||||
self.ruleChanged.emit()
|
||||
|
||||
def getRule(self) -> CsvMappingRule:
|
||||
r = CsvMappingRule(csv_path=self.path_edit.text().strip())
|
||||
r.enabled = self.enabled_cb.isChecked()
|
||||
return r
|
||||
|
||||
Reference in New Issue
Block a user