diff --git a/src/cthulhu/cthulhu-setup.ui b/src/cthulhu/cthulhu-setup.ui
index 295958b..1fa5b4a 100644
--- a/src/cthulhu/cthulhu-setup.ui
+++ b/src/cthulhu/cthulhu-setup.ui
@@ -124,6 +124,12 @@
1
10
+
+
@@ -1036,23 +1029,96 @@
2
+
+
+ 0
+
+
+
+
+ True
+ False
+ General
+
+
+ 0
+ False
+
+
+
+
+ True
+ False
+ 12
+ 10
+ 10
-
+
True
False
0
none
-
+
True
False
12
-
+
True
False
- vertical
- 6
+ 10
+ 20
+
+
+ Enable _sounds
+ True
+ True
+ False
+ True
+ 0
+ True
+ True
+
+
+
+ 0
+ 0
+ 2
+
+
+
+
+ True
+ False
+ 0
+ Sound _volume:
+ True
+ soundVolumeScale
+
+
+
+
+
+ 0
+ 1
+
+
+
+
+ True
+ True
+ soundVolumeAdjustment
+ 2
+ 2
+ right
+
+
+
+ 1
+ 1
+
+
True
@@ -1093,11 +1159,48 @@
- False
- True
- 0
+ 0
+ 2
+ 2
+
+
+
+
+
+
+ True
+ False
+ Output
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ 0
+ none
+
+
+ True
+ False
+ 12
+
+
+ True
+ False
+ 10
+ 20
True
@@ -1138,9 +1241,9 @@
- False
- True
- 1
+ 0
+ 0
+ 2
@@ -1183,9 +1286,77 @@
- False
- True
- 2
+ 0
+ 1
+ 2
+
+
+
+
+ Play sound for _role
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+
+ 0
+ 2
+ 2
+
+
+
+
+ Play sound for _state
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+
+ 0
+ 3
+ 2
+
+
+
+
+ Play sound for p_osition in set
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+
+ 0
+ 4
+ 2
+
+
+
+
+ Play sound for _value
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+
+ 0
+ 5
+ 2
@@ -1193,10 +1364,10 @@
-
+
True
False
- Sound
+ Presentation
@@ -1205,18 +1376,111 @@
1
- 4
+ 0
+
+
+
+
+ True
+ False
+ 0
+ none
+
+
+ True
+ False
+ 12
+
+
+ True
+ False
+ 10
+ 20
+
+
+ Bee_p progress bar updates
+ True
+ True
+ False
+ True
+ 0
+ True
+ True
+
+
+
+ 0
+ 0
+ 2
+
+
+
+
+ True
+ False
+ 0
+ Beep _frequency (secs):
+ True
+ progressBarBeepIntervalSpinButton
+
+
+
+
+
+ 0
+ 1
+
+
+
+
+ True
+ True
+ 0
+ progressBarBeepIntervalAdjustment
+ 1
+ True
+ 0
+
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+ True
+ False
+ Alerts
+
+
+
+
+
+
+
+ 0
+ 1
+ 2
+
+ 1
+
-
+
True
False
- General
+ Sounds
+ 1
False
@@ -1614,7 +1878,7 @@
- 1
+ 2
@@ -1624,7 +1888,7 @@
Voice
- 1
+ 2
False
@@ -2312,7 +2576,7 @@
- 2
+ 3
@@ -2322,7 +2586,7 @@
Speech
- 2
+ 3
False
@@ -2911,7 +3175,7 @@
- 3
+ 4
@@ -2921,7 +3185,7 @@
Braille
- 3
+ 4
False
@@ -3436,7 +3700,7 @@
- 4
+ 5
@@ -3446,7 +3710,7 @@
Echo
- 4
+ 5
False
@@ -3528,7 +3792,7 @@
- 5
+ 6
@@ -3538,7 +3802,7 @@
Key Bindings
- 5
+ 6
False
@@ -3638,7 +3902,7 @@
- 6
+ 7
@@ -3648,7 +3912,7 @@
Pronunciation
- 6
+ 7
False
@@ -3983,7 +4247,7 @@
- 7
+ 8
@@ -3993,7 +4257,7 @@
Text Attributes
- 7
+ 8
False
diff --git a/src/cthulhu/cthulhu_gui_prefs.py b/src/cthulhu/cthulhu_gui_prefs.py
index 412a5d1..6ecfa54 100644
--- a/src/cthulhu/cthulhu_gui_prefs.py
+++ b/src/cthulhu/cthulhu_gui_prefs.py
@@ -198,6 +198,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self.soundThemeCombo = None
self.soundSinkCombo = None
self.roleSoundPresentationCombo = None
+ self.soundVolumeScale = None
+ self.progressBarBeepIntervalSpinButton = None
self._isInitialSetup = False
self._updatingSpeechFamilies = False
self.selectedFamilyChoices = {}
@@ -3111,9 +3113,35 @@ print(json.dumps(result))
self.soundSinkCombo = self.get_widget("soundSinkCombo")
self.soundThemeCombo = self.get_widget("soundThemeCombo")
self.roleSoundPresentationCombo = self.get_widget("roleSoundPresentationCombo")
+ self.soundVolumeScale = self.get_widget("soundVolumeScale")
+ self.progressBarBeepIntervalSpinButton = self.get_widget("progressBarBeepIntervalSpinButton")
self.soundSinkCombo.set_can_focus(False)
self.soundThemeCombo.set_can_focus(False)
self.roleSoundPresentationCombo.set_can_focus(False)
+ self.get_widget("enableSoundCheckButton").set_active(
+ prefs.get("enableSound", settings.enableSound)
+ )
+ self.soundVolumeScale.set_value(
+ float(prefs.get("soundVolume", settings.soundVolume))
+ )
+ self.get_widget("playSoundForRoleCheckButton").set_active(
+ prefs.get("playSoundForRole", settings.playSoundForRole)
+ )
+ self.get_widget("playSoundForStateCheckButton").set_active(
+ prefs.get("playSoundForState", settings.playSoundForState)
+ )
+ self.get_widget("playSoundForPositionInSetCheckButton").set_active(
+ prefs.get("playSoundForPositionInSet", settings.playSoundForPositionInSet)
+ )
+ self.get_widget("playSoundForValueCheckButton").set_active(
+ prefs.get("playSoundForValue", settings.playSoundForValue)
+ )
+ self.get_widget("beepProgressBarUpdatesCheckButton").set_active(
+ prefs.get("beepProgressBarUpdates", settings.beepProgressBarUpdates)
+ )
+ self.progressBarBeepIntervalSpinButton.set_value(
+ int(prefs.get("progressBarBeepInterval", settings.progressBarBeepInterval))
+ )
self._soundSinkChoices = [
(settings.SOUND_SINK_AUTO, guilabels.SOUND_BACKEND_AUTO),
@@ -3199,6 +3227,14 @@ print(json.dumps(result))
value = self._roleSoundPresentationChoices[activeIndex][0]
self.prefsDict["roleSoundPresentation"] = value
+ def soundVolumeValueChanged(self, widget):
+ """Signal handler for the sound volume scale."""
+ self.prefsDict["soundVolume"] = widget.get_value()
+
+ def progressBarBeepIntervalValueChanged(self, widget):
+ """Signal handler for the progress bar beep interval spin button."""
+ self.prefsDict["progressBarBeepInterval"] = widget.get_value_as_int()
+
def _updateCthulhuModifier(self):
combobox = self.get_widget("cthulhuModifierComboBox")
@@ -4974,6 +5010,7 @@ print(json.dumps(result))
self._apply_plugin_changes()
self.writeUserPreferences()
cthulhu.loadUserSettings(self.script)
+ self._initSoundThemeState()
self._refresh_dynamic_plugin_tabs()
braille.checkBrailleSetting()
self._initSpeechState()
@@ -5236,6 +5273,7 @@ print(json.dumps(result))
cthulhu.loadUserSettings(skipReloadMessage=True)
self._initGUIState()
+ self._initSoundThemeState()
braille.checkBrailleSetting()
diff --git a/src/cthulhu/scripts/web/script.py b/src/cthulhu/scripts/web/script.py
index c4bbdac..9916b99 100644
--- a/src/cthulhu/scripts/web/script.py
+++ b/src/cthulhu/scripts/web/script.py
@@ -1503,13 +1503,11 @@ class Script(default.Script):
self.presentMessage("Activating...")
result = self._performClickableAction(obj)
if result:
- self._presentDelayedMessage("Element activated", 50)
return True
from cthulhu import ax_event_synthesizer
result = ax_event_synthesizer.AXEventSynthesizer.click_object(obj)
if result:
- self._presentDelayedMessage("Element activated", 50)
self._restoreFocusAfterClick(original_focus)
return True
@@ -1520,13 +1518,11 @@ class Script(default.Script):
self.presentMessage("Activating...")
result = self._performClickableAction(obj)
if result:
- self._presentDelayedMessage("Element activated", 50)
return True
from cthulhu import ax_event_synthesizer
result = ax_event_synthesizer.AXEventSynthesizer.click_object(obj)
if result:
- self._presentDelayedMessage("Element activated", 50)
self._restoreFocusAfterClick(original_focus)
return True
diff --git a/tests/test_sound_preferences_regressions.py b/tests/test_sound_preferences_regressions.py
index 4e31837..531a1c3 100644
--- a/tests/test_sound_preferences_regressions.py
+++ b/tests/test_sound_preferences_regressions.py
@@ -1,12 +1,15 @@
import sys
import tempfile
import unittest
+import xml.etree.ElementTree as ET
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
+from cthulhu import cthulhu
from cthulhu import settings
+from cthulhu import cthulhu_gui_prefs
from cthulhu import settings_manager
@@ -23,5 +26,150 @@ class SoundSettingsPersistenceTests(unittest.TestCase):
self.assertIn("progressBarBeepInterval = 0", settingsText)
+class SoundPreferencesBuilderTests(unittest.TestCase):
+ def test_ui_definition_exposes_sounds_tab_controls(self):
+ uiPath = Path(__file__).resolve().parents[1] / "src" / "cthulhu" / "cthulhu-setup.ui"
+ root = ET.fromstring(uiPath.read_text(encoding="utf-8"))
+
+ objectIds = {
+ element.attrib["id"]
+ for element in root.iter()
+ if element.tag == "object" and "id" in element.attrib
+ }
+
+ self.assertIn("soundsGrid", objectIds)
+ self.assertIn("soundsTabLabel", objectIds)
+ self.assertIn("enableSoundCheckButton", objectIds)
+ self.assertIn("soundVolumeScale", objectIds)
+ self.assertIn("progressBarBeepIntervalSpinButton", objectIds)
+
+ def test_notebook_tab_positions_keep_sounds_page_and_label_aligned(self):
+ uiPath = Path(__file__).resolve().parents[1] / "src" / "cthulhu" / "cthulhu-setup.ui"
+ root = ET.fromstring(uiPath.read_text(encoding="utf-8"))
+ notebook = next(
+ element for element in root.iter()
+ if element.tag == "object" and element.attrib.get("id") == "notebook"
+ )
+
+ positions = {}
+ for child in notebook.findall("child"):
+ obj = child.find("object")
+ if obj is None or "id" not in obj.attrib:
+ continue
+ position = None
+ packing = child.find("packing")
+ if packing is not None:
+ for prop in packing.findall("property"):
+ if prop.attrib.get("name") == "position":
+ position = int(prop.text)
+ break
+ positions[obj.attrib["id"]] = position
+
+ self.assertEqual(positions["generalGrid"], 0)
+ self.assertEqual(positions["generalTabLabel"], 0)
+ self.assertEqual(positions["soundsGrid"], 1)
+ self.assertEqual(positions["soundsTabLabel"], 1)
+ self.assertEqual(positions["voiceGrid"], 2)
+ self.assertEqual(positions["voiceLabel"], 2)
+ self.assertEqual(positions["textAttributesGrid"], 8)
+ self.assertEqual(positions["textAttributesTabLabel"], 8)
+
+
+class SoundPreferencesControllerTests(unittest.TestCase):
+ def test_init_sound_state_loads_sound_volume_and_beep_interval(self):
+ gui = cthulhu_gui_prefs.CthulhuSetupGUI.__new__(cthulhu_gui_prefs.CthulhuSetupGUI)
+ gui.prefsDict = {
+ "enableSound": False,
+ "soundSink": settings.SOUND_SINK_PULSE,
+ "soundTheme": "default",
+ "roleSoundPresentation": settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY,
+ "soundVolume": 0.65,
+ "progressBarBeepInterval": 0,
+ "beepProgressBarUpdates": True,
+ "playSoundForRole": True,
+ "playSoundForState": True,
+ "playSoundForPositionInSet": False,
+ "playSoundForValue": False,
+ }
+ widgets = {
+ "soundSinkCombo": mock.Mock(),
+ "soundThemeCombo": mock.Mock(),
+ "roleSoundPresentationCombo": mock.Mock(),
+ "soundVolumeScale": mock.Mock(),
+ "progressBarBeepIntervalSpinButton": mock.Mock(),
+ "enableSoundCheckButton": mock.Mock(),
+ "playSoundForRoleCheckButton": mock.Mock(),
+ "playSoundForStateCheckButton": mock.Mock(),
+ "playSoundForPositionInSetCheckButton": mock.Mock(),
+ "playSoundForValueCheckButton": mock.Mock(),
+ "beepProgressBarUpdatesCheckButton": mock.Mock(),
+ }
+ gui.get_widget = widgets.__getitem__
+
+ fakeThemeManager = mock.Mock()
+ fakeThemeManager.getAvailableThemes.return_value = ["default"]
+ fakeSettingsManager = mock.Mock()
+ fakeSettingsManager.getSetting.return_value = settings.SOUND_SINK_AUTO
+ fakeApp = mock.Mock(settingsManager=fakeSettingsManager)
+
+ with (
+ mock.patch.object(cthulhu_gui_prefs.cthulhu, "cthulhuApp", fakeApp),
+ mock.patch.object(
+ cthulhu_gui_prefs.sound_theme_manager,
+ "getManager",
+ return_value=fakeThemeManager,
+ ),
+ ):
+ gui._initSoundThemeState()
+
+ widgets["enableSoundCheckButton"].set_active.assert_called_once_with(False)
+ widgets["soundVolumeScale"].set_value.assert_called_once_with(0.65)
+ widgets["playSoundForRoleCheckButton"].set_active.assert_called_once_with(True)
+ widgets["playSoundForStateCheckButton"].set_active.assert_called_once_with(True)
+ widgets["playSoundForPositionInSetCheckButton"].set_active.assert_called_once_with(False)
+ widgets["playSoundForValueCheckButton"].set_active.assert_called_once_with(False)
+ widgets["beepProgressBarUpdatesCheckButton"].set_active.assert_called_once_with(True)
+ widgets["progressBarBeepIntervalSpinButton"].set_value.assert_called_once_with(0)
+
+ def test_sound_value_handlers_store_current_values(self):
+ gui = cthulhu_gui_prefs.CthulhuSetupGUI.__new__(cthulhu_gui_prefs.CthulhuSetupGUI)
+ gui.prefsDict = {}
+
+ volumeWidget = mock.Mock()
+ volumeWidget.get_value.return_value = 0.7
+ beepWidget = mock.Mock()
+ beepWidget.get_value_as_int.return_value = 0
+
+ gui.soundVolumeValueChanged(volumeWidget)
+ gui.progressBarBeepIntervalValueChanged(beepWidget)
+
+ self.assertEqual(gui.prefsDict["soundVolume"], 0.7)
+ self.assertEqual(gui.prefsDict["progressBarBeepInterval"], 0)
+
+ def test_load_profile_reinitializes_sound_state(self):
+ gui = cthulhu_gui_prefs.CthulhuSetupGUI.__new__(cthulhu_gui_prefs.CthulhuSetupGUI)
+ gui.saveBasicSettings = mock.Mock()
+ gui._initGUIState = mock.Mock()
+ gui._initSoundThemeState = mock.Mock()
+ gui._initSpeechState = mock.Mock()
+ gui._initEchoSpeechState = mock.Mock()
+ gui._populateKeyBindings = mock.Mock()
+ gui._CthulhuSetupGUI__initProfileCombo = mock.Mock()
+ gui.prefsDict = {}
+
+ fakeSettingsManager = mock.Mock()
+ fakeSettingsManager.getGeneralSettings.return_value = {"soundVolume": 0.5}
+ fakeApp = mock.Mock(settingsManager=fakeSettingsManager)
+
+ with (
+ mock.patch.object(cthulhu_gui_prefs.cthulhu, "cthulhuApp", fakeApp),
+ mock.patch.object(cthulhu_gui_prefs.cthulhu, "loadUserSettings"),
+ mock.patch.object(cthulhu_gui_prefs.braille, "checkBrailleSetting"),
+ ):
+ gui.loadProfile(["Default", "default"])
+
+ gui._initSoundThemeState.assert_called_once_with()
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_web_router_regressions.py b/tests/test_web_router_regressions.py
index b06c549..9683f1b 100644
--- a/tests/test_web_router_regressions.py
+++ b/tests/test_web_router_regressions.py
@@ -58,6 +58,7 @@ class WebClickableActivationTests(unittest.TestCase):
doAction.assert_called_once_with(caretObject, "click-ancestor")
clickObject.assert_not_called()
+ testScript._presentDelayedMessage.assert_not_called()
class WebHiddenPopupTests(unittest.TestCase):