d1a13115a4
Made-with: Cursor
373 lines
12 KiB
Python
373 lines
12 KiB
Python
"""
|
|
Rule configuration widgets: each rule type has a small form that updates the Rule model.
|
|
"""
|
|
from PyQt6.QtWidgets import (
|
|
QWidget,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QLineEdit,
|
|
QSpinBox,
|
|
QComboBox,
|
|
QCheckBox,
|
|
QGroupBox,
|
|
QFormLayout,
|
|
QStackedWidget,
|
|
QPushButton,
|
|
QFileDialog,
|
|
)
|
|
from PyQt6.QtCore import pyqtSignal
|
|
|
|
from engine.rules import (
|
|
ReplaceRule,
|
|
InsertRule,
|
|
RemoveRule,
|
|
CaseRule,
|
|
NumberingRule,
|
|
EpisodeRenumberRule,
|
|
RegexRule,
|
|
PrefixSuffixRule,
|
|
CsvMappingRule,
|
|
)
|
|
|
|
|
|
class ReplaceRuleWidget(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)
|
|
self.find = QLineEdit()
|
|
self.find.setPlaceholderText("Text to find")
|
|
self.find.textChanged.connect(self._emit)
|
|
self.replace = QLineEdit()
|
|
self.replace.setPlaceholderText("Replace with")
|
|
self.replace.textChanged.connect(self._emit)
|
|
self.caseSensitive = QCheckBox("Case sensitive")
|
|
self.caseSensitive.toggled.connect(self._emit)
|
|
self.wholeWord = QCheckBox("Whole word only")
|
|
self.wholeWord.toggled.connect(self._emit)
|
|
layout.addRow("Find:", self.find)
|
|
layout.addRow("Replace with:", self.replace)
|
|
layout.addRow(self.caseSensitive)
|
|
layout.addRow(self.wholeWord)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> ReplaceRule:
|
|
r = ReplaceRule(
|
|
find=self.find.text(),
|
|
replace=self.replace.text(),
|
|
case_sensitive=self.caseSensitive.isChecked(),
|
|
whole_word=self.wholeWord.isChecked(),
|
|
)
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class RegexRuleWidget(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)
|
|
self.pattern = QLineEdit()
|
|
self.pattern.setPlaceholderText(r"e.g. S(\d+)E(\d+)")
|
|
self.pattern.textChanged.connect(self._emit)
|
|
self.replacement = QLineEdit()
|
|
self.replacement.setPlaceholderText(r"e.g. S\1E\2")
|
|
self.replacement.textChanged.connect(self._emit)
|
|
layout.addRow("Regex pattern:", self.pattern)
|
|
layout.addRow("Replacement:", self.replacement)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> RegexRule:
|
|
r = RegexRule(pattern=self.pattern.text(), replacement=self.replacement.text())
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class InsertRuleWidget(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)
|
|
self.text = QLineEdit()
|
|
self.text.setPlaceholderText("Text to insert")
|
|
self.text.textChanged.connect(self._emit)
|
|
self.position = QComboBox()
|
|
self.position.addItems(["At start", "At end", "At position..."])
|
|
self.position.setCurrentIndex(0)
|
|
self.position.currentIndexChanged.connect(self._emit)
|
|
self.positionSpin = QSpinBox()
|
|
self.positionSpin.setMinimum(0)
|
|
self.positionSpin.setMaximum(9999)
|
|
self.positionSpin.valueChanged.connect(self._emit)
|
|
layout.addRow("Text:", self.text)
|
|
layout.addRow("Position:", self.position)
|
|
layout.addRow("Index:", self.positionSpin)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> InsertRule:
|
|
idx = self.position.currentIndex()
|
|
if idx == 0:
|
|
pos = 0
|
|
elif idx == 1:
|
|
pos = -1
|
|
else:
|
|
pos = self.positionSpin.value()
|
|
r = InsertRule(text=self.text.text(), position=pos)
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class RemoveRuleWidget(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)
|
|
self.removeType = QComboBox()
|
|
self.removeType.addItems([
|
|
"Remove text",
|
|
"Remove all digits",
|
|
"Remove first N characters",
|
|
"Remove last N characters",
|
|
])
|
|
self.removeType.currentIndexChanged.connect(self._emit)
|
|
self.value = QLineEdit()
|
|
self.value.setPlaceholderText("Text or number")
|
|
self.value.textChanged.connect(self._emit)
|
|
layout.addRow("Type:", self.removeType)
|
|
layout.addRow("Value:", self.value)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> RemoveRule:
|
|
idx = self.removeType.currentIndex()
|
|
types = ["chars", "digits", "first_n", "last_n"]
|
|
r = RemoveRule(remove_type=types[idx], value=self.value.text())
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class CaseRuleWidget(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)
|
|
self.caseType = QComboBox()
|
|
self.caseType.addItems(["Title Case", "UPPER", "lower", "Sentence case"])
|
|
self.caseType.currentIndexChanged.connect(self._emit)
|
|
layout.addRow("Case:", self.caseType)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> CaseRule:
|
|
types = ["title", "upper", "lower", "sentence"]
|
|
r = CaseRule(case_type=types[self.caseType.currentIndex()])
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class NumberingRuleWidget(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)
|
|
self.start = QSpinBox()
|
|
self.start.setMinimum(-9999)
|
|
self.start.setMaximum(99999)
|
|
self.start.setValue(1)
|
|
self.start.valueChanged.connect(self._emit)
|
|
self.step = QSpinBox()
|
|
self.step.setMinimum(1)
|
|
self.step.setMaximum(100)
|
|
self.step.setValue(1)
|
|
self.step.valueChanged.connect(self._emit)
|
|
self.padding = QSpinBox()
|
|
self.padding.setMinimum(1)
|
|
self.padding.setMaximum(10)
|
|
self.padding.setValue(2)
|
|
self.padding.valueChanged.connect(self._emit)
|
|
self.where = QComboBox()
|
|
self.where.addItems(["Prefix", "Suffix", "Insert at index"])
|
|
self.where.currentIndexChanged.connect(self._emit)
|
|
self.separator = QLineEdit()
|
|
self.separator.setText(" ")
|
|
self.separator.textChanged.connect(self._emit)
|
|
self.insertAt = QSpinBox()
|
|
self.insertAt.setMinimum(0)
|
|
self.insertAt.setMaximum(9999)
|
|
self.insertAt.valueChanged.connect(self._emit)
|
|
layout.addRow("Start:", self.start)
|
|
layout.addRow("Step:", self.step)
|
|
layout.addRow("Padding:", self.padding)
|
|
layout.addRow("Where:", self.where)
|
|
layout.addRow("Separator:", self.separator)
|
|
layout.addRow("Insert at index:", self.insertAt)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> NumberingRule:
|
|
where_map = ["prefix", "suffix", "insert_at"]
|
|
r = NumberingRule(
|
|
start=self.start.value(),
|
|
step=self.step.value(),
|
|
padding=self.padding.value(),
|
|
where=where_map[self.where.currentIndex()],
|
|
insert_at=self.insertAt.value(),
|
|
separator=self.separator.text() or " ",
|
|
)
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class EpisodeRenumberRuleWidget(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)
|
|
self.start = QSpinBox()
|
|
self.start.setMinimum(1)
|
|
self.start.setMaximum(9999)
|
|
self.start.setValue(1)
|
|
self.start.valueChanged.connect(self._emit)
|
|
self.step = QSpinBox()
|
|
self.step.setMinimum(1)
|
|
self.step.setValue(1)
|
|
self.step.valueChanged.connect(self._emit)
|
|
self.padding = QSpinBox()
|
|
self.padding.setMinimum(1)
|
|
self.padding.setMaximum(3)
|
|
self.padding.setValue(2)
|
|
self.padding.valueChanged.connect(self._emit)
|
|
layout.addRow("First episode number:", self.start)
|
|
layout.addRow("Step:", self.step)
|
|
layout.addRow("Zero-pad width:", self.padding)
|
|
info = QLabel("Matches patterns like S01E05 - Title or Show 1x03 - Title. Episode number is replaced; title is kept.")
|
|
info.setWordWrap(True)
|
|
layout.addRow(info)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> EpisodeRenumberRule:
|
|
r = EpisodeRenumberRule(
|
|
start=self.start.value(),
|
|
step=self.step.value(),
|
|
padding=self.padding.value(),
|
|
)
|
|
r.enabled = self.enabled_cb.isChecked()
|
|
return r
|
|
|
|
|
|
class PrefixSuffixRuleWidget(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)
|
|
self.prefix = QLineEdit()
|
|
self.prefix.setPlaceholderText("Prefix")
|
|
self.prefix.textChanged.connect(self._emit)
|
|
self.suffix = QLineEdit()
|
|
self.suffix.setPlaceholderText("Suffix")
|
|
self.suffix.textChanged.connect(self._emit)
|
|
layout.addRow("Prefix:", self.prefix)
|
|
layout.addRow("Suffix:", self.suffix)
|
|
|
|
def _emit(self):
|
|
self.ruleChanged.emit()
|
|
|
|
def getRule(self) -> PrefixSuffixRule:
|
|
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
|