Skip to content

Commit b437911

Browse files
committed
Release v0.5.1
1 parent a7140bc commit b437911

2 files changed

Lines changed: 56 additions & 15 deletions

File tree

src/helvox/app.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import tkinter as tk
22
from pathlib import Path
3-
from tkinter import ttk
3+
from tkinter import messagebox, ttk
44

55
from helvox.ui.auto_resize_text import AutoResizingText
66
from helvox.ui.button import RoundedButton
@@ -464,7 +464,15 @@ def show_settings(self) -> None:
464464

465465
if result:
466466
self._apply_settings_result(result)
467-
self._persist_settings()
467+
try:
468+
self._persist_settings()
469+
except OSError as exc:
470+
messagebox.showerror(
471+
"Could not save settings",
472+
f"Failed to write config file:\n{exc}\n\n"
473+
"On macOS, try moving the app out of the DMG to a local folder first.",
474+
parent=self.root,
475+
)
468476

469477
self._refresh_ui_after_session_change()
470478

src/helvox/ui/settings.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from tkinter import filedialog, messagebox, ttk
55

66
from helvox.ui.tooltip import add_tooltip
7+
from helvox.utils.config_paths import portable_config_file, user_config_file
78
from helvox.utils.data import validate_samples_for_dialect
89
from helvox.utils.platform import app_font, recordings_dir
910
from helvox.utils.recorder import Recorder
@@ -170,7 +171,7 @@ def setup_ui(self) -> None:
170171
)
171172
info_label.grid(row=1, column=0, sticky="w", pady=(5, 0))
172173

173-
# Output / recordings folder (default: helvox next to app)
174+
# Output / recordings folder — only shown/editable in non-portable mode
174175
rec_frame = ttk.LabelFrame(tab_data, text="Output folder (recordings)", padding="15")
175176
rec_frame.grid(row=1, column=0, sticky="ew", padx=(10, 10), pady=(10, 0))
176177
rec_frame.columnconfigure(0, weight=1)
@@ -189,28 +190,35 @@ def setup_ui(self) -> None:
189190
)
190191
self.browse_out_btn.grid(row=0, column=1, pady=(0, 0), sticky="e")
191192

192-
ttk.Label(
193+
self.folder_info_label = ttk.Label(
193194
rec_frame,
194-
text="Default: next to the exe, in a helvox folder.",
195+
text="",
195196
style="Info.TLabel",
196-
).grid(row=1, column=0, sticky="w", pady=(5, 0))
197+
wraplength=560,
198+
)
199+
self.folder_info_label.grid(row=1, column=0, sticky="w", pady=(5, 0))
197200

198-
# Settings file: portable vs user profile
201+
# Portable mode
199202
settings_store_frame = ttk.LabelFrame(
200-
tab_data, text="Settings file (config.json)", padding="15"
203+
tab_data, text="Portable mode", padding="15"
201204
)
202205
settings_store_frame.grid(row=2, column=0, sticky="ew", padx=(10, 10), pady=(10, 0))
203206
settings_store_frame.columnconfigure(0, weight=1)
204207

205208
self.config_portable_var = tk.BooleanVar(value=self.recorder.config_portable)
206209
portable_check = ttk.Checkbutton(
207210
settings_store_frame,
208-
text="Save config.json next to the app (in the helvox folder)",
211+
text="Portable — store config and recordings in helvox/ next to the app",
209212
variable=self.config_portable_var,
210213
command=self.on_config_portable_toggle,
211214
)
212215
portable_check.grid(row=0, column=0, sticky="w")
213216

217+
self.config_path_label = ttk.Label(
218+
settings_store_frame, text="", style="Info.TLabel", wraplength=560
219+
)
220+
self.config_path_label.grid(row=1, column=0, sticky="w", pady=(4, 0))
221+
214222
options_frame = ttk.LabelFrame(tab_data, text="Options", padding="15")
215223
options_frame.grid(row=3, column=0, sticky="ew", padx=(10, 10), pady=(10, 0))
216224
options_frame.columnconfigure(0, weight=1)
@@ -326,12 +334,31 @@ def setup_ui(self) -> None:
326334
self.on_config_portable_toggle()
327335

328336
def on_config_portable_toggle(self) -> None:
329-
# Keep path fields editable in both modes.
330-
# NOTE: toggling the config location must NOT affect the output folder.
337+
is_portable = self.config_portable_var.get()
338+
339+
if is_portable:
340+
# Lock output folder to helvox/ next to the app.
341+
base = recordings_dir()
342+
self.folder_var.set(str(base))
343+
self.folder_input.configure(state="disabled")
344+
self.browse_out_btn.configure(state="disabled")
345+
self.folder_info_label.configure(
346+
text=f"Locked to: {base} (recordings go in <speaker>/ inside)"
347+
)
348+
self.config_path_label.configure(
349+
text=f"Config: {portable_config_file()}\n"
350+
f"Recordings: {base / '<speaker_id>'}"
351+
)
352+
else:
353+
self.folder_input.configure(state="normal")
354+
self.browse_out_btn.configure(state="normal")
355+
self.folder_info_label.configure(text="")
356+
self.config_path_label.configure(
357+
text=f"Config will be saved to: {user_config_file()}"
358+
)
359+
331360
self.file_input.configure(state="normal")
332-
self.folder_input.configure(state="normal")
333361
self.browse_file_btn.configure(state="normal")
334-
self.browse_out_btn.configure(state="normal")
335362

336363
def select_output_folder(self) -> None:
337364
raw = self.folder_var.get().strip() or str(recordings_dir())
@@ -449,12 +476,18 @@ def on_ok(self) -> None:
449476
if not self.validate_inputs():
450477
return
451478

452-
out_folder = self.folder_var.get().strip() or str(recordings_dir())
479+
is_portable = self.config_portable_var.get()
480+
# In portable mode the output folder is always recordings_dir() — locked.
481+
out_folder = (
482+
str(recordings_dir())
483+
if is_portable
484+
else self.folder_var.get().strip() or str(recordings_dir())
485+
)
453486
self.result = {
454487
"speaker_id": self.speaker_var.get().strip(),
455488
"speaker_dialect": self.dialect_var.get(),
456489
"output_folder": out_folder,
457-
"config_portable": self.config_portable_var.get(),
490+
"config_portable": is_portable,
458491
"device": self.device_var.get(),
459492
"enable_skip": self.enable_skip_var.get(),
460493
"input_file": self.file_var.get(),

0 commit comments

Comments
 (0)