Skip to content

Commit

Permalink
Speech feedback on more Word formatting shortcuts (nvaccess#17197)
Browse files Browse the repository at this point in the history
Fixes (at least partially) nvaccess#10271

Supersedes nvaccess#10283.

Summary of the issue:
User wants to have font formatting state announced when native font formatting shortcut are executed under Word and Outlook.

Description of user facing changes
The results of the following MS Word shortcuts are now reported:

control+shift+D: Underline double
control+shift+W: Underline words only
control+shift+A: All caps
ctrl+Shift+K: Small caps
ctrl+shift+i, ctrl+shift+b and ctrl+shift+u: alternative gestures for toggle italic, bold and underline.
When relevant, these shortcuts are also reported in Outlook.

Notes about various capitalization shortcuts
In Word, the Shift+F3 and Ctrl+Shift+A shortcuts are slightly different:

Shift+F3 only modifies the case of the current selection (or the word under the cursor if there is no selection)
Ctrl+Shift+A also modifies the case of selected text (or current word), but in addition, it modifies the case of the characters that will be typed at insertion point of the cursor.
Outlook's Ctrl+Shift+A shortcut is equivalent to Word's Shift+F3, whereas Word's Ctrl+Shift+A has no equivalent under Outlook as far as I know.

Messages
Word's shortcuts Ctrl+Shift+A, Ctrl+Shift+K and Shift+F3 modify all the selection's (or current word's) capitalization. However, Ctrl+Shift+A and Ctrl+Shift+K also impact the next characters that will be typed. Thus for this shortcut "on" and "off" have been used in their announced message in order to denote a state change. On the contrary Shift+F3 announces only the result of a modification of current selection (or word); thus "on" and "off" have not been used in its message.

Description of development approach
Use object model and polling to check state change, as done for other formatting scripts.
  • Loading branch information
CyrilleB79 authored Sep 26, 2024
1 parent babb7a1 commit ea94505
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 32 deletions.
156 changes: 125 additions & 31 deletions source/NVDAObjects/window/winword.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Optional,
Dict,
Generator,
Self,
TYPE_CHECKING,
)

Expand Down Expand Up @@ -43,6 +44,7 @@
import locationHelper
from enum import IntEnum
import documentBase
from utils.displayString import DisplayStringIntEnum

if TYPE_CHECKING:
import inputCore
Expand Down Expand Up @@ -78,6 +80,69 @@
wdMaximumNumberOfRows = 15
wdStartOfRangeColumnNumber = 16
wdMaximumNumberOfColumns = 18


class WdUnderline(DisplayStringIntEnum):
# Word underline styles
# see https://docs.microsoft.com/en-us/office/vba/api/word.wdunderline
NONE = 0
SINGLE = 1
WORDS = 2
DOUBLE = 3
DOTTED = 4
THICK = 6
DASH = 7
DOT_DASH = 9
DOT_DOT_DASH = 10
WAVY = 11
DOTTED_HEAVY = 20
DASH_HEAVY = 23
DOT_DASH_HEAVY = 25
DOT_DOT_DASH_HEAVY = 26
WAVY_HEAVY = 27
DASH_LONG = 39
WAVY_DOUBLE = 43
DASH_LONG_HEAVY = 55

@property
def _displayStringLabels(self) -> dict[Self, str]:
return {
WdUnderline.SINGLE: "", # For single underline we just say "underline"
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.WORDS: _("Words only"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOUBLE: _("Double"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOTTED: _("Dotted"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.THICK: _("Thick"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DASH: _("Dash"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOT_DASH: _("Dot dash"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOT_DOT_DASH: _("Dot dot dash"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.WAVY: _("Wave"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOTTED_HEAVY: _("Dotted heavy"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DASH_HEAVY: _("Dashed heavy"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOT_DASH_HEAVY: _("Dot dash heavy"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DOT_DOT_DASH_HEAVY: _("Dot dot dash heavy"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.WAVY_HEAVY: _("Wave heavy"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DASH_LONG: _("Dashed long"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.WAVY_DOUBLE: _("Wave double"),
# Translators: an underline style in Microsoft Word as announced in the font window.
WdUnderline.DASH_LONG_HEAVY: _("Dashed long heavy"),
}


# Horizontal alignment
wdAlignParagraphLeft = 0
wdAlignParagraphCenter = 1
Expand Down Expand Up @@ -1507,6 +1572,7 @@ def _WaitForValueChangeForAction(self, action, fetcher, timeout=0.15):
curTime = time.time()
return curVal

@script(gesture="kb:control+b")
def script_toggleBold(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand All @@ -1524,6 +1590,7 @@ def script_toggleBold(self, gesture):
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Bold off"))

@script(gestures=["kb:control+i", "kb:control+shift+i"])
def script_toggleItalic(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand All @@ -1541,23 +1608,59 @@ def script_toggleItalic(self, gesture):
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Italic off"))

def script_toggleUnderline(self, gesture):
@script(gestures=["kb:control+u", "kb:control+shift+u", "kb:control+shift+d"])
def script_toggleUnderline(self, gesture: "inputCore.InputGesture"):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
# The object model may be unavailable because this is a pure UIA implementation such as Windows 10 Mail, or its within Windows Defender Application Guard.
# Eventually UIA will have its own way of detecting format changes at the cursor. For now, just let the gesture through and don't erport anything.
# The object model may be unavailable because this is a pure UIA implementation such as Windows 10 Mail,
# or its within Windows Defender Application Guard.
# Eventually UIA will have its own way of detecting format changes at the cursor.
# For now, just let the gesture through and don't report anything.
return gesture.send()
val = self._WaitForValueChangeForAction(
lambda: gesture.send(),
lambda: self.WinwordSelectionObject.font.underline,
)
if val:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Underline on"))
if val != WdUnderline.NONE:
try:
style = WdUnderline(val).displayString
if len(style) > 0:
style += " "
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Underline {style}on").format(style=style))
except ValueError:
# In case an unlisted value is returned by Word Object model.
# This may happen if the selection contains multiple underline styles and if the gesture has failed to
# apply the underline style (e.g. too short timeout, gesture mismatch due to localization mismatch
# between Word and NVDA, etc.)
log.debugWarning(f"No underline value for {val}")
else:
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Underline off"))

@script(gesture="kb:control+shift+k")
def script_toggleCaps(self, gesture: "inputCore.InputGesture"):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
# The object model may be unavailable because this is a pure UIA implementation such as Windows 10 Mail,
# or its within Windows Defender Application Guard.
# Eventually UIA will have its own way of detecting format changes at the cursor.
# For now, just let the gesture through and don't report anything.
return gesture.send()
val = self._WaitForValueChangeForAction(
lambda: gesture.send(),
lambda: (self.WinwordSelectionObject.font.allcaps, self.WinwordSelectionObject.font.smallcaps),
)
if val[0]:
# Translators: a message when toggling formatting to 'all capital' in Microsoft word
ui.message(_("All caps on"))
elif val[1]:
# Translators: a message when toggling formatting to 'small capital' in Microsoft word
ui.message(_("Small caps on"))
else:
# Translators: a message when toggling formatting to 'No capital' in Microsoft word
ui.message(_("Caps off"))

@script(gestures=["kb:control+l", "kb:control+e", "kb:control+r", "kb:control+j"])
def script_toggleAlignment(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand Down Expand Up @@ -1598,6 +1701,7 @@ def script_changeParagraphLeftIndent(self, gesture):
msg = self.getLocalizedMeasurementTextForPointSize(margin + val)
ui.message(msg)

@script(gestures=["kb:control+=", "kb:control+shift+="])
def script_toggleSuperscriptSubscript(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand All @@ -1621,6 +1725,7 @@ def script_toggleSuperscriptSubscript(self, gesture):
# Translators: a message when toggling formatting in Microsoft word
ui.message(_("Baseline"))

@script(gesture="kb:alt+shift+downArrow")
def script_moveParagraphDown(self, gesture):
oldBookmark = self.makeTextInfo(textInfos.POSITION_CARET).bookmark
gesture.send()
Expand All @@ -1636,6 +1741,7 @@ def script_moveParagraphDown(self, gesture):
# Translators: a message reported when a paragraph is moved below a blank paragraph
ui.message(_("Moved below blank paragraph"))

@script(gesture="kb:alt+shift+upArrow")
def script_moveParagraphUp(self, gesture):
oldBookmark = self.makeTextInfo(textInfos.POSITION_CARET).bookmark
gesture.send()
Expand All @@ -1652,6 +1758,16 @@ def script_moveParagraphUp(self, gesture):
# Translators: a message reported when a paragraph is moved above a blank paragraph
ui.message(_("Moved above blank paragraph"))

@script(
gestures=[
"kb:alt+shift+rightArrow",
"kb:alt+shift+leftArrow",
"kb:control+shift+n",
"kb:control+alt+1",
"kb:control+alt+2",
"kb:control+alt+3",
],
)
def script_increaseDecreaseOutlineLevel(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand All @@ -1668,6 +1784,7 @@ def script_increaseDecreaseOutlineLevel(self, gesture):
_("{styleName} style, outline level {outlineLevel}").format(styleName=style, outlineLevel=val),
)

@script(gestures=["kb:control+[", "kb:control+]", "kb:control+shift+,", "kb:control+shift+."])
def script_increaseDecreaseFontSize(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand Down Expand Up @@ -1779,6 +1896,7 @@ def getLocalizedMeasurementTextForPointSize(self, offset):
offset,
).format(offset=offset)

@script(gestures=["kb:control+1", "kb:control+2", "kb:control+5"])
def script_changeLineSpacing(self, gesture):
if not self.WinwordSelectionObject:
# We cannot fetch the Word object model, so we therefore cannot report the format change.
Expand Down Expand Up @@ -1822,30 +1940,6 @@ def initOverlayClass(self):
self.bindGesture("kb:alt+shift+pageDown", "caret_changeSelection")

__gestures = {
"kb:control+[": "increaseDecreaseFontSize",
"kb:control+]": "increaseDecreaseFontSize",
"kb:control+shift+,": "increaseDecreaseFontSize",
"kb:control+shift+.": "increaseDecreaseFontSize",
"kb:control+b": "toggleBold",
"kb:control+i": "toggleItalic",
"kb:control+u": "toggleUnderline",
"kb:control+=": "toggleSuperscriptSubscript",
"kb:control+shift+=": "toggleSuperscriptSubscript",
"kb:control+l": "toggleAlignment",
"kb:control+e": "toggleAlignment",
"kb:control+r": "toggleAlignment",
"kb:control+j": "toggleAlignment",
"kb:alt+shift+downArrow": "moveParagraphDown",
"kb:alt+shift+upArrow": "moveParagraphUp",
"kb:alt+shift+rightArrow": "increaseDecreaseOutlineLevel",
"kb:alt+shift+leftArrow": "increaseDecreaseOutlineLevel",
"kb:control+shift+n": "increaseDecreaseOutlineLevel",
"kb:control+alt+1": "increaseDecreaseOutlineLevel",
"kb:control+alt+2": "increaseDecreaseOutlineLevel",
"kb:control+alt+3": "increaseDecreaseOutlineLevel",
"kb:control+1": "changeLineSpacing",
"kb:control+2": "changeLineSpacing",
"kb:control+5": "changeLineSpacing",
"kb:control+pageUp": "caret_moveByLine",
"kb:control+pageDown": "caret_moveByLine",
}
Expand Down
8 changes: 7 additions & 1 deletion source/appModules/winword.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2019-2020 NV Access Limited, Cyrille Bougot
# Copyright (C) 2019-2024 NV Access Limited, Cyrille Bougot

"""App module for Microsoft Word.
Word and Outlook share a lot of code and components. This app module gathers the code that is relevant for
Expand Down Expand Up @@ -40,3 +40,9 @@ def script_toggleChangeTracking(self, gesture):
else:
# Translators: a message when toggling change tracking in Microsoft word
ui.message(_("Change tracking off"))

__gestures = {
"kb:control+shift+b": "toggleBold",
"kb:control+shift+w": "toggleUnderline",
"kb:control+shift+a": "toggleCaps",
}
1 change: 1 addition & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ In order to use this feature, the application volume adjuster needs to be enable
* It now starts with a more user friendly explanation of its purpose, instead of a warning. (#12351)
* The initial window can now be exited with `escape` or `alt+f4`. (#10799)
* It will now show a message to the user, including the error, in the rare event of a Windows error while attempting COM re-registrations.
* In Word and Outlook the result of more font formatting shortcuts is now reported. (#10271, @CyrilleB79)

### Bug Fixes

Expand Down

0 comments on commit ea94505

Please sign in to comment.