supervertaler 1.9.163__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.
- Supervertaler.py +48473 -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 +1911 -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 +351 -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 +1176 -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.163.dist-info/METADATA +906 -0
- supervertaler-1.9.163.dist-info/RECORD +85 -0
- supervertaler-1.9.163.dist-info/WHEEL +5 -0
- supervertaler-1.9.163.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.163.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.163.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration Manager for Supervertaler
|
|
3
|
+
Handles user_data folder location, first-time setup, and configuration persistence.
|
|
4
|
+
|
|
5
|
+
Author: Michael Beijer
|
|
6
|
+
License: MIT
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import shutil
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigManager:
|
|
17
|
+
"""
|
|
18
|
+
Manages Supervertaler configuration and user_data paths.
|
|
19
|
+
|
|
20
|
+
MODES:
|
|
21
|
+
- Dev mode: .supervertaler.local exists → uses user_data_private/ folder (git-ignored)
|
|
22
|
+
- User mode: No .supervertaler.local → uses ~/.supervertaler_config.json to store path
|
|
23
|
+
|
|
24
|
+
Stores configuration in home directory as .supervertaler_config.json
|
|
25
|
+
Allows users to choose their own user_data folder location.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
CONFIG_FILENAME = ".supervertaler_config.json"
|
|
29
|
+
DEFAULT_USER_DATA_FOLDER = "Supervertaler_Data"
|
|
30
|
+
DEV_MODE_FLAG = ".supervertaler.local"
|
|
31
|
+
API_KEYS_EXAMPLE_FILENAME = "api_keys.example.txt"
|
|
32
|
+
API_KEYS_FILENAME = "api_keys.txt"
|
|
33
|
+
|
|
34
|
+
# Folder structure that must exist in user_data directory
|
|
35
|
+
REQUIRED_FOLDERS = [
|
|
36
|
+
# Note: Old numbered folders (1_System_Prompts, 2_Domain_Prompts, etc.) are deprecated
|
|
37
|
+
# Migration moves them to unified Library structure
|
|
38
|
+
"prompt_library/domain_expertise",
|
|
39
|
+
"prompt_library/project_prompts",
|
|
40
|
+
"prompt_library/style_guides",
|
|
41
|
+
"resources/termbases",
|
|
42
|
+
"resources/tms",
|
|
43
|
+
"resources/non_translatables",
|
|
44
|
+
"resources/segmentation_rules",
|
|
45
|
+
"projects",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
"""Initialize ConfigManager."""
|
|
50
|
+
self.dev_mode = self._is_dev_mode()
|
|
51
|
+
self.script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
52
|
+
self.config_path = self._get_config_file_path()
|
|
53
|
+
self.config = self._load_config()
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _is_dev_mode() -> bool:
|
|
57
|
+
"""Check if running in dev mode (looking for .supervertaler.local flag)."""
|
|
58
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
59
|
+
repo_root = os.path.dirname(script_dir) # Go up one level from modules/
|
|
60
|
+
dev_flag_path = os.path.join(repo_root, ConfigManager.DEV_MODE_FLAG)
|
|
61
|
+
return os.path.exists(dev_flag_path)
|
|
62
|
+
|
|
63
|
+
def _get_config_file_path(self) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Get the full path to the config file.
|
|
66
|
+
|
|
67
|
+
Dev mode: No config file needed (uses user_data_private/)
|
|
68
|
+
User mode: ~/.supervertaler_config.json
|
|
69
|
+
"""
|
|
70
|
+
if self.dev_mode:
|
|
71
|
+
return None # Dev mode doesn't use config file
|
|
72
|
+
home = str(Path.home())
|
|
73
|
+
return os.path.join(home, ConfigManager.CONFIG_FILENAME)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _get_default_user_data_path() -> str:
|
|
77
|
+
"""Get the default suggested user_data path."""
|
|
78
|
+
home = str(Path.home())
|
|
79
|
+
return os.path.join(home, ConfigManager.DEFAULT_USER_DATA_FOLDER)
|
|
80
|
+
|
|
81
|
+
def _load_config(self) -> dict:
|
|
82
|
+
"""Load configuration from file. Return empty dict if file doesn't exist."""
|
|
83
|
+
# Dev mode doesn't use config file
|
|
84
|
+
if self.dev_mode:
|
|
85
|
+
return {}
|
|
86
|
+
|
|
87
|
+
if os.path.exists(self.config_path):
|
|
88
|
+
try:
|
|
89
|
+
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
90
|
+
return json.load(f)
|
|
91
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
92
|
+
print(f"[Config] Error loading config: {e}. Using defaults.")
|
|
93
|
+
return {}
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
def _save_config(self) -> bool:
|
|
97
|
+
"""Save configuration to file. Return True if successful."""
|
|
98
|
+
# Dev mode doesn't use config file
|
|
99
|
+
if self.dev_mode:
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
if self.config_path is None:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
107
|
+
json.dump(self.config, f, indent=2, ensure_ascii=False)
|
|
108
|
+
return True
|
|
109
|
+
except IOError as e:
|
|
110
|
+
print(f"[Config] Error saving config: {e}")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def is_first_launch(self) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Check if this is the first launch (no user_data path set).
|
|
116
|
+
|
|
117
|
+
Dev mode: Always False (dev doesn't need first-launch wizard)
|
|
118
|
+
User mode: True if no path in config
|
|
119
|
+
"""
|
|
120
|
+
if self.dev_mode:
|
|
121
|
+
return False
|
|
122
|
+
return 'user_data_path' not in self.config or not self.config['user_data_path']
|
|
123
|
+
|
|
124
|
+
def get_user_data_path(self) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Get the current user_data path.
|
|
127
|
+
|
|
128
|
+
Dev mode: Returns ./user_data_private/ (in repo root)
|
|
129
|
+
User mode: Returns configured path from ~/.supervertaler_config.json
|
|
130
|
+
|
|
131
|
+
If not configured, returns default suggestion (doesn't create it).
|
|
132
|
+
Use ensure_user_data_exists() to create the folder.
|
|
133
|
+
"""
|
|
134
|
+
if self.dev_mode:
|
|
135
|
+
# Dev mode: use user_data_private folder
|
|
136
|
+
repo_root = os.path.dirname(self.script_dir)
|
|
137
|
+
return os.path.join(repo_root, "user_data_private")
|
|
138
|
+
|
|
139
|
+
# User mode: use configured path
|
|
140
|
+
if 'user_data_path' in self.config and self.config['user_data_path']:
|
|
141
|
+
return self.config['user_data_path']
|
|
142
|
+
return self._get_default_user_data_path()
|
|
143
|
+
|
|
144
|
+
def set_user_data_path(self, path: str) -> Tuple[bool, str]:
|
|
145
|
+
"""
|
|
146
|
+
Set the user_data path and save configuration.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
path: Full path to user_data folder
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Tuple of (success: bool, message: str)
|
|
153
|
+
"""
|
|
154
|
+
# Validate path
|
|
155
|
+
is_valid, error_msg = self._validate_path(path)
|
|
156
|
+
if not is_valid:
|
|
157
|
+
return False, error_msg
|
|
158
|
+
|
|
159
|
+
# Normalize path
|
|
160
|
+
path = os.path.normpath(path)
|
|
161
|
+
|
|
162
|
+
# Save configuration
|
|
163
|
+
self.config['user_data_path'] = path
|
|
164
|
+
self.config['last_modified'] = str(Path.ctime(Path(self.config_path))) if os.path.exists(self.config_path) else None
|
|
165
|
+
|
|
166
|
+
if self._save_config():
|
|
167
|
+
return True, f"User data path set to: {path}"
|
|
168
|
+
else:
|
|
169
|
+
return False, "Failed to save configuration"
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def _validate_path(path: str) -> Tuple[bool, str]:
|
|
173
|
+
"""
|
|
174
|
+
Validate that a path is suitable for user_data.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Tuple of (is_valid: bool, error_message: str)
|
|
178
|
+
"""
|
|
179
|
+
if not path or not isinstance(path, str):
|
|
180
|
+
return False, "Path must be a non-empty string"
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
path_obj = Path(path)
|
|
184
|
+
|
|
185
|
+
# Try to create the path
|
|
186
|
+
path_obj.mkdir(parents=True, exist_ok=True)
|
|
187
|
+
|
|
188
|
+
# Check if writable
|
|
189
|
+
test_file = path_obj / ".supervertaler_test"
|
|
190
|
+
test_file.touch()
|
|
191
|
+
test_file.unlink()
|
|
192
|
+
|
|
193
|
+
return True, ""
|
|
194
|
+
except PermissionError:
|
|
195
|
+
return False, f"Permission denied: Cannot write to {path}"
|
|
196
|
+
except OSError as e:
|
|
197
|
+
return False, f"Invalid path: {e}"
|
|
198
|
+
|
|
199
|
+
def ensure_user_data_exists(self, user_data_path: Optional[str] = None) -> Tuple[bool, str]:
|
|
200
|
+
"""
|
|
201
|
+
Ensure user_data folder exists with proper structure.
|
|
202
|
+
|
|
203
|
+
Creates all required subdirectories if they don't exist.
|
|
204
|
+
Also copies api_keys.example.txt → api_keys.txt if not present.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
user_data_path: Optional specific path. If None, uses configured path.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Tuple of (success: bool, message: str)
|
|
211
|
+
"""
|
|
212
|
+
if user_data_path is None:
|
|
213
|
+
user_data_path = self.get_user_data_path()
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# Create root user_data folder
|
|
217
|
+
Path(user_data_path).mkdir(parents=True, exist_ok=True)
|
|
218
|
+
|
|
219
|
+
# Create all required subdirectories
|
|
220
|
+
for folder in self.REQUIRED_FOLDERS:
|
|
221
|
+
folder_path = os.path.join(user_data_path, folder)
|
|
222
|
+
Path(folder_path).mkdir(parents=True, exist_ok=True)
|
|
223
|
+
|
|
224
|
+
# Copy api_keys.example.txt if it exists and api_keys.txt doesn't
|
|
225
|
+
self._setup_api_keys(user_data_path)
|
|
226
|
+
|
|
227
|
+
return True, f"User data folder structure created at: {user_data_path}"
|
|
228
|
+
except Exception as e:
|
|
229
|
+
return False, f"Failed to create user_data structure: {e}"
|
|
230
|
+
|
|
231
|
+
def _setup_api_keys(self, user_data_path: str) -> Tuple[bool, str]:
|
|
232
|
+
"""
|
|
233
|
+
Copy api_keys.example.txt to api_keys.txt in user_data folder.
|
|
234
|
+
|
|
235
|
+
Only creates if api_keys.txt doesn't already exist.
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
# Get paths
|
|
239
|
+
repo_root = os.path.dirname(self.script_dir)
|
|
240
|
+
example_source = os.path.join(repo_root, self.API_KEYS_EXAMPLE_FILENAME)
|
|
241
|
+
api_keys_dest = os.path.join(user_data_path, self.API_KEYS_FILENAME)
|
|
242
|
+
|
|
243
|
+
# If api_keys.txt already exists, nothing to do
|
|
244
|
+
if os.path.exists(api_keys_dest):
|
|
245
|
+
return True, "api_keys.txt already exists"
|
|
246
|
+
|
|
247
|
+
# If example file exists, copy it
|
|
248
|
+
if os.path.exists(example_source):
|
|
249
|
+
shutil.copy2(example_source, api_keys_dest)
|
|
250
|
+
print(f"[Config] Created {api_keys_dest} from template")
|
|
251
|
+
return True, f"Created api_keys.txt from template"
|
|
252
|
+
else:
|
|
253
|
+
# Create empty api_keys.txt with instructions
|
|
254
|
+
with open(api_keys_dest, 'w', encoding='utf-8') as f:
|
|
255
|
+
f.write("# API Keys Configuration\n")
|
|
256
|
+
f.write("# Add your API keys here in the format: KEY_NAME=value\n")
|
|
257
|
+
f.write("# Example:\n")
|
|
258
|
+
f.write("# OPENAI_API_KEY=sk-...\n")
|
|
259
|
+
f.write("# ANTHROPIC_API_KEY=sk-ant-...\n\n")
|
|
260
|
+
print(f"[Config] Created empty {api_keys_dest} with instructions")
|
|
261
|
+
return True, "Created api_keys.txt with instructions"
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"[Config] Error setting up api_keys: {e}")
|
|
264
|
+
return False, f"Failed to setup api_keys.txt: {e}"
|
|
265
|
+
|
|
266
|
+
def get_subfolder_path(self, subfolder: str) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Get the full path to a subfolder in user_data.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
config.get_subfolder_path('resources/tms')
|
|
272
|
+
-> '/home/user/Supervertaler/resources/tms'
|
|
273
|
+
"""
|
|
274
|
+
user_data_path = self.get_user_data_path()
|
|
275
|
+
full_path = os.path.join(user_data_path, subfolder)
|
|
276
|
+
|
|
277
|
+
# Ensure subfolder exists
|
|
278
|
+
Path(full_path).mkdir(parents=True, exist_ok=True)
|
|
279
|
+
|
|
280
|
+
return full_path
|
|
281
|
+
|
|
282
|
+
def get_existing_user_data_folder(self) -> Optional[str]:
|
|
283
|
+
"""
|
|
284
|
+
Detect if there's existing user_data in the script directory (from development).
|
|
285
|
+
|
|
286
|
+
Returns path if found, None otherwise.
|
|
287
|
+
"""
|
|
288
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
289
|
+
old_user_data_path = os.path.join(script_dir, "user_data")
|
|
290
|
+
|
|
291
|
+
if os.path.exists(old_user_data_path) and os.path.isdir(old_user_data_path):
|
|
292
|
+
# Check if it has any content
|
|
293
|
+
if os.listdir(old_user_data_path):
|
|
294
|
+
return old_user_data_path
|
|
295
|
+
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def migrate_user_data(self, old_path: str, new_path: str) -> Tuple[bool, str]:
|
|
299
|
+
"""
|
|
300
|
+
Migrate user_data from old location to new location.
|
|
301
|
+
|
|
302
|
+
Also handles migration of api_keys.txt if it exists in old location.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
old_path: Current user_data location
|
|
306
|
+
new_path: New user_data location
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Tuple of (success: bool, message: str)
|
|
310
|
+
"""
|
|
311
|
+
if not os.path.exists(old_path):
|
|
312
|
+
return False, f"Old path does not exist: {old_path}"
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# Ensure new location exists
|
|
316
|
+
Path(new_path).mkdir(parents=True, exist_ok=True)
|
|
317
|
+
|
|
318
|
+
# Move all items from old to new
|
|
319
|
+
files_moved = 0
|
|
320
|
+
for item in os.listdir(old_path):
|
|
321
|
+
old_item_path = os.path.join(old_path, item)
|
|
322
|
+
new_item_path = os.path.join(new_path, item)
|
|
323
|
+
|
|
324
|
+
# Skip if item already exists at destination
|
|
325
|
+
if os.path.exists(new_item_path):
|
|
326
|
+
print(f"[Migration] Skipping (exists): {item}")
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
if os.path.isdir(old_item_path):
|
|
331
|
+
shutil.copytree(old_item_path, new_item_path)
|
|
332
|
+
else:
|
|
333
|
+
shutil.copy2(old_item_path, new_item_path)
|
|
334
|
+
files_moved += 1
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"[Migration] Error moving {item}: {e}")
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
return True, f"Migrated {files_moved} items from {old_path} to {new_path}"
|
|
340
|
+
except Exception as e:
|
|
341
|
+
return False, f"Migration failed: {e}"
|
|
342
|
+
|
|
343
|
+
def migrate_api_keys_from_installation(self, user_data_path: str) -> Tuple[bool, str]:
|
|
344
|
+
"""
|
|
345
|
+
Migrate api_keys.txt from installation folder to user_data folder if it exists.
|
|
346
|
+
|
|
347
|
+
This handles migration for users upgrading from older versions.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
user_data_path: Target user_data folder
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Tuple of (success: bool, message: str)
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
repo_root = os.path.dirname(self.script_dir)
|
|
357
|
+
old_api_keys = os.path.join(repo_root, self.API_KEYS_FILENAME)
|
|
358
|
+
new_api_keys = os.path.join(user_data_path, self.API_KEYS_FILENAME)
|
|
359
|
+
|
|
360
|
+
# If old api_keys.txt exists and new one doesn't, move it
|
|
361
|
+
if os.path.exists(old_api_keys) and not os.path.exists(new_api_keys):
|
|
362
|
+
shutil.copy2(old_api_keys, new_api_keys)
|
|
363
|
+
print(f"[Migration] Migrated api_keys.txt to {new_api_keys}")
|
|
364
|
+
return True, f"Migrated api_keys.txt to user_data folder"
|
|
365
|
+
|
|
366
|
+
return True, "api_keys.txt migration not needed"
|
|
367
|
+
except Exception as e:
|
|
368
|
+
print(f"[Migration] Error migrating api_keys.txt: {e}")
|
|
369
|
+
return False, f"Failed to migrate api_keys.txt: {e}"
|
|
370
|
+
|
|
371
|
+
def validate_current_path(self) -> Tuple[bool, str]:
|
|
372
|
+
"""
|
|
373
|
+
Validate that the currently configured path is still valid.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Tuple of (is_valid: bool, error_message: str)
|
|
377
|
+
"""
|
|
378
|
+
user_data_path = self.get_user_data_path()
|
|
379
|
+
|
|
380
|
+
# Check if path exists and is writable
|
|
381
|
+
if not os.path.exists(user_data_path):
|
|
382
|
+
return False, f"User data path no longer exists: {user_data_path}"
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
# Try to write test file
|
|
386
|
+
test_file = os.path.join(user_data_path, ".supervertaler_test")
|
|
387
|
+
Path(test_file).touch()
|
|
388
|
+
Path(test_file).unlink()
|
|
389
|
+
return True, ""
|
|
390
|
+
except Exception as e:
|
|
391
|
+
return False, f"User data path is not writable: {e}"
|
|
392
|
+
|
|
393
|
+
def get_preferences_path(self) -> str:
|
|
394
|
+
"""Get the path to the UI preferences file."""
|
|
395
|
+
user_data_path = self.get_user_data_path()
|
|
396
|
+
return os.path.join(user_data_path, 'ui_preferences.json')
|
|
397
|
+
|
|
398
|
+
def load_preferences(self) -> dict:
|
|
399
|
+
"""Load UI preferences from file."""
|
|
400
|
+
prefs_path = self.get_preferences_path()
|
|
401
|
+
if os.path.exists(prefs_path):
|
|
402
|
+
try:
|
|
403
|
+
with open(prefs_path, 'r', encoding='utf-8') as f:
|
|
404
|
+
return json.load(f)
|
|
405
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
406
|
+
print(f"[Config] Error loading preferences: {e}")
|
|
407
|
+
return {}
|
|
408
|
+
|
|
409
|
+
def save_preferences(self, preferences: dict) -> bool:
|
|
410
|
+
"""Save UI preferences to file."""
|
|
411
|
+
prefs_path = self.get_preferences_path()
|
|
412
|
+
try:
|
|
413
|
+
# Ensure directory exists
|
|
414
|
+
os.makedirs(os.path.dirname(prefs_path), exist_ok=True)
|
|
415
|
+
with open(prefs_path, 'w', encoding='utf-8') as f:
|
|
416
|
+
json.dump(preferences, f, indent=2, ensure_ascii=False)
|
|
417
|
+
return True
|
|
418
|
+
except IOError as e:
|
|
419
|
+
print(f"[Config] Error saving preferences: {e}")
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
def get_all_config_info(self) -> dict:
|
|
423
|
+
"""Get all configuration information for debugging."""
|
|
424
|
+
return {
|
|
425
|
+
'config_file': self.config_path,
|
|
426
|
+
'user_data_path': self.get_user_data_path(),
|
|
427
|
+
'is_first_launch': self.is_first_launch(),
|
|
428
|
+
'config': self.config,
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def get_last_directory(self) -> str:
|
|
432
|
+
"""
|
|
433
|
+
Get the last directory used in file dialogs.
|
|
434
|
+
Returns empty string if no directory has been saved yet.
|
|
435
|
+
"""
|
|
436
|
+
return self.config.get('last_directory', '')
|
|
437
|
+
|
|
438
|
+
def set_last_directory(self, directory: str) -> None:
|
|
439
|
+
"""
|
|
440
|
+
Save the last directory used in file dialogs.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
directory: Full path to the directory to remember
|
|
444
|
+
"""
|
|
445
|
+
if directory and os.path.isdir(directory):
|
|
446
|
+
self.config['last_directory'] = os.path.normpath(directory)
|
|
447
|
+
self._save_config()
|
|
448
|
+
|
|
449
|
+
def update_last_directory_from_file(self, file_path: str) -> None:
|
|
450
|
+
"""
|
|
451
|
+
Extract and save the directory from a file path.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
file_path: Full path to a file
|
|
455
|
+
"""
|
|
456
|
+
if file_path:
|
|
457
|
+
directory = os.path.dirname(file_path)
|
|
458
|
+
self.set_last_directory(directory)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# Convenience function for easy access
|
|
462
|
+
_config_manager = None
|
|
463
|
+
|
|
464
|
+
def get_config_manager() -> ConfigManager:
|
|
465
|
+
"""Get or create the global ConfigManager instance."""
|
|
466
|
+
global _config_manager
|
|
467
|
+
if _config_manager is None:
|
|
468
|
+
_config_manager = ConfigManager()
|
|
469
|
+
return _config_manager
|