Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions src/TTS_ka/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,16 @@ def _build_setup_tab(self, frm: Any) -> None:
).grid(row=1, column=0, sticky=tk.W, pady=(2, 10))

# --- Step 1: verify setup -------------------------------------------
ttk.Label(frm, text="1. Verify your setup", style="Heading.TLabel").grid(row=2, column=0, sticky=tk.W)
self._setup_report = _scrolled_text(frm, height=9, wrap=tk.WORD)
self._setup_report.grid(row=3, column=0, sticky=(tk.E, tk.W), pady=(4, 0))
step1 = ttk.Frame(frm)
step1.grid(row=2, column=0, sticky=tk.EW)
ttk.Label(step1, text="1. Verify your setup", style="Heading.TLabel").pack(side=tk.LEFT)
ttk.Button(step1, text="Re-check", command=self._on_setup_recheck).pack(side=tk.RIGHT)
# The report is the row that gives way, so steps 2 and 3 stay visible at the minimum size.
self._setup_report = _scrolled_text(frm, height=6, wrap=tk.WORD)
self._setup_report.grid(row=3, column=0, sticky=tk.NSEW, pady=(4, 0))
frm.rowconfigure(3, weight=1)
self._setup_report.insert("1.0", "Checking dependencies…")
self._setup_report.configure(state=tk.DISABLED)
ttk.Button(frm, text="Re-check", command=self._on_setup_recheck).grid(
row=4, column=0, sticky=tk.W, pady=(4, 0)
)

ttk.Separator(frm, orient=tk.HORIZONTAL).grid(
row=5, column=0, sticky=(tk.E, tk.W), pady=12
Expand All @@ -449,8 +451,11 @@ def _build_setup_tab(self, frm: Any) -> None:
)
self._setup_preview_btn.pack(side=tk.LEFT, padx=(8, 0))
self.setup_status = tk.StringVar(value="")
ttk.Label(frm, textvariable=self.setup_status, wraplength=520, justify=tk.LEFT).grid(
row=8, column=0, sticky=tk.W, pady=(4, 0)
status_lbl = ttk.Label(frm, textvariable=self.setup_status, wraplength=520, justify=tk.LEFT)
status_lbl.grid(row=8, column=0, sticky=tk.W, pady=(4, 0))
status_lbl.grid_remove() # an empty line would push step 3 down
self.setup_status.trace_add(
"write", lambda *_: status_lbl.grid() if self.setup_status.get() else status_lbl.grid_remove()
)

ttk.Separator(frm, orient=tk.HORIZONTAL).grid(
Expand Down Expand Up @@ -668,13 +673,14 @@ def _build_config_tab(self, frm: Any) -> None:

ttk.Checkbutton(
frm,
text="VLC RC playlist mode on Windows (vlc_rc; uncheck sets TTS_KA_VLC_RC=0)",
text="Windows: play all chunks in one VLC window (vlc_rc)",
variable=self.cfg_vlc_rc,
).grid(row=7, column=0, columnspan=2, sticky=tk.W)
).grid(row=7, column=0, columnspan=3, sticky=tk.W)

help_txt = (
"Saved keys match ~/.tts_config.json: lang (Speak tab), output, chunk_seconds, "
"parallel, no_play, stream, no_gui, no_turbo, vlc_rc."
"parallel, no_play, stream, no_gui, no_turbo, vlc_rc. "
"Unticking the VLC option sets TTS_KA_VLC_RC=0 (one VLC per chunk)."
)
ttk.Label(frm, text=help_txt, wraplength=480, justify=tk.LEFT).grid(
row=8, column=0, columnspan=3, sticky=tk.W, pady=(12, 0)
Expand Down
83 changes: 83 additions & 0 deletions tests/test_gui_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,86 @@ def test_speak_tab_file_row_and_player_window_follow_their_toggles() -> None:
assert not app._player_window_cb.instate(["disabled"])
finally:
app.root.destroy()


def test_setup_tab_fits_at_minimum_size() -> None:
"""At the window's minimum size, the last Setup step must still be on screen."""
tk = pytest.importorskip("tkinter")
try:
probe = tk.Tk()
probe.destroy()
except tk.TclError:
pytest.skip("no display available")

from tkinter import ttk

from TTS_ka import gui

with patch("TTS_ka.deps.collect_dep_rows", return_value=[]), \
patch("TTS_ka.deps.format_dep_report", return_value="ok"), \
patch("TTS_ka.native_hotkeys.NativeHotkeyManager", MagicMock()), \
patch.object(gui.TTSSpeakApp, "_run_setup_check_async"):
app = gui.TTSSpeakApp({"lang": "en"})
try:
w, h = app.root.minsize()
app.root.geometry(f"{w}x{h}")
app.root.update()
tab = app.root.nametowidget(app._nb.tabs()[0])
go = next(
c for c in tab.winfo_children()
if isinstance(c, ttk.Button) and c.cget("text").startswith("Go to Speak")
)
assert go.winfo_y() + go.winfo_height() <= tab.winfo_height()
finally:
app.root.destroy()


def test_no_tab_cuts_off_text_at_the_minimum_window() -> None:
"""At the minimum size every label / button / check / radio gets its full size inside its tab.

Text boxes and entries are left out: they are meant to shrink.
"""
tk = pytest.importorskip("tkinter")
try:
probe = tk.Tk()
probe.destroy()
except tk.TclError:
pytest.skip("no display available")

from TTS_ka import gui

fixed = {"TLabel", "TButton", "TCheckbutton", "TRadiobutton"}

def descendants(w):
for c in w.winfo_children():
yield c
yield from descendants(c)

with patch("TTS_ka.deps.collect_dep_rows", return_value=[]), \
patch("TTS_ka.deps.format_dep_report", return_value="ok"), \
patch("TTS_ka.native_hotkeys.NativeHotkeyManager", MagicMock()), \
patch.object(gui.TTSSpeakApp, "_run_setup_check_async"):
app = gui.TTSSpeakApp({"lang": "en"})
try:
w, h = app.root.minsize()
app.root.geometry(f"{w}x{h}")
cut = []
for tab_id in app._nb.tabs():
app._nb.select(tab_id)
app.root.update()
tab = app.root.nametowidget(tab_id)
right = tab.winfo_rootx() + tab.winfo_width()
bottom = tab.winfo_rooty() + tab.winfo_height()
for c in descendants(tab):
if c.winfo_class() not in fixed or not c.winfo_ismapped():
continue
if (
c.winfo_width() < c.winfo_reqwidth()
or c.winfo_height() < c.winfo_reqheight()
or c.winfo_rootx() + c.winfo_width() > right
or c.winfo_rooty() + c.winfo_height() > bottom
):
cut.append((app._nb.tab(tab_id, "text"), c.winfo_class(), str(c.cget("text"))[:40]))
assert not cut, cut
finally:
app.root.destroy()
Loading