Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -312,7 +313,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 @@ -367,7 +368,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
9 changes: 6 additions & 3 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 @@ -31,7 +31,10 @@
import globalVars
from typing import List, Union
import diffHandler
from config.configFlags import TypingEcho
from config.configFlags import (
TypingEcho,
ReportSpellingErrors,
)


class ProgressBar(NVDAObject):
Expand Down Expand Up @@ -297,7 +300,7 @@ def _delayedDetection():

def event_typedCharacter(self, ch: str):
if (
config.conf["documentFormatting"]["reportSpellingErrors"]
config.conf["documentFormatting"]["reportSpellingErrors2"] != ReportSpellingErrors.OFF.value
and config.conf["keyboard"]["alertForSpellingErrors"]
and (
# Not alpha, apostrophe or control.
Expand Down
2 changes: 1 addition & 1 deletion source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ def __setitem__(

# 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():
# if BACK_COMPAT_TO < (2027, 1, 0) and NVDAState._allowDeprecatedAPI():
# self._linkDeprecatedValues(key, val)

def _linkDeprecatedValues(self, key: aggregatedSection._cacheKeyT, val: aggregatedSection._cacheValueT):
Expand Down
33 changes: 33 additions & 0 deletions source/config/configFlags.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,39 @@ def _displayStringLabels(self):
}


@unique
class ReportSpellingErrors(DisplayStringIntFlag):
"""IntFlag enumeration containing the possible config values to report spelling errors while reading.

Use ReportSpellingErrors.MEMBER.value to compare with the config;
the config stores a bitwise combination of zero, one or more of these values.
Use ReportSpellingErrors.MEMBER.displayString in the UI for a translatable description of this member.
"""

OFF = 0b0
SPEECH = 0b1
SOUND = 0b10
SPEECH_AND_SOUND = SPEECH | SOUND

@property
def _displayStringLabels(self) -> dict["ReportSpellingErrors", str]:
return {
# Translators: A value reported by the cycle script defining how spelling errors are reported.
ReportSpellingErrors.OFF: pgettext("reportSpellingErrorsSetting", "Off"),
# Translators: A value reported by the cycle script defining how spelling errors are reported, also used
# as choice in a checklist box in the document formatting dialog to report spelling errors with speech.
ReportSpellingErrors.SPEECH: pgettext("reportSpellingErrorsSetting", "Speech"),
# Translators: A value reported by the cycle script defining how spelling errors are reported, also used
# as choice in a checklist box in the document formatting dialog to report spelling errors with a sound.
ReportSpellingErrors.SOUND: pgettext("reportSpellingErrorsSetting", "Sound"),
ReportSpellingErrors.SPEECH_AND_SOUND: pgettext(
"reportSpellingErrorsSetting",
# Translators: A value reported by the cycle script defining how spelling errors are reported.
"Speech and sound",
),
}


@unique
class ReportTableHeaders(DisplayStringIntEnum):
"""Enumeration containing the possible config values to report table headers.
Expand Down
6 changes: 4 additions & 2 deletions 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 @@ -227,7 +227,9 @@
reportAlignment = boolean(default=false)
reportLineSpacing = boolean(default=false)
reportStyle = boolean(default=false)
reportSpellingErrors = boolean(default=true)
# Bitwise combination of none, some or all values of ReportSpellingErrors
# 1: Speech, 2: Sound
reportSpellingErrors2 = integer(min=0, max=3, default=1)
reportPage = boolean(default=true)
reportLineNumber = boolean(default=False)
# 0: Off, 1: Speech, 2: Tones, 3: Both Speech and Tones
Expand Down
25 changes: 25 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,27 @@ 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
del profile[section][key]
log.debug(
f"Converted '{key}' with value {oldValue} to '{newKey}' with value {newValue}"
f" ({ReportSpellingErrors(newValue).name}). The old key '{key}' has been deleted.",
)
24 changes: 12 additions & 12 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)
def script_toggleReportSpellingErrors(self, gesture: inputCore.InputGesture):
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
31 changes: 24 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 not config.conf["documentFormatting"]["reportSpellingErrors2"]:
self.alertForSpellingErrorsCheckBox.Disable()

# Translators: This is the label for a checkbox in the
Expand Down Expand Up @@ -2810,11 +2811,23 @@ 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._spellingErrorsChecklist = docInfoGroup.addLabeledControl(
# Translators: This is the label for a checklist in the
# document formatting settings panel.
_("Spelling e&rrors"),
nvdaControls.CustomCheckListBox,
choices=[i.displayString for i in ReportSpellingErrors],
)
checkedItems = []
for i, mode in enumerate(ReportSpellingErrors):
if config.conf["documentFormatting"]["reportSpellingErrors2"] & mode.value:
checkedItems.append(i)
self._spellingErrorsChecklist.SetCheckedItems(checkedItems)
self._spellingErrorsChecklist.Select(0)
self.bindHelpEvent(
"reportSpellingErrors",
self._spellingErrorsChecklist,
)

# Translators: This is the label for a group of document formatting options in the
# document formatting settings panel
Expand Down Expand Up @@ -3037,7 +3050,11 @@ 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"] = sum(
mode.value
for (n, mode) in enumerate(ReportSpellingErrors)
if self._spellingErrorsChecklist.IsChecked(n)
)
config.conf["documentFormatting"]["reportPage"] = self.pageCheckBox.IsChecked()
config.conf["documentFormatting"]["reportLineNumber"] = self.lineNumberCheckBox.IsChecked()
config.conf["documentFormatting"]["reportLineIndentation"] = self.lineIndentationCombo.GetSelection()
Expand Down
25 changes: 14 additions & 11 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 Down Expand Up @@ -66,6 +67,7 @@
import config
from config.configFlags import (
ReportLineIndentation,
ReportSpellingErrors,
ReportTableHeaders,
ReportCellBorders,
OutputMode,
Expand Down Expand Up @@ -1501,7 +1503,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 @@ -1525,7 +1527,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"] = 0

# Fetch the last controlFieldStack, or make a blank one
controlFieldStackCache = speakTextInfoState.controlFieldStackCache if speakTextInfoState else []
Expand Down Expand Up @@ -1902,7 +1904,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 @@ -3002,20 +3004,21 @@ 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"]:
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:
texts = []
if invalidSpelling:
# Translators: Reported when text contains a spelling error.
text = _("spelling error")
if formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SOUND.value:
texts.append(WaveFileCommand(r"waves\textError.wav"))
if formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SPEECH.value:
# Translators: Reported when text contains a spelling error.
texts.append(_("spelling error"))
elif extraDetail:
# Translators: Reported when moving out of text containing a spelling error.
text = _("out of spelling error")
else:
text = ""
if text:
textList.append(text)
texts.append(_("out of spelling error"))
textList.extend(texts)
invalidGrammar = attrs.get("invalid-grammar")
oldInvalidGrammar = attrsCache.get("invalid-grammar") if attrsCache is not None else None
if (invalidGrammar or oldInvalidGrammar is not None) and invalidGrammar != oldInvalidGrammar:
Expand Down
Loading