Fix prefs state churn and sound-only role speech

Some settings were being reset when tab changed and a key was pressed, e.g. control.

Radio buttons were still announced even with sound only set.

Controls for AI assistant tab were vanishing after being set.
This commit is contained in:
Storm Dragon
2026-02-18 07:02:31 -05:00
parent 9152455227
commit 95d33047fa
5 changed files with 34 additions and 22 deletions
+10 -12
View File
@@ -2563,22 +2563,20 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self._updateAIControlsState(enabled) self._updateAIControlsState(enabled)
def _updateAIControlsState(self, enabled): def _updateAIControlsState(self, enabled):
"""Enable or disable AI controls based on AI enabled state.""" """Refresh AI controls while keeping configuration fields editable."""
self.aiProviderCombo.set_sensitive(enabled) _ = enabled # kept for signal/call compatibility
self.aiApiKeyEntry.set_sensitive(enabled) # Keep settings editable even when AI assistant is disabled so users
self.aiOllamaModelEntry.set_sensitive(enabled) # can configure providers/keys before enabling it.
self.aiOllamaEndpointEntry.set_sensitive(enabled) self.aiProviderCombo.set_sensitive(True)
self.aiConfirmationCheckButton.set_sensitive(enabled) self.aiConfirmationCheckButton.set_sensitive(True)
self.aiScreenshotQualityCombo.set_sensitive(enabled) self.aiScreenshotQualityCombo.set_sensitive(True)
try: try:
self.get_widget("aiGetClaudeKeyButton").set_sensitive(enabled) self.get_widget("aiGetClaudeKeyButton").set_sensitive(True)
except: except:
pass # Button might not exist in older UI files pass # Button might not exist in older UI files
# Update provider-specific controls if AI is enabled current_provider = self.prefsDict.get("aiProvider", settings.aiProvider)
if enabled: self._updateProviderControls(current_provider)
current_provider = self.prefsDict.get("aiProvider", settings.aiProvider)
self._updateProviderControls(current_provider)
def _initIndentationState(self): def _initIndentationState(self):
"""Initialize Indentation widgets with current settings.""" """Initialize Indentation widgets with current settings."""
+8 -7
View File
@@ -325,7 +325,6 @@ class AIAssistant(Plugin):
if not self._prefs_widgets: if not self._prefs_widgets:
return return
enabled = self._prefs_widgets["enable_check"].get_active()
provider_values = self._prefs_widgets.get("provider_values", []) provider_values = self._prefs_widgets.get("provider_values", [])
provider_index = self._prefs_widgets["provider_combo"].get_active() provider_index = self._prefs_widgets["provider_combo"].get_active()
provider = provider_values[provider_index] if 0 <= provider_index < len(provider_values) else settings.aiProvider provider = provider_values[provider_index] if 0 <= provider_index < len(provider_values) else settings.aiProvider
@@ -333,12 +332,14 @@ class AIAssistant(Plugin):
is_gemini = provider == settings.AI_PROVIDER_GEMINI is_gemini = provider == settings.AI_PROVIDER_GEMINI
is_ollama = provider == settings.AI_PROVIDER_OLLAMA is_ollama = provider == settings.AI_PROVIDER_OLLAMA
self._prefs_widgets["provider_combo"].set_sensitive(enabled) # Keep preferences editable even when the feature is disabled so users
self._prefs_widgets["api_key_entry"].set_sensitive(enabled and is_gemini) # can prepare configuration before turning AI assistant on.
self._prefs_widgets["ollama_model_entry"].set_sensitive(enabled and is_ollama) self._prefs_widgets["provider_combo"].set_sensitive(True)
self._prefs_widgets["ollama_endpoint_entry"].set_sensitive(enabled and is_ollama) self._prefs_widgets["api_key_entry"].set_sensitive(is_gemini)
self._prefs_widgets["confirmation_check"].set_sensitive(enabled) self._prefs_widgets["ollama_model_entry"].set_sensitive(is_ollama)
self._prefs_widgets["quality_combo"].set_sensitive(enabled) self._prefs_widgets["ollama_endpoint_entry"].set_sensitive(is_ollama)
self._prefs_widgets["confirmation_check"].set_sensitive(True)
self._prefs_widgets["quality_combo"].set_sensitive(True)
def refresh_settings(self): def refresh_settings(self):
"""Refresh plugin settings and reinitialize provider. Called when settings change.""" """Refresh plugin settings and reinitialize provider. Called when settings change."""
+9
View File
@@ -1967,6 +1967,15 @@ class Script(script.Script):
debug.printMessage(debug.LEVEL_INFO, msg, True) debug.printMessage(debug.LEVEL_INFO, msg, True)
return return
# Some toolkits emit transient window:deactivate/window:activate pairs
# while the same window remains active. Treat those as noise so we do
# not clear state and force a full script/settings reactivation.
AXObject.clear_cache(event.source)
if AXUtilities.is_active(event.source):
msg = "DEFAULT: Ignoring event. Source window still active."
debug.printMessage(debug.LEVEL_INFO, msg, True)
return
if self.flatReviewPresenter.is_active(): if self.flatReviewPresenter.is_active():
self.flatReviewPresenter.quit() self.flatReviewPresenter.quit()
+3 -1
View File
@@ -572,7 +572,9 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
and soundEnabled: and soundEnabled:
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role) roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \ if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and soundEnabled and roleSoundIcon: and soundEnabled:
# Stateful controls present their role via state sounds; suppress
# spoken role names here even if no dedicated role icon exists.
if AXUtilities.is_check_box(obj) \ if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(obj) \ or AXUtilities.is_check_menu_item(obj) \
or AXUtilities.is_radio_button(obj) \ or AXUtilities.is_radio_button(obj) \
+3 -1
View File
@@ -678,7 +678,9 @@ class SpeechGenerator(generator.Generator):
and soundEnabled: and soundEnabled:
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role) roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \ if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and soundEnabled and roleSoundIcon: and soundEnabled:
# Stateful controls present their role via state sounds; suppress
# spoken role names here even if no dedicated role icon exists.
if AXUtilities.is_check_box(obj) \ if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(obj) \ or AXUtilities.is_check_menu_item(obj) \
or AXUtilities.is_radio_button(obj) \ or AXUtilities.is_radio_button(obj) \