Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7472426
Report spelling errors with a sound while reading
Apr 18, 2025
bc96c36
Change log
Apr 22, 2025
30b5b75
Pre-commit auto-fix
pre-commit-ci[bot] Apr 22, 2025
fed0575
Fix pre-commit rework - adding tailing comma
Apr 23, 2025
d42f3ef
Apply suggestions from code review
CyrilleB79 May 5, 2025
fcf6afb
Change anchor name
May 5, 2025
72b2025
Remove debug comments
May 5, 2025
ecee988
Merge branch 'master' into spellingSound
May 7, 2025
743a5a5
Merge branch 'master' into spellingSound
CyrilleB79 Jun 3, 2025
579f3b1
Merge branch 'master' into spellingSound
Jul 8, 2025
5696410
Convert to new key; feature broken in Word (UIA or legacy)
Jul 8, 2025
44f72b0
Merge branch 'master' into spellingSound
Aug 5, 2025
b88944f
Fix config latest after merge
Aug 5, 2025
70770bb
Implement aliases
Aug 5, 2025
c7905cf
Fix and document config key aliasing
Aug 6, 2025
f2fc880
Merge branch 'master' into spellingSound
Aug 6, 2025
4b7670a
Pre-commit auto-fix
pre-commit-ci[bot] Aug 6, 2025
263c027
Apply suggestions from code review
CyrilleB79 Aug 7, 2025
03f5b09
Fix type hint
Aug 7, 2025
72b3d41
Merge branch 'master' into spellingSound
Aug 8, 2025
3b5219e
Apply suggestions from code review
CyrilleB79 Aug 8, 2025
d3676cc
Added unit tests
Aug 8, 2025
bb0a58e
Pre-commit auto-fix
pre-commit-ci[bot] Aug 8, 2025
41aaecd
Merge branch 'master' into spellingSound
Aug 8, 2025
658fd7e
Pre-commit auto-fix
pre-commit-ci[bot] Aug 8, 2025
da2d886
Bump CI
Aug 8, 2025
c4e5e97
Update tests/unit/test_config.py
SaschaCowley Aug 19, 2025
660049c
Merge branch 'master' into spellingSound
CyrilleB79 Aug 28, 2025
bce05e3
Remove backward compa code since we now target 2026.1
CyrilleB79 Aug 28, 2025
cba9bfa
ReportSpellingErrors converted to DisplayStringIntFlag; usage to be u…
CyrilleB79 Aug 28, 2025
602a05e
Pre-commit auto-fix
pre-commit-ci[bot] Aug 28, 2025
aeac6b8
Convert to bitwise flag everywhere
Aug 29, 2025
0e92ea7
fix translator's comment
Aug 29, 2025
156ea3a
Update change for dev
Aug 29, 2025
1cfe185
Update User §Guide with checklist box
Aug 29, 2025
cb6d06f
Fix config upgrade
Aug 29, 2025
3a7db06
Pre-commit auto-fix
pre-commit-ci[bot] Aug 29, 2025
1f6c43d
type hint
Aug 29, 2025
e324235
Fix syntax error
Aug 29, 2025
8f5ca89
Pre-commit auto-fix
pre-commit-ci[bot] Aug 29, 2025
3008570
Fix missing import
Aug 29, 2025
7a9810e
One more attempt to fix translators comment!
Aug 29, 2025
3087fa6
Fix both reporting modes
CyrilleB79 Aug 30, 2025
5103c8b
Pre-commit auto-fix
pre-commit-ci[bot] Aug 30, 2025
d3a156e
Apply suggestions from code review
CyrilleB79 Sep 2, 2025
6a0437b
Merge branch 'master' into spellingSound
CyrilleB79 Sep 3, 2025
4c2b721
Remove unnecessary if
CyrilleB79 Sep 3, 2025
bc29cb4
Update source/config/profileUpgradeSteps.py
SaschaCowley Sep 5, 2025
94b65d9
Pre-commit auto-fix
pre-commit-ci[bot] Sep 5, 2025
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
5 changes: 3 additions & 2 deletions source/NVDAObjects/UIA/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import controlTypes
from controlTypes import TextPosition, TextAlign
import config
from config.configFlags import ReportSpellingErrors
import speech
import api
import textInfos
Expand Down Expand Up @@ -313,7 +314,7 @@ def _getFormatFieldAnnotationTypes(
# Always mutate to a tuple to allow for a generic x in y matching
if not isinstance(annotationTypes, tuple):
annotationTypes = (annotationTypes,)
if formatConfig["reportSpellingErrors"]:
if formatConfig["reportSpellingErrors2"] != ReportSpellingErrors.OFF.value:
if UIAHandler.AnnotationType_SpellingError in annotationTypes:
formatField["invalid-spelling"] = True
if UIAHandler.AnnotationType_GrammarError in annotationTypes:
Expand Down Expand Up @@ -368,7 +369,7 @@ def _getFormatFieldAtRange( # noqa: C901
if not isinstance(textRange, UIAHandler.IUIAutomationTextRange):
raise ValueError("%s is not a text range" % textRange)
fetchAnnotationTypes = (
formatConfig["reportSpellingErrors"]
formatConfig["reportSpellingErrors2"] != ReportSpellingErrors.OFF.value
or formatConfig["reportComments"]
or formatConfig["reportRevisions"]
or formatConfig["reportBookmarks"]
Expand Down
4 changes: 2 additions & 2 deletions source/NVDAObjects/behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2006-2025 NV Access Limited, Peter Vágner, Joseph Lee, Bill Dengler,
# Burman's Computer and Education Ltd, Cary-rowen
# Burman's Computer and Education Ltd, Cary-rowen, Cyrille Bougot

"""Mix-in classes which provide common behaviour for particular types of controls across different APIs.
Behaviors described in this mix-in include providing table navigation commands for certain table rows, terminal input and output support, announcing notifications and suggestion items and so on.
Expand Down Expand Up @@ -297,7 +297,7 @@ def _delayedDetection():

def event_typedCharacter(self, ch: str):
if (
config.conf["documentFormatting"]["reportSpellingErrors"]
config.conf["documentFormatting"]["reportSpellingErrors2"]
and config.conf["keyboard"]["alertForSpellingErrors"]
and (
# Not alpha, apostrophe or control.
Expand Down
26 changes: 23 additions & 3 deletions source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import logging
from logging import DEBUG
from shlobj import FolderId, SHGetKnownFolderPath
from addonAPIVersion import BACK_COMPAT_TO
import baseObject
import easeOfAccess
from fileUtils import FaultTolerantFile
Expand All @@ -35,6 +36,7 @@
from . import profileUpgrader
from . import aggregatedSection
from .configSpec import confspec
from .configFlags import ReportSpellingErrors
from .featureFlag import (
_transformSpec_AddFeatureFlagDefault,
_validateConfig_featureFlag,
Expand Down Expand Up @@ -1285,9 +1287,9 @@ def __setitem__(
self._cache[key] = val

# Alias old config items to their new counterparts for backwards compatibility.
# Uncomment when there are new links that need to be made.
# if BACK_COMPAT_TO < (2026, 1, 0) and NVDAState._allowDeprecatedAPI():
# self._linkDeprecatedValues(key, val)
# TODO: Comment out in 2026.1.
if BACK_COMPAT_TO < (2026, 1, 0) and NVDAState._allowDeprecatedAPI():
self._linkDeprecatedValues(key, val)

def _linkDeprecatedValues(self, key: aggregatedSection._cacheKeyT, val: aggregatedSection._cacheValueT):
"""Link deprecated config keys and values to their replacements.
Expand Down Expand Up @@ -1315,6 +1317,24 @@ def _linkDeprecatedValues(self, key: aggregatedSection._cacheKeyT, val: aggregat
>>> ...
"""
match self.path:
case ("documentFormatting",):
match key:
case "reportSpellingErrors2":
# Alias documentFormatting.reportSpellingErrors2 to documentFormatting.reportSpellingErrors for backwards compatibility.
key = "reportSpellingErrors"
val = val != ReportSpellingErrors.OFF.value
case "reportSpellingErrors":
# Alias documentFormatting.reportSpellingErrors to documentFormatting.reportSpellingErrors2 for forwards compatibility.
log.warning(
"documentFormatting.reportSpellingErrors is deprecated. Use documentFormatting.reportSpellingErrors2 instead.",
# Include stack info so testers can report warning to add-on author.
stack_info=True,
)
key = "reportSpellingErrors2"
val = ReportSpellingErrors.SPEECH.value if val else ReportSpellingErrors.OFF.value
case _:
# We don't care about other keys in this section.
return
case _:
# We don't care about other sections.
return
Expand Down
26 changes: 26 additions & 0 deletions source/config/configFlags.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,32 @@ def _displayStringLabels(self):
}


@unique
class ReportSpellingErrors(DisplayStringIntEnum):
Comment thread
SaschaCowley marked this conversation as resolved.
Outdated
"""Enumeration containing the possible config values to report spelling errors while reading.

Use ReportSpellingErrors.MEMBER.value to compare with the config;
use ReportSpellingErrors.MEMBER.displayString in the UI for a translatable description of this member.
"""

OFF = 0
SPEECH = 1
SOUND = 2

@property
def _displayStringLabels(self) -> dict["ReportSpellingErrors", str]:
return {
# Translators: A choice in a combo box in the document formatting dialog to not report spelling errors.
ReportSpellingErrors.OFF: pgettext("reportSpellingErrorsSetting", "Off"),
# Translators: A choice in a combo box in the document formatting dialog to report spelling errors
# with speech.
ReportSpellingErrors.SPEECH: pgettext("reportSpellingErrorsSetting", "Speech"),
# Translators: A choice in a combo box in the document formatting dialog to report spelling errors
# with a sound.
ReportSpellingErrors.SOUND: pgettext("reportSpellingErrorsSetting", "Sound"),
}


@unique
class ReportTableHeaders(DisplayStringIntEnum):
"""Enumeration containing the possible config values to report table headers.
Expand Down
5 changes: 4 additions & 1 deletion source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#: provide an upgrade step (@see profileUpgradeSteps.py). An upgrade step does not need to be added when
#: just adding a new element to (or removing from) the schema, only when old versions of the config
#: (conforming to old schema versions) will not work correctly with the new schema.
latestSchemaVersion = 18
latestSchemaVersion = 19

#: The configuration specification string
#: @type: String
Expand Down Expand Up @@ -226,7 +226,10 @@
reportAlignment = boolean(default=false)
reportLineSpacing = boolean(default=false)
reportStyle = boolean(default=false)
# Deprecated, and aliased to reportSpellingErrors2, to be removed in 2026.1
reportSpellingErrors = boolean(default=true)
# 0: Off, 1: Speech, 2: Sound
reportSpellingErrors2 = integer(min=0, max=2, default=1)
reportPage = boolean(default=true)
reportLineNumber = boolean(default=False)
# 0: Off, 1: Speech, 2: Tones, 3: Both Speech and Tones
Expand Down
27 changes: 27 additions & 0 deletions source/config/profileUpgradeSteps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
OutputMode,
ReportCellBorders,
ReportLineIndentation,
ReportSpellingErrors,
ReportTableHeaders,
ShowMessages,
TetherTo,
Expand Down Expand Up @@ -597,3 +598,29 @@ def upgradeConfigFrom_17_to_18(profile: ConfigObj) -> None:
"dotPad added to braille display auto detection excluded displays due to generic USB PID/VID. "
f"List is now: {excludedDisplays}",
)


def upgradeConfigFrom_18_to_19(profile: ConfigObj):
Comment thread
CyrilleB79 marked this conversation as resolved.
"""Convert report spelling errors configurations from boolean to integer values."""

section = "documentFormatting"
key = "reportSpellingErrors"
newKey = "reportSpellingErrors2"
try:
oldValue: bool = profile[section].as_bool(key)
except KeyError:
log.debug(f"'{key}' not present in config, no action taken.")
return
except ValueError:
log.error(f"'{key}' is not a boolean, got {profile[section][key]!r}. No action taken.")
return

newValue = ReportSpellingErrors.SPEECH.value if oldValue else ReportSpellingErrors.OFF.value
profile[section][newKey] = newValue
log.debug(
(
f"Added '{newKey}' with value {newValue} ({ReportSpellingErrors(newValue).name})."
f" The old key '{key}' remains with its original value ({oldValue}) since the two keys are aliased in the"
" code."
),
)
Comment thread
SaschaCowley marked this conversation as resolved.
22 changes: 11 additions & 11 deletions source/globalCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
BrailleMode,
OutputMode,
TypingEcho,
ReportSpellingErrors,
)
from config.featureFlag import FeatureFlag
from config.featureFlagEnums import BoolFlag
Expand Down Expand Up @@ -788,19 +789,18 @@ def script_toggleReportStyle(self, gesture):

@script(
# Translators: Input help mode message for toggle report spelling errors command.
description=_("Toggles on and off the reporting of spelling errors"),
description=_("Cycles through options for how to report spelling errors"),
category=SCRCAT_DOCUMENTFORMATTING,
)
def script_toggleReportSpellingErrors(self, gesture):
if config.conf["documentFormatting"]["reportSpellingErrors"]:
# Translators: The message announced when toggling the report spelling errors document formatting setting.
state = _("report spelling errors off")
config.conf["documentFormatting"]["reportSpellingErrors"] = False
else:
# Translators: The message announced when toggling the report spelling errors document formatting setting.
state = _("report spelling errors on")
config.conf["documentFormatting"]["reportSpellingErrors"] = True
ui.message(state)
toggleIntegerValue(
configSection="documentFormatting",
configKey="reportSpellingErrors2",
enumClass=ReportSpellingErrors,
# Translators: Reported when the user cycles through the choices to report spelling errors.
# {mode} will be replaced with the mode; e.g. Off, Speech, Sound.
messageTemplate=_("Report spelling errors {mode}"),
)

@script(
# Translators: Input help mode message for toggle report pages command.
Expand Down Expand Up @@ -2536,7 +2536,7 @@ def _reportFormattingHelper(self, info, browseable=False):
"reportColor",
"reportStyle",
"reportAlignment",
"reportSpellingErrors",
"reportSpellingErrors2",
"reportLineIndentation",
"reportParagraphIndentation",
"reportLineSpacing",
Expand Down
22 changes: 15 additions & 7 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
TetherTo,
ParagraphStartMarker,
ReportLineIndentation,
ReportSpellingErrors,
ReportTableHeaders,
ReportCellBorders,
OutputMode,
Expand Down Expand Up @@ -2124,7 +2125,7 @@ def makeSettings(self, settingsSizer):
)
self.bindHelpEvent("KeyboardSettingsAlertForSpellingErrors", self.alertForSpellingErrorsCheckBox)
self.alertForSpellingErrorsCheckBox.SetValue(config.conf["keyboard"]["alertForSpellingErrors"])
if not config.conf["documentFormatting"]["reportSpellingErrors"]:
if config.conf["documentFormatting"]["reportSpellingErrors2"] == ReportSpellingErrors.OFF.value:
self.alertForSpellingErrorsCheckBox.Disable()

# Translators: This is the label for a checkbox in the
Expand Down Expand Up @@ -2800,11 +2801,18 @@ def makeSettings(self, settingsSizer):
self.revisionsCheckBox = docInfoGroup.addItem(wx.CheckBox(docInfoBox, label=revisionsText))
self.revisionsCheckBox.SetValue(config.conf["documentFormatting"]["reportRevisions"])

# Translators: This is the label for a checkbox in the
# document formatting settings panel.
spellingErrorText = _("Spelling e&rrors")
self.spellingErrorsCheckBox = docInfoGroup.addItem(wx.CheckBox(docInfoBox, label=spellingErrorText))
self.spellingErrorsCheckBox.SetValue(config.conf["documentFormatting"]["reportSpellingErrors"])
self.spellingErrorsCombo = docInfoGroup.addLabeledControl(
# Translators: This is the label for a checkbox in the
# document formatting settings panel.
_("Spelling e&rrors"),
wx.Choice,
choices=[i.displayString for i in ReportSpellingErrors],
)
self.bindHelpEvent(
"reportSpellingErrors",
self.spellingErrorsCombo,
)
self.spellingErrorsCombo.SetSelection(config.conf["documentFormatting"]["reportSpellingErrors2"])

# Translators: This is the label for a group of document formatting options in the
# document formatting settings panel
Expand Down Expand Up @@ -3027,7 +3035,7 @@ def onSave(self):
config.conf["documentFormatting"]["reportHighlight"] = self.highlightCheckBox.IsChecked()
config.conf["documentFormatting"]["reportAlignment"] = self.alignmentCheckBox.IsChecked()
config.conf["documentFormatting"]["reportStyle"] = self.styleCheckBox.IsChecked()
config.conf["documentFormatting"]["reportSpellingErrors"] = self.spellingErrorsCheckBox.IsChecked()
config.conf["documentFormatting"]["reportSpellingErrors2"] = self.spellingErrorsCombo.GetSelection()
config.conf["documentFormatting"]["reportPage"] = self.pageCheckBox.IsChecked()
config.conf["documentFormatting"]["reportLineNumber"] = self.lineNumberCheckBox.IsChecked()
config.conf["documentFormatting"]["reportLineIndentation"] = self.lineIndentationCombo.GetSelection()
Expand Down
17 changes: 11 additions & 6 deletions source/speech/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
EndUtteranceCommand,
SuppressUnicodeNormalizationCommand,
CharacterModeCommand,
WaveFileCommand,
)
from .shortcutKeys import getKeyboardShortcutsSpeech

Expand All @@ -65,6 +66,7 @@
import config
from config.configFlags import (
ReportLineIndentation,
ReportSpellingErrors,
ReportTableHeaders,
ReportCellBorders,
OutputMode,
Expand Down Expand Up @@ -1490,7 +1492,7 @@ def speakTextInfo(
def getTextInfoSpeech( # noqa: C901
info: textInfos.TextInfo,
useCache: Union[bool, SpeakTextInfoState] = True,
formatConfig: Dict[str, bool] = None,
formatConfig: dict[str, bool | int] | None = None,
unit: Optional[str] = None,
reason: OutputReason = OutputReason.QUERY,
_prefixSpeechCommand: Optional[SpeechCommand] = None,
Expand All @@ -1514,7 +1516,7 @@ def getTextInfoSpeech( # noqa: C901
)
# For performance reasons, when navigating by paragraph or table cell, spelling errors will not be announced.
if unit in (textInfos.UNIT_PARAGRAPH, textInfos.UNIT_CELL) and reason == OutputReason.CARET:
formatConfig["reportSpellingErrors"] = False
formatConfig["reportSpellingErrors2"] = ReportSpellingErrors.OFF.value

# Fetch the last controlFieldStack, or make a blank one
controlFieldStackCache = speakTextInfoState.controlFieldStackCache if speakTextInfoState else []
Expand Down Expand Up @@ -1891,7 +1893,7 @@ def _getTextInfoSpeech_considerSpelling(
speechSequence: SpeechSequence,
language: str,
) -> Generator[SpeechSequence, None, None]:
if onlyInitialFields or any(isinstance(x, str) for x in speechSequence):
if onlyInitialFields or speechSequence:
yield speechSequence
if not onlyInitialFields:
spellingSequence = list(
Expand Down Expand Up @@ -2991,13 +2993,16 @@ def getFormatFieldSpeech( # noqa: C901
# Translators: Reported when text no longer contains a bookmark
text = _("out of bookmark")
textList.append(text)
if formatConfig["reportSpellingErrors"]:
if formatConfig["reportSpellingErrors2"] != ReportSpellingErrors.OFF.value:
invalidSpelling = attrs.get("invalid-spelling")
oldInvalidSpelling = attrsCache.get("invalid-spelling") if attrsCache is not None else None
if (invalidSpelling or oldInvalidSpelling is not None) and invalidSpelling != oldInvalidSpelling:
if invalidSpelling:
# Translators: Reported when text contains a spelling error.
text = _("spelling error")
if formatConfig["reportSpellingErrors2"] == ReportSpellingErrors.SPEECH.value:
# Translators: Reported when text contains a spelling error.
text = _("spelling error")
else:
text = WaveFileCommand(r"waves\textError.wav")
elif extraDetail:
# Translators: Reported when moving out of text containing a spelling error.
text = _("out of spelling error")
Expand Down
5 changes: 5 additions & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Add-ons can be sorted by minimum and last tested NVDA version as well as by installation date. (#18440, #18560, @nvdaes, @CyrilleB79)
* Minimum and last tested version will now be also shown in the details area for an add-on in the Available Add-ons tab. (#18440, @nvdaes)
* Installation date will now be also shown in the details area for external add-ons. (#18560, @CyrilleB79)
* While reading text, spelling errors can now be reported with a sound instead of speech. (#4233, @jcsteh, @CyrilleB79)

### Changes

Expand Down Expand Up @@ -56,6 +57,10 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentatio
* `LP__ULARGE_INTEGER`
* `SynthDriver.isSpeaking`
* `easeOfAccess.RegistryKey` and `config.RegistryKey` is deprecated, use `config.registry.RegistryKey` instead. (#18608)
* The `bool` configuration key `[documentFormatting][reportSpellingErrors]` is deprecated for removal in 2026.1, instead use `[reportSpellingErrors2]`. (#17997, @CyrilleB79)
* The new key has an `int` value matching an `ReportSpellingErrors` `enum` with options for off, speech and sound.
* API consumers can use the `bool` value as previously, or check the `ReportSpellingErrors` if handling speech or sound specifically.
* These keys are currently synchronized until 2026.1.

## 2025.2

Expand Down
Loading