settings: lazy-build tabs (instant open); move connection dots beside the field; single-instance Settings dialog; Release 1.9.1
- gtksettings: build each notebook tab on first view instead of all up front, so the dialog opens instantly (was ~1.3s building Input/Benchmark file-choosers); _collect() force-builds unvisited tabs before saving so no field is missed. - gtksettings: connection dot now sits left of the URL entry (like the Engines tab) instead of at the far right. - gtkui: open_settings raises the existing dialog instead of opening a second. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
27cf81de46
commit
a472ce62ba
@ -9,6 +9,21 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.9.1] - 2026-06-08
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Settings opens instantly.** Each tab's contents are now built the first time
|
||||||
|
you view it instead of all up front, so the dialog no longer pauses ~1.3s
|
||||||
|
constructing the file-choosers in the Input/Benchmark tabs. Saving force-builds
|
||||||
|
any tab you didn't visit first, so no field is ever missed.
|
||||||
|
- **Connection dots moved beside their field.** The Wakeword and TTS reachability
|
||||||
|
dots now sit just left of the URL entry (matching the Engines tab) instead of
|
||||||
|
at the far right of the row.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Settings could be opened more than once.** Choosing Settings while it's
|
||||||
|
already open now raises the existing window instead of stacking a second copy.
|
||||||
|
|
||||||
## [1.9.0] - 2026-06-08
|
## [1.9.0] - 2026-06-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host
|
|||||||
(not in a container) so it can type into any application via xdotool.
|
(not in a container) so it can type into any application via xdotool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "1.9.0"
|
__version__ = "1.9.1"
|
||||||
|
|||||||
@ -256,6 +256,8 @@ def _url_field(parent: Gtk.Box, label: str, placeholder: str, on_reload,
|
|||||||
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
||||||
lbl = Gtk.Label(label=label, xalign=0.0); lbl.set_size_request(130, -1)
|
lbl = Gtk.Label(label=label, xalign=0.0); lbl.set_size_request(130, -1)
|
||||||
row.pack_start(lbl, False, False, 0)
|
row.pack_start(lbl, False, False, 0)
|
||||||
|
if dot is not None:
|
||||||
|
row.pack_start(dot, False, False, 0) # connection dot beside the field (left), like Engines
|
||||||
e = Gtk.Entry(); e.set_hexpand(True)
|
e = Gtk.Entry(); e.set_hexpand(True)
|
||||||
if placeholder:
|
if placeholder:
|
||||||
e.set_placeholder_text(placeholder)
|
e.set_placeholder_text(placeholder)
|
||||||
@ -272,8 +274,6 @@ def _url_field(parent: Gtk.Box, label: str, placeholder: str, on_reload,
|
|||||||
btn.get_accessible().set_name("Refresh models")
|
btn.get_accessible().set_name("Refresh models")
|
||||||
btn.connect("clicked", lambda _b: on_reload())
|
btn.connect("clicked", lambda _b: on_reload())
|
||||||
row.pack_start(btn, False, False, 0)
|
row.pack_start(btn, False, False, 0)
|
||||||
if dot is not None:
|
|
||||||
row.pack_start(dot, False, False, 0)
|
|
||||||
parent.pack_start(row, False, False, 0)
|
parent.pack_start(row, False, False, 0)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
@ -340,13 +340,22 @@ class SettingsDialog:
|
|||||||
|
|
||||||
nb = Gtk.Notebook()
|
nb = Gtk.Notebook()
|
||||||
self.dlg.get_content_area().pack_start(nb, True, True, 0)
|
self.dlg.get_content_area().pack_start(nb, True, True, 0)
|
||||||
self._build_presets(_page(nb, "Presets"))
|
# Build each tab lazily on first view so the dialog opens instantly. The
|
||||||
self._build_engines(_page(nb, "Engines"))
|
# heavy bits are the Gtk.FileChooserButton pickers in Input/Benchmark
|
||||||
self._build_input(_page(nb, "Input"))
|
# (~0.2s each to construct); deferring them until you visit that tab keeps
|
||||||
self._build_general(_page(nb, "General"))
|
# the initial open near-instant. _collect() force-builds any unvisited tab
|
||||||
self._build_benchmark(_page(nb, "Benchmark"))
|
# before saving, so no field is ever missed.
|
||||||
self._build_log(_page(nb, "Log"))
|
self._pending_tabs: dict = {}
|
||||||
self._build_about(_page(nb, "About"))
|
for title, builder in (("Presets", self._build_presets),
|
||||||
|
("Engines", self._build_engines),
|
||||||
|
("Input", self._build_input),
|
||||||
|
("General", self._build_general),
|
||||||
|
("Benchmark", self._build_benchmark),
|
||||||
|
("Log", self._build_log),
|
||||||
|
("About", self._build_about)):
|
||||||
|
self._pending_tabs[_page(nb, title)] = builder
|
||||||
|
self._build_tab(nb.get_nth_page(0)) # the visible tab, eagerly
|
||||||
|
nb.connect("switch-page", lambda _nb, page, _n: self._build_tab(page))
|
||||||
|
|
||||||
self._bind_entry = None
|
self._bind_entry = None
|
||||||
self._bind_pressed: list[str] = []
|
self._bind_pressed: list[str] = []
|
||||||
@ -355,6 +364,18 @@ class SettingsDialog:
|
|||||||
self.dlg.connect("response", self._on_response)
|
self.dlg.connect("response", self._on_response)
|
||||||
self.dlg.connect("destroy", lambda *_: self._cleanup())
|
self.dlg.connect("destroy", lambda *_: self._cleanup())
|
||||||
|
|
||||||
|
def _build_tab(self, page: Gtk.Box) -> None:
|
||||||
|
"""Build a notebook tab's contents the first time it's shown (lazy)."""
|
||||||
|
builder = self._pending_tabs.pop(page, None)
|
||||||
|
if builder is not None:
|
||||||
|
builder(page)
|
||||||
|
page.show_all() # the dialog may already be on-screen
|
||||||
|
|
||||||
|
def _force_build_tabs(self) -> None:
|
||||||
|
"""Build any not-yet-visited tabs so _collect() sees every field."""
|
||||||
|
for page in list(self._pending_tabs):
|
||||||
|
self._build_tab(page)
|
||||||
|
|
||||||
# ===== Presets ==========================================================
|
# ===== Presets ==========================================================
|
||||||
def _build_presets(self, page: Gtk.Box) -> None:
|
def _build_presets(self, page: Gtk.Box) -> None:
|
||||||
_infobox(page, "Presets are your dictation actions. Pick one to edit it, or add your "
|
_infobox(page, "Presets are your dictation actions. Pick one to edit it, or add your "
|
||||||
@ -1495,6 +1516,8 @@ class SettingsDialog:
|
|||||||
d.run(); d.destroy()
|
d.run(); d.destroy()
|
||||||
|
|
||||||
def _on_response(self, dlg, resp):
|
def _on_response(self, dlg, resp):
|
||||||
|
if resp in (RESP_SAVE, RESP_SAVE_RESTART):
|
||||||
|
self._force_build_tabs() # ensure every tab's fields exist
|
||||||
if resp == RESP_SAVE:
|
if resp == RESP_SAVE:
|
||||||
if self._collect():
|
if self._collect():
|
||||||
save(self.cfg)
|
save(self.cfg)
|
||||||
|
|||||||
@ -366,9 +366,18 @@ class App:
|
|||||||
|
|
||||||
# -- panel / settings / lifecycle -----------------------------------------
|
# -- panel / settings / lifecycle -----------------------------------------
|
||||||
def open_settings(self) -> None:
|
def open_settings(self) -> None:
|
||||||
|
# Single instance: if Settings is already open, bring it to the front
|
||||||
|
# instead of spawning a second dialog (the tray menu bypasses the
|
||||||
|
# dialog's own modality, so it could otherwise be opened repeatedly).
|
||||||
|
existing = getattr(self, "_settings", None)
|
||||||
|
if existing is not None:
|
||||||
|
existing.dlg.present()
|
||||||
|
return
|
||||||
from .gtksettings import SettingsDialog
|
from .gtksettings import SettingsDialog
|
||||||
|
|
||||||
SettingsDialog(self.win, self.cfg, daemon=self.daemon).run_dialog()
|
self._settings = SettingsDialog(self.win, self.cfg, daemon=self.daemon)
|
||||||
|
self._settings.dlg.connect("destroy", lambda *_: setattr(self, "_settings", None))
|
||||||
|
self._settings.run_dialog()
|
||||||
|
|
||||||
def show_panel(self) -> None:
|
def show_panel(self) -> None:
|
||||||
self.win.show_all()
|
self.win.show_all()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user