From 20bdb39e6f83ef87726817cb1bad31fa7b0f73cb Mon Sep 17 00:00:00 2001 From: Michael Weghorn Date: Tue, 10 Sep 2024 08:23:53 +0200 Subject: [PATCH] soffice: Report keyboard-triggered font size change (#17147) Partially implements feature requests from #6915 Summary of the issue: commit 46a3436 ("soffice: Report keyboard-triggered formatting toggles in Writer (#16413)") implemented announcement of formatting changes triggered by keyboard shortcuts for formatting attributes whose state is represented by toggle buttons in Writer's formatting toolbar. Issue #6915 requests announcement of further text formatting attributes that are not represented by simple toggle buttons. This includes decreasing/increasing the font size, which can be done using the Ctrl+[ and Ctrl+] shortcuts when using an English (US) UI and keyboard layout. Description of user facing changes When decreasing or increasing the font size in LibreOffice Writer using the corresponding keyboard shortcuts, NVDA announces the new font size. Description of development approach Extend the solution from the above-mentioned commit 46a3436 to not only cover toggle buttons, but also UI controls implementing the IAccessibleText interface and handle the gestures/keyboard shortcuts for changing the font size: Rename methods and variables introduced earlier to not have button-specific names. Extract some logic to helper methods/classes to avoid duplication in SymphonyButton and the newly introduced SymphonyText logic. Add Ctrl+[ and Ctrl+] to the list of keyboard gestures to handle. Add SymphonyText.event_valueChange override that announces the new value. This gets triggered when the value of the "Font Size" editable combobox in Writer's formatting toolbar changes after using the keyboard shortcut, and results in the new value being announced, e.g. "14 pt". This is comparable to the handling in SymphonyButton.event_stateChange introduced in the earlier above-mentioned commit. Increase the timeout for announcement of events from 0.15 to 2 seconds, as 0.15 seconds wasn't always sufficient when testing the new feature. --- source/appModules/soffice.py | 62 ++++++++++++++++++++++++++---------- user_docs/en/changes.md | 4 +++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/source/appModules/soffice.py b/source/appModules/soffice.py index 3bf0651bd2e..34b4984b652 100755 --- a/source/appModules/soffice.py +++ b/source/appModules/soffice.py @@ -33,6 +33,20 @@ import vision +class SymphonyUtils: + """Helper class providing utility methods.""" + + @staticmethod + def is_toolbar_item(obj: NVDAObject) -> bool: + """Whether the given object is part of a toolbar.""" + parent = obj.parent + while parent: + if parent.role == controlTypes.Role.TOOLBAR: + return True + parent = parent.parent + return False + + class SymphonyTextInfo(IA2TextTextInfo): # C901 '_getFormatFieldFromLegacyAttributesString' is too complex # Note: when working on _getFormatFieldFromLegacyAttributesString, look for opportunities to simplify @@ -235,6 +249,17 @@ def _get_positionInfo(self): return {"level": int(level)} return super(SymphonyText, self).positionInfo + def event_valueChange(self) -> None: + # announce new value to indicate formatting change if registered gesture + # triggered the change in toolbar item's value/text + if SymphonyDocument.isFormattingChangeAnnouncementEnabled() and SymphonyUtils.is_toolbar_item(self): + message = self.IAccessibleTextObject.text(0, -1) + ui.message(message) + # disable announcement until next registered keypress enables it again + SymphonyDocument.announceFormattingGestureChange = False + + return super().event_valueChange() + class SymphonyTableCell(IAccessible): """Silences particular states, and redundant column/row numbers""" @@ -355,16 +380,7 @@ class SymphonyButton(IAccessible): def event_stateChange(self) -> None: # announce new state of toggled toolbar button to indicate formatting change # if registered gesture resulted in button state change - if ( - SymphonyDocument.announceToolbarButtonToggle - and self.parent - and self.parent.role == controlTypes.Role.TOOLBAR - and time.time() - < ( - SymphonyDocument.lastFormattingGestureEventTime - + SymphonyDocument.GESTURE_ANNOUNCEMENT_TIMEOUT - ) - ): + if SymphonyDocument.isFormattingChangeAnnouncementEnabled() and SymphonyUtils.is_toolbar_item(self): states = self.states enabled = controlTypes.State.PRESSED in states or controlTypes.State.CHECKED in states # button's accessible name is the font attribute, e.g. "Bold", "Italic" @@ -376,7 +392,7 @@ def event_stateChange(self) -> None: message = _("{textAttribute} off").format(textAttribute=self.name) ui.message(message) # disable announcement until next registered keypress enables it again - SymphonyDocument.announceToolbarButtonToggle = False + SymphonyDocument.announceFormattingGestureChange = False return super().event_stateChange() @@ -431,10 +447,16 @@ class SymphonyDocument(CompoundDocument): TextInfo = SymphonyDocumentTextInfo # variables used for handling announcements resulting from gestures - GESTURE_ANNOUNCEMENT_TIMEOUT = 0.15 - announceToolbarButtonToggle = False + GESTURE_ANNOUNCEMENT_TIMEOUT = 2.0 + announceFormattingGestureChange = False lastFormattingGestureEventTime = 0 + @staticmethod + def isFormattingChangeAnnouncementEnabled() -> bool: + return SymphonyDocument.announceFormattingGestureChange and time.time() < ( + SymphonyDocument.lastFormattingGestureEventTime + SymphonyDocument.GESTURE_ANNOUNCEMENT_TIMEOUT + ) + # override base class implementation because that one assumes # that the text retrieved from the text info for the text unit # is the same as the text that actually gets removed, which at @@ -489,13 +511,19 @@ def _backspaceScriptHelper(self, unit: str, gesture: inputCore.InputGesture): "kb:control+r", # justified "kb:control+j", + # decrease font size + "kb:control+[", + # increase font size + "kb:control+]", ], ) - def script_toggleTextAttribute(self, gesture: inputCore.InputGesture): - """Reset time and enable announcement of toggled toolbar buttons. - See :func:`SymphonyButton.event_stateChange` + def script_changeTextFormatting(self, gesture: inputCore.InputGesture): + """Reset time and enable announcement of newly changed state/text of toolbar + items related to text formatting. + See also :func:`SymphonyButton.event_stateChange` and + :func:`SymphonyText.event_valueChange`. """ - SymphonyDocument.announceToolbarButtonToggle = True + SymphonyDocument.announceFormattingGestureChange = True SymphonyDocument.lastFormattingGestureEventTime = time.time() # send gesture gesture.send() diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index 1c6080c87a8..bd3c5140ba4 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -15,6 +15,7 @@ In order to use this feature, the application volume adjuster needs to be enable * Added an action in the Add-on Store to cancel the install of add-ons. (#15578, @hwf1324) * Added an action in the Add-on Store to retry the installation if the download/installation of an add-on fails. (#17090, @hwf1324) * It is now possible to specify a mirror URL to use for the Add-on Store. (#14974) +* When decreasing or increasing the font size in LibreOffice Writer using the corresponding keyboard shortcuts, NVDA announces the new font size. (#6915, @michaelweghorn) ### Changes @@ -60,6 +61,9 @@ Please open a GitHub issue if your add-on has an issue with updating to the new As the Add-on Store base URL is now configurable directly within NVDA, no replacement is planned. (#17099) * `NVDAObjects.UIA.winConsoleUIA.WinTerminalUIA` has been removed with no public replacement. (#14047, #16820, @codeofdusk) * `NVDAObjects.IAccessible.ia2TextMozilla.FakeEmbeddingTextInfo` has been removed. (#16768, @jcsteh) +* The following symbols in `appModules.soffice` have been renamed (#6915, @michaelweghorn): + * `SymphonyDocument.announceToolbarButtonToggle` to `SymphonyDocument.announceFormattingGestureChange` + * `SymphonyDocument.script_toggleTextAttribute` to `SymphonyDocument.script_changeTextFormatting` #### Deprecations