supervertaler 1.9.153__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervertaler might be problematic. Click here for more details.
- Supervertaler.py +47886 -0
- modules/__init__.py +10 -0
- modules/ai_actions.py +964 -0
- modules/ai_attachment_manager.py +343 -0
- modules/ai_file_viewer_dialog.py +210 -0
- modules/autofingers_engine.py +466 -0
- modules/cafetran_docx_handler.py +379 -0
- modules/config_manager.py +469 -0
- modules/database_manager.py +1878 -0
- modules/database_migrations.py +417 -0
- modules/dejavurtf_handler.py +779 -0
- modules/document_analyzer.py +427 -0
- modules/docx_handler.py +689 -0
- modules/encoding_repair.py +319 -0
- modules/encoding_repair_Qt.py +393 -0
- modules/encoding_repair_ui.py +481 -0
- modules/feature_manager.py +350 -0
- modules/figure_context_manager.py +340 -0
- modules/file_dialog_helper.py +148 -0
- modules/find_replace.py +164 -0
- modules/find_replace_qt.py +457 -0
- modules/glossary_manager.py +433 -0
- modules/image_extractor.py +188 -0
- modules/keyboard_shortcuts_widget.py +571 -0
- modules/llm_clients.py +1211 -0
- modules/llm_leaderboard.py +737 -0
- modules/llm_superbench_ui.py +1401 -0
- modules/local_llm_setup.py +1104 -0
- modules/model_update_dialog.py +381 -0
- modules/model_version_checker.py +373 -0
- modules/mqxliff_handler.py +638 -0
- modules/non_translatables_manager.py +743 -0
- modules/pdf_rescue_Qt.py +1822 -0
- modules/pdf_rescue_tkinter.py +909 -0
- modules/phrase_docx_handler.py +516 -0
- modules/project_home_panel.py +209 -0
- modules/prompt_assistant.py +357 -0
- modules/prompt_library.py +689 -0
- modules/prompt_library_migration.py +447 -0
- modules/quick_access_sidebar.py +282 -0
- modules/ribbon_widget.py +597 -0
- modules/sdlppx_handler.py +874 -0
- modules/setup_wizard.py +353 -0
- modules/shortcut_manager.py +932 -0
- modules/simple_segmenter.py +128 -0
- modules/spellcheck_manager.py +727 -0
- modules/statuses.py +207 -0
- modules/style_guide_manager.py +315 -0
- modules/superbench_ui.py +1319 -0
- modules/superbrowser.py +329 -0
- modules/supercleaner.py +600 -0
- modules/supercleaner_ui.py +444 -0
- modules/superdocs.py +19 -0
- modules/superdocs_viewer_qt.py +382 -0
- modules/superlookup.py +252 -0
- modules/tag_cleaner.py +260 -0
- modules/tag_manager.py +333 -0
- modules/term_extractor.py +270 -0
- modules/termbase_entry_editor.py +842 -0
- modules/termbase_import_export.py +488 -0
- modules/termbase_manager.py +1060 -0
- modules/termview_widget.py +1172 -0
- modules/theme_manager.py +499 -0
- modules/tm_editor_dialog.py +99 -0
- modules/tm_manager_qt.py +1280 -0
- modules/tm_metadata_manager.py +545 -0
- modules/tmx_editor.py +1461 -0
- modules/tmx_editor_qt.py +2784 -0
- modules/tmx_generator.py +284 -0
- modules/tracked_changes.py +900 -0
- modules/trados_docx_handler.py +430 -0
- modules/translation_memory.py +715 -0
- modules/translation_results_panel.py +2134 -0
- modules/translation_services.py +282 -0
- modules/unified_prompt_library.py +659 -0
- modules/unified_prompt_manager_qt.py +3951 -0
- modules/voice_commands.py +920 -0
- modules/voice_dictation.py +477 -0
- modules/voice_dictation_lite.py +249 -0
- supervertaler-1.9.153.dist-info/METADATA +896 -0
- supervertaler-1.9.153.dist-info/RECORD +85 -0
- supervertaler-1.9.153.dist-info/WHEEL +5 -0
- supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.153.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Find & Replace Module for Supervertaler (PyQt6)
|
|
3
|
+
|
|
4
|
+
This module provides enhanced Find & Replace functionality including:
|
|
5
|
+
- History dropdowns for recent search/replace terms
|
|
6
|
+
- Saveable F&R Sets for batch operations
|
|
7
|
+
- Import/Export of .svfr files
|
|
8
|
+
|
|
9
|
+
Classes:
|
|
10
|
+
- FindReplaceHistory: Manages and persists recent search/replace terms
|
|
11
|
+
- FindReplaceOperation: Single F&R operation with all settings
|
|
12
|
+
- FindReplaceSet: Collection of F&R operations
|
|
13
|
+
- FindReplaceSetsManager: UI widget for managing F&R sets
|
|
14
|
+
- HistoryComboBox: Editable combo box with history dropdown
|
|
15
|
+
|
|
16
|
+
Author: Michael Beijer
|
|
17
|
+
License: MIT
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
from dataclasses import dataclass, field, asdict
|
|
23
|
+
from typing import List, Optional, Callable
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from PyQt6.QtWidgets import (
|
|
27
|
+
QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
|
|
28
|
+
QPushButton, QComboBox, QLineEdit, QHeaderView, QAbstractItemView,
|
|
29
|
+
QMessageBox, QFileDialog, QInputDialog, QSplitter, QLabel, QCheckBox
|
|
30
|
+
)
|
|
31
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
|
32
|
+
from PyQt6.QtGui import QColor
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FindReplaceHistory:
|
|
36
|
+
"""Manages and persists recent find/replace terms."""
|
|
37
|
+
|
|
38
|
+
MAX_HISTORY = 20
|
|
39
|
+
|
|
40
|
+
def __init__(self, user_data_path: str):
|
|
41
|
+
self.user_data_path = Path(user_data_path)
|
|
42
|
+
self.history_file = self.user_data_path / "find_replace_history.json"
|
|
43
|
+
self.find_history: List[str] = []
|
|
44
|
+
self.replace_history: List[str] = []
|
|
45
|
+
self._load()
|
|
46
|
+
|
|
47
|
+
def _load(self):
|
|
48
|
+
"""Load history from file."""
|
|
49
|
+
if self.history_file.exists():
|
|
50
|
+
try:
|
|
51
|
+
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
52
|
+
data = json.load(f)
|
|
53
|
+
self.find_history = data.get('find', [])[:self.MAX_HISTORY]
|
|
54
|
+
self.replace_history = data.get('replace', [])[:self.MAX_HISTORY]
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def _save(self):
|
|
59
|
+
"""Save history to file."""
|
|
60
|
+
try:
|
|
61
|
+
self.user_data_path.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
with open(self.history_file, 'w', encoding='utf-8') as f:
|
|
63
|
+
json.dump({
|
|
64
|
+
'find': self.find_history[:self.MAX_HISTORY],
|
|
65
|
+
'replace': self.replace_history[:self.MAX_HISTORY]
|
|
66
|
+
}, f, ensure_ascii=False, indent=2)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def add_find(self, text: str):
|
|
71
|
+
"""Add a find term to history."""
|
|
72
|
+
if not text or not text.strip():
|
|
73
|
+
return
|
|
74
|
+
text = text.strip()
|
|
75
|
+
# Remove if exists, add to front
|
|
76
|
+
if text in self.find_history:
|
|
77
|
+
self.find_history.remove(text)
|
|
78
|
+
self.find_history.insert(0, text)
|
|
79
|
+
self.find_history = self.find_history[:self.MAX_HISTORY]
|
|
80
|
+
self._save()
|
|
81
|
+
|
|
82
|
+
def add_replace(self, text: str):
|
|
83
|
+
"""Add a replace term to history."""
|
|
84
|
+
if text is None:
|
|
85
|
+
return
|
|
86
|
+
text = text.strip() if text else ""
|
|
87
|
+
# Remove if exists, add to front
|
|
88
|
+
if text in self.replace_history:
|
|
89
|
+
self.replace_history.remove(text)
|
|
90
|
+
self.replace_history.insert(0, text)
|
|
91
|
+
self.replace_history = self.replace_history[:self.MAX_HISTORY]
|
|
92
|
+
self._save()
|
|
93
|
+
|
|
94
|
+
def add_operation(self, find_text: str, replace_text: str):
|
|
95
|
+
"""Add both find and replace terms."""
|
|
96
|
+
self.add_find(find_text)
|
|
97
|
+
self.add_replace(replace_text)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class FindReplaceOperation:
|
|
102
|
+
"""Single find/replace operation with all settings."""
|
|
103
|
+
find_text: str
|
|
104
|
+
replace_text: str = ""
|
|
105
|
+
search_in: str = "target" # "source", "target", "both"
|
|
106
|
+
match_mode: int = 0 # 0=anything, 1=whole words, 2=entire segment
|
|
107
|
+
case_sensitive: bool = False
|
|
108
|
+
enabled: bool = True
|
|
109
|
+
|
|
110
|
+
def to_dict(self) -> dict:
|
|
111
|
+
return asdict(self)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_dict(cls, data: dict) -> 'FindReplaceOperation':
|
|
115
|
+
return cls(**data)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class FindReplaceSet:
|
|
120
|
+
"""Collection of F&R operations that can be saved/loaded."""
|
|
121
|
+
name: str
|
|
122
|
+
operations: List[FindReplaceOperation] = field(default_factory=list)
|
|
123
|
+
|
|
124
|
+
def to_dict(self) -> dict:
|
|
125
|
+
return {
|
|
126
|
+
'name': self.name,
|
|
127
|
+
'operations': [op.to_dict() for op in self.operations]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_dict(cls, data: dict) -> 'FindReplaceSet':
|
|
132
|
+
ops = [FindReplaceOperation.from_dict(op) for op in data.get('operations', [])]
|
|
133
|
+
return cls(name=data.get('name', 'Unnamed Set'), operations=ops)
|
|
134
|
+
|
|
135
|
+
def add_operation(self, op: FindReplaceOperation):
|
|
136
|
+
self.operations.append(op)
|
|
137
|
+
|
|
138
|
+
def remove_operation(self, index: int):
|
|
139
|
+
if 0 <= index < len(self.operations):
|
|
140
|
+
del self.operations[index]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class HistoryComboBox(QComboBox):
|
|
144
|
+
"""Editable combo box with history dropdown."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, parent=None):
|
|
147
|
+
super().__init__(parent)
|
|
148
|
+
self.setEditable(True)
|
|
149
|
+
self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
|
|
150
|
+
self.setMaxVisibleItems(15)
|
|
151
|
+
self.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
|
|
152
|
+
self.setMinimumWidth(200)
|
|
153
|
+
|
|
154
|
+
def set_history(self, history: List[str]):
|
|
155
|
+
"""Update the dropdown items with history."""
|
|
156
|
+
current_text = self.currentText()
|
|
157
|
+
self.clear()
|
|
158
|
+
self.addItems(history)
|
|
159
|
+
self.setCurrentText(current_text)
|
|
160
|
+
|
|
161
|
+
def text(self) -> str:
|
|
162
|
+
"""Get current text (convenience method)."""
|
|
163
|
+
return self.currentText()
|
|
164
|
+
|
|
165
|
+
def setText(self, text: str):
|
|
166
|
+
"""Set current text (convenience method)."""
|
|
167
|
+
self.setCurrentText(text)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class FindReplaceSetsManager(QWidget):
|
|
171
|
+
"""UI widget for managing F&R sets."""
|
|
172
|
+
|
|
173
|
+
# Signals
|
|
174
|
+
operation_selected = pyqtSignal(object) # Emits FindReplaceOperation
|
|
175
|
+
run_set_requested = pyqtSignal(object) # Emits FindReplaceSet
|
|
176
|
+
set_selected = pyqtSignal(object) # Emits FindReplaceSet for batch run
|
|
177
|
+
|
|
178
|
+
def __init__(self, user_data_path: str, parent=None):
|
|
179
|
+
super().__init__(parent)
|
|
180
|
+
self.user_data_path = Path(user_data_path)
|
|
181
|
+
self.sets_dir = self.user_data_path / "find_replace_sets"
|
|
182
|
+
self.sets_dir.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
|
|
184
|
+
self.sets: List[FindReplaceSet] = []
|
|
185
|
+
self.current_set: Optional[FindReplaceSet] = None
|
|
186
|
+
|
|
187
|
+
self._setup_ui()
|
|
188
|
+
self._load_sets()
|
|
189
|
+
self._refresh_sets_table()
|
|
190
|
+
|
|
191
|
+
def _setup_ui(self):
|
|
192
|
+
"""Set up the UI layout."""
|
|
193
|
+
layout = QVBoxLayout(self)
|
|
194
|
+
layout.setContentsMargins(0, 5, 0, 0)
|
|
195
|
+
|
|
196
|
+
# Horizontal splitter for sets list and operations
|
|
197
|
+
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
198
|
+
|
|
199
|
+
# Left side: Saved F&R Sets
|
|
200
|
+
left_widget = QWidget()
|
|
201
|
+
left_layout = QVBoxLayout(left_widget)
|
|
202
|
+
left_layout.setContentsMargins(0, 0, 5, 0)
|
|
203
|
+
|
|
204
|
+
left_layout.addWidget(QLabel("<b>📁 Saved F&R Sets</b>"))
|
|
205
|
+
|
|
206
|
+
self.sets_table = QTableWidget()
|
|
207
|
+
self.sets_table.setColumnCount(2)
|
|
208
|
+
self.sets_table.setHorizontalHeaderLabels(["Name", "Operations"])
|
|
209
|
+
self.sets_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
|
210
|
+
self.sets_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
|
|
211
|
+
self.sets_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
|
212
|
+
self.sets_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
213
|
+
self.sets_table.itemSelectionChanged.connect(self._on_set_selected)
|
|
214
|
+
left_layout.addWidget(self.sets_table)
|
|
215
|
+
|
|
216
|
+
# Sets buttons
|
|
217
|
+
sets_btn_layout = QHBoxLayout()
|
|
218
|
+
|
|
219
|
+
new_set_btn = QPushButton("+ New Set")
|
|
220
|
+
new_set_btn.clicked.connect(self._create_new_set)
|
|
221
|
+
sets_btn_layout.addWidget(new_set_btn)
|
|
222
|
+
|
|
223
|
+
import_btn = QPushButton("📥 Import")
|
|
224
|
+
import_btn.clicked.connect(self._import_set)
|
|
225
|
+
sets_btn_layout.addWidget(import_btn)
|
|
226
|
+
|
|
227
|
+
left_layout.addLayout(sets_btn_layout)
|
|
228
|
+
|
|
229
|
+
splitter.addWidget(left_widget)
|
|
230
|
+
|
|
231
|
+
# Right side: Operations in selected set
|
|
232
|
+
right_widget = QWidget()
|
|
233
|
+
right_layout = QVBoxLayout(right_widget)
|
|
234
|
+
right_layout.setContentsMargins(5, 0, 0, 0)
|
|
235
|
+
|
|
236
|
+
self.ops_label = QLabel("<b>📋 Operations in: (none selected)</b>")
|
|
237
|
+
right_layout.addWidget(self.ops_label)
|
|
238
|
+
|
|
239
|
+
self.ops_table = QTableWidget()
|
|
240
|
+
self.ops_table.setColumnCount(5)
|
|
241
|
+
self.ops_table.setHorizontalHeaderLabels(["✓", "Find", "Replace", "Search in", "Match"])
|
|
242
|
+
self.ops_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
|
243
|
+
self.ops_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
|
244
|
+
self.ops_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
|
245
|
+
self.ops_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
|
246
|
+
self.ops_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
|
|
247
|
+
self.ops_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
|
248
|
+
self.ops_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
249
|
+
self.ops_table.cellDoubleClicked.connect(self._on_op_double_clicked)
|
|
250
|
+
self.ops_table.cellChanged.connect(self._on_op_cell_changed)
|
|
251
|
+
right_layout.addWidget(self.ops_table)
|
|
252
|
+
|
|
253
|
+
# Operations buttons
|
|
254
|
+
ops_btn_layout = QHBoxLayout()
|
|
255
|
+
|
|
256
|
+
add_op_btn = QPushButton("+ Add Operation")
|
|
257
|
+
add_op_btn.clicked.connect(self._add_empty_operation)
|
|
258
|
+
ops_btn_layout.addWidget(add_op_btn)
|
|
259
|
+
|
|
260
|
+
ops_btn_layout.addStretch()
|
|
261
|
+
|
|
262
|
+
run_all_btn = QPushButton("▶ Run All")
|
|
263
|
+
run_all_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
|
|
264
|
+
run_all_btn.clicked.connect(self._run_all_operations)
|
|
265
|
+
ops_btn_layout.addWidget(run_all_btn)
|
|
266
|
+
|
|
267
|
+
right_layout.addLayout(ops_btn_layout)
|
|
268
|
+
|
|
269
|
+
splitter.addWidget(right_widget)
|
|
270
|
+
splitter.setSizes([300, 500])
|
|
271
|
+
|
|
272
|
+
layout.addWidget(splitter)
|
|
273
|
+
|
|
274
|
+
def _load_sets(self):
|
|
275
|
+
"""Load all F&R sets from the sets directory."""
|
|
276
|
+
self.sets = []
|
|
277
|
+
for file_path in self.sets_dir.glob("*.svfr"):
|
|
278
|
+
try:
|
|
279
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
280
|
+
data = json.load(f)
|
|
281
|
+
fr_set = FindReplaceSet.from_dict(data)
|
|
282
|
+
self.sets.append(fr_set)
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
# If no sets exist, create a default one
|
|
287
|
+
if not self.sets:
|
|
288
|
+
default_set = FindReplaceSet(name="F&R Set 1")
|
|
289
|
+
self.sets.append(default_set)
|
|
290
|
+
self._save_set(default_set)
|
|
291
|
+
|
|
292
|
+
def _save_set(self, fr_set: FindReplaceSet):
|
|
293
|
+
"""Save a F&R set to file."""
|
|
294
|
+
safe_name = "".join(c if c.isalnum() or c in " _-" else "_" for c in fr_set.name)
|
|
295
|
+
file_path = self.sets_dir / f"{safe_name}.svfr"
|
|
296
|
+
try:
|
|
297
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
298
|
+
json.dump(fr_set.to_dict(), f, ensure_ascii=False, indent=2)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print(f"Error saving F&R set: {e}")
|
|
301
|
+
|
|
302
|
+
def _refresh_sets_table(self):
|
|
303
|
+
"""Refresh the sets table."""
|
|
304
|
+
self.sets_table.setRowCount(len(self.sets))
|
|
305
|
+
for i, fr_set in enumerate(self.sets):
|
|
306
|
+
name_item = QTableWidgetItem(fr_set.name)
|
|
307
|
+
name_item.setForeground(QColor("#1976D2")) # Blue color for names
|
|
308
|
+
self.sets_table.setItem(i, 0, name_item)
|
|
309
|
+
|
|
310
|
+
count_item = QTableWidgetItem(str(len(fr_set.operations)))
|
|
311
|
+
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
312
|
+
self.sets_table.setItem(i, 1, count_item)
|
|
313
|
+
|
|
314
|
+
# Select first set if none selected
|
|
315
|
+
if self.sets and not self.current_set:
|
|
316
|
+
self.sets_table.selectRow(0)
|
|
317
|
+
|
|
318
|
+
def _refresh_ops_table(self):
|
|
319
|
+
"""Refresh the operations table for the current set."""
|
|
320
|
+
self.ops_table.blockSignals(True)
|
|
321
|
+
|
|
322
|
+
if not self.current_set:
|
|
323
|
+
self.ops_table.setRowCount(0)
|
|
324
|
+
self.ops_label.setText("<b>📋 Operations in: (none selected)</b>")
|
|
325
|
+
self.ops_table.blockSignals(False)
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
self.ops_label.setText(f"<b>📋 Operations in: {self.current_set.name}</b>")
|
|
329
|
+
self.ops_table.setRowCount(len(self.current_set.operations))
|
|
330
|
+
|
|
331
|
+
for i, op in enumerate(self.current_set.operations):
|
|
332
|
+
# Enabled checkbox
|
|
333
|
+
enabled_item = QTableWidgetItem("✓" if op.enabled else "")
|
|
334
|
+
enabled_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
335
|
+
enabled_item.setFlags(enabled_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
336
|
+
enabled_item.setCheckState(Qt.CheckState.Checked if op.enabled else Qt.CheckState.Unchecked)
|
|
337
|
+
self.ops_table.setItem(i, 0, enabled_item)
|
|
338
|
+
|
|
339
|
+
# Find text
|
|
340
|
+
find_item = QTableWidgetItem(op.find_text)
|
|
341
|
+
self.ops_table.setItem(i, 1, find_item)
|
|
342
|
+
|
|
343
|
+
# Replace text
|
|
344
|
+
replace_item = QTableWidgetItem(op.replace_text)
|
|
345
|
+
self.ops_table.setItem(i, 2, replace_item)
|
|
346
|
+
|
|
347
|
+
# Search in - full text
|
|
348
|
+
search_in_map = {"source": "Source", "target": "Target", "both": "Both"}
|
|
349
|
+
search_item = QTableWidgetItem(search_in_map.get(op.search_in, "Target"))
|
|
350
|
+
self.ops_table.setItem(i, 3, search_item)
|
|
351
|
+
|
|
352
|
+
# Match mode - full text
|
|
353
|
+
match_map = {0: "Anything", 1: "Whole words", 2: "Entire segment"}
|
|
354
|
+
match_item = QTableWidgetItem(match_map.get(op.match_mode, "Anything"))
|
|
355
|
+
self.ops_table.setItem(i, 4, match_item)
|
|
356
|
+
|
|
357
|
+
self.ops_table.blockSignals(False)
|
|
358
|
+
|
|
359
|
+
def _on_set_selected(self):
|
|
360
|
+
"""Handle set selection in the sets table."""
|
|
361
|
+
selected_rows = self.sets_table.selectionModel().selectedRows()
|
|
362
|
+
if selected_rows:
|
|
363
|
+
index = selected_rows[0].row()
|
|
364
|
+
if 0 <= index < len(self.sets):
|
|
365
|
+
self.current_set = self.sets[index]
|
|
366
|
+
self._refresh_ops_table()
|
|
367
|
+
|
|
368
|
+
def _on_op_double_clicked(self, row: int, col: int):
|
|
369
|
+
"""Handle double-click on an operation to load it into the dialog."""
|
|
370
|
+
if self.current_set and 0 <= row < len(self.current_set.operations):
|
|
371
|
+
op = self.current_set.operations[row]
|
|
372
|
+
self.operation_selected.emit(op)
|
|
373
|
+
|
|
374
|
+
def _on_op_cell_changed(self, row: int, col: int):
|
|
375
|
+
"""Handle cell changes in the operations table."""
|
|
376
|
+
if not self.current_set or row >= len(self.current_set.operations):
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
op = self.current_set.operations[row]
|
|
380
|
+
item = self.ops_table.item(row, col)
|
|
381
|
+
|
|
382
|
+
if col == 0: # Enabled checkbox
|
|
383
|
+
op.enabled = item.checkState() == Qt.CheckState.Checked
|
|
384
|
+
elif col == 1: # Find text
|
|
385
|
+
op.find_text = item.text()
|
|
386
|
+
elif col == 2: # Replace text
|
|
387
|
+
op.replace_text = item.text()
|
|
388
|
+
|
|
389
|
+
self._save_set(self.current_set)
|
|
390
|
+
|
|
391
|
+
def _create_new_set(self):
|
|
392
|
+
"""Create a new F&R set."""
|
|
393
|
+
name, ok = QInputDialog.getText(self, "New F&R Set", "Enter set name:")
|
|
394
|
+
if ok and name.strip():
|
|
395
|
+
new_set = FindReplaceSet(name=name.strip())
|
|
396
|
+
self.sets.append(new_set)
|
|
397
|
+
self._save_set(new_set)
|
|
398
|
+
self._refresh_sets_table()
|
|
399
|
+
# Select the new set
|
|
400
|
+
self.sets_table.selectRow(len(self.sets) - 1)
|
|
401
|
+
|
|
402
|
+
def _import_set(self):
|
|
403
|
+
"""Import a F&R set from a .svfr file."""
|
|
404
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
405
|
+
self, "Import F&R Set", "", "Supervertaler F&R Sets (*.svfr);;All Files (*)"
|
|
406
|
+
)
|
|
407
|
+
if file_path:
|
|
408
|
+
try:
|
|
409
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
410
|
+
data = json.load(f)
|
|
411
|
+
fr_set = FindReplaceSet.from_dict(data)
|
|
412
|
+
self.sets.append(fr_set)
|
|
413
|
+
self._save_set(fr_set)
|
|
414
|
+
self._refresh_sets_table()
|
|
415
|
+
QMessageBox.information(self, "Import", f"Imported '{fr_set.name}' with {len(fr_set.operations)} operations.")
|
|
416
|
+
except Exception as e:
|
|
417
|
+
QMessageBox.warning(self, "Import Error", f"Failed to import: {e}")
|
|
418
|
+
|
|
419
|
+
def _add_empty_operation(self):
|
|
420
|
+
"""Add an empty operation to the current set."""
|
|
421
|
+
if not self.current_set:
|
|
422
|
+
QMessageBox.information(self, "No Set", "Please select or create a F&R set first.")
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
op = FindReplaceOperation(find_text="", replace_text="")
|
|
426
|
+
self.current_set.add_operation(op)
|
|
427
|
+
self._save_set(self.current_set)
|
|
428
|
+
self._refresh_ops_table()
|
|
429
|
+
self._refresh_sets_table() # Update operation count
|
|
430
|
+
|
|
431
|
+
def _run_all_operations(self):
|
|
432
|
+
"""Request to run all enabled operations in the current set."""
|
|
433
|
+
if not self.current_set:
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
enabled_ops = [op for op in self.current_set.operations if op.enabled and op.find_text]
|
|
437
|
+
if not enabled_ops:
|
|
438
|
+
QMessageBox.information(self, "Run All", "No enabled operations with find text.")
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
self.set_selected.emit(self.current_set)
|
|
442
|
+
|
|
443
|
+
def add_current_operation_to_set(self, op: FindReplaceOperation):
|
|
444
|
+
"""Add an operation to the current set (called from main dialog)."""
|
|
445
|
+
if not self.current_set:
|
|
446
|
+
# Create a default set if none exists
|
|
447
|
+
if not self.sets:
|
|
448
|
+
self._create_new_set()
|
|
449
|
+
if self.sets:
|
|
450
|
+
self.current_set = self.sets[0]
|
|
451
|
+
self.sets_table.selectRow(0)
|
|
452
|
+
|
|
453
|
+
if self.current_set:
|
|
454
|
+
self.current_set.add_operation(op)
|
|
455
|
+
self._save_set(self.current_set)
|
|
456
|
+
self._refresh_ops_table()
|
|
457
|
+
self._refresh_sets_table() # Update operation count
|