From 0879474987158d3043fe5c4f32bea6d5e47f18ae Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Wed, 3 Jun 2026 11:31:42 +0200 Subject: [PATCH 1/8] rewritting userdoc --- user_docs/en/userGuide.md | 45 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index 6d1299395cb..d10869e5777 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -1625,32 +1625,27 @@ NVDA will announce the name of the currently selected filter. The default color filter when the magnifier is first enabled can be configured in the [Magnifier settings](#MagnifierSettings). -### Focus Tracking Modes {#MagnifierFullscreenFocusModes} +### Fullscreen Tracking Modes {#MagnifierFullscreenTrackingModes} -The magnifier offers three different modes for tracking focus and determining which part of the screen to magnify: +The magnifier offers three different modes for tracking and determining which part of the screen to magnify: -* Center: The magnified area is centered on the current focus position. -This mode keeps the focused element at the center of the screen and clamps to the screen edge. +* Center: The magnified area is centered on the current position. +This mode keeps the followed element at the center of the screen and clamps to the screen edge. To disable clamping, activate [true center mode in the Magnifier settings](#MagnifierUseTrueCenter). -* Border: The magnified area only moves when the focus approaches the edge of the visible area. +* Border: The magnified area only moves when the followed element approaches the edge of the visible area. This mode provides a more stable view, only adjusting when necessary. -* Relative: The magnified area maintains the relative position of the focus within the screen. -This mode mimics the behavior of the Windows Magnifier. +* Relative: The magnified area maintains the relative position of the followed element with the screen. The magnified area will be centered if the followed element is in the center of the screen, and will be at the edge of the screen if the followed element is at the edge of the screen. +This will move the tracking progressively towards the edge of the magnified area as it moves towards the edge of the screen, and will move progressively towards the center of the screen as it moves towards the center. -To cycle through the focus tracking modes, please assign a custom gesture using the [Input Gestures dialog](#InputGestures). +To cycle through the tracking modes, please assign a custom gesture using the [Input Gestures dialog](#InputGestures). NVDA will announce the name of the currently selected mode. -The default focus mode when the magnifier is first enabled can be configured in the [Magnifier settings](#MagnifierSettings). +The default tracking mode when the magnifier is first enabled can be configured in the [Magnifier settings](#MagnifierSettings). ### Spotlight Mode {#MagnifierSpotlight} -Spotlight mode is a special feature designed for presentations or focused reading tasks. -When activated, it temporarily zooms out the magnified view to show the full screen, then zooms back in to the current focus position after a brief period of mouse inactivity. - -This is useful when you want to: - -* Show context to your audience during a presentation before zooming in on specific details -* Temporarily view the full screen layout while magnifying +Spotlight mode is a special feature designed to help get the context of where the magnifier is at the time. +When activated, it temporarily zooms out the magnified view to show the full screen, then zooms back in to the current mouse position after a brief period of mouse inactivity. To activate spotlight mode, press `NVDA+shift+l` while the magnifier is enabled. Once activated, the magnifier will: @@ -2919,10 +2914,10 @@ The available options are: | Grayscale | Converts all colors to shades of gray, which can help reduce eye strain and improve contrast. | | Inverted | Inverts all colors on the screen, which can be helpful for users who prefer light text on dark backgrounds or have photophobia. | -##### Focus mode {#MagnifierFullscreenFocusMode} +##### Tracking mode {#MagnifierFullscreenTrackingMode} -This combo box allows you to select the focus tracking mode when using the magnifier. -To cycle through the focus tracking modes, please assign a custom gesture using the [Input Gestures dialog](#InputGestures). +This combo box allows you to select the tracking mode when using the magnifier. +To cycle through the tracking modes, please assign a custom gesture using the [Input Gestures dialog](#InputGestures). The available options are: | . {.hideHeaderRow} |.| @@ -2932,9 +2927,9 @@ The available options are: | Option | Description | |---|---| -| Center | The magnified area is always centered on the current focus position. | -| Border | The magnified area only moves when the focus approaches the edge of the visible area. | -| Relative | The magnified area maintains the relative position of the focus within the screen. | +| Center | The magnified area is always centered on the current followed position. | +| Border | The magnified area only moves when the followed element approaches the edge of the visible area. | +| Relative | The magnified area maintains the relative position of the followed element within the screen. | ##### Panning step size {#MagnifierPanningStepSize} @@ -2946,7 +2941,7 @@ For example, if your visible magnified window is 200 pixels wide and you have a Higher percentages cause larger movements, making it faster to navigate across the screen, while lower percentages provide finer control for precise positioning. The actual pixel distance will automatically adjust based on your current zoom level. -Note: Pan commands allow you to manually move the magnified view in any direction, independent of the focus tracking mode. +Note: Pan commands allow you to manually move the magnified view in any direction, independent of the tracking mode. Available pan actions include: | . {.hideHeaderRow} |.| @@ -2956,8 +2951,8 @@ Available pan actions include: ##### Use true center {#MagnifierUseTrueCenter} -This checkbox controls whether the magnifier should always keep the focus centered on the screen, or if it should allow the focus to move towards the edges of the screen before moving the magnified area. -When enabled, the magnifier will always keep the focus centered on the screen, which can be helpful for users who prefer a consistent position of the focus within the magnified view. +This checkbox controls whether the magnifier should always keep the followed element centered on the screen, or if it should allow the followed element to move towards the edges of the screen before moving the magnified area. +When enabled, the magnifier will always keep the followed element centered on the screen, which can be helpful for users who prefer a consistent position of the followed element within the magnified view. Getting close to the edge of the screen, users will see out of screen areas. This option is disabled by default. From 2ce2521245ce216740d9fdfd98e586e48e8a953f Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Wed, 10 Jun 2026 13:27:26 +0200 Subject: [PATCH 2/8] removed unecessary focus naming --- source/_magnifier/commands.py | 16 ++--- source/_magnifier/config.py | 38 +++++------ source/_magnifier/utils/focusManager.py | 32 ++++----- source/_magnifier/utils/types.py | 20 +++--- source/globalCommands.py | 16 ++--- source/gui/settingsDialogs.py | 67 ++++++++++--------- .../unit/test_magnifier/test_focusManager.py | 62 ++++++++--------- 7 files changed, 127 insertions(+), 124 deletions(-) diff --git a/source/_magnifier/commands.py b/source/_magnifier/commands.py index 31e11df3d1b..5153f3da379 100644 --- a/source/_magnifier/commands.py +++ b/source/_magnifier/commands.py @@ -32,7 +32,7 @@ MagnifiedView, FullScreenMode, MagnifierAction, - MagnifierFollowFocusType, + MagnifierFollowTrackingType, ) from logHandler import log @@ -222,19 +222,19 @@ def cycleMagnifiedView() -> None: ) -def toggleFollow(focusType: MagnifierFollowFocusType) -> None: +def toggleFollow(trackingType: MagnifierFollowTrackingType) -> None: """ Toggle the specified follow mode setting. - :param focusType: The follow mode to toggle (mouse, system focus, review cursor, navigator object) + :param trackingType: The follow mode to toggle (mouse, system focus, review cursor, navigator object) """ magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, MagnifierAction.TOGGLE_FOLLOW_SETTINGS, ): - state = not getFollowState(focusType) - setFollowState(focusType, state) + state = not getFollowState(trackingType) + setFollowState(trackingType, state) ui.message( pgettext( @@ -242,7 +242,7 @@ def toggleFollow(focusType: MagnifierFollowFocusType) -> None: # Translators: Message announced when toggling a follow setting with {setting} being the name of the setting and {state} being either "enabled" or "disabled". "{setting} {state}", ).format( - setting=focusType.displayString, + setting=trackingType.displayString, state=pgettext( "magnifier", # Translators: State of the follow setting being toggled enabled. @@ -281,8 +281,8 @@ def toggleAllFollow() -> None: ui.message(stateMessage) -def toggleFullscreenMode() -> None: - """Cycle through full-screen focus modes (center, border, relative)""" +def cycleFullscreenMode() -> None: + """Cycle through full-screen modes (center, border, relative)""" magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, diff --git a/source/_magnifier/config.py b/source/_magnifier/config.py index d61a43a54a7..8f9a49a6912 100644 --- a/source/_magnifier/config.py +++ b/source/_magnifier/config.py @@ -10,7 +10,7 @@ import config from dataclasses import dataclass, field -from .utils.types import Filter, FullScreenMode, MagnifierFollowFocusType, MagnifiedView +from .utils.types import Filter, FullScreenMode, MagnifierFollowTrackingType, MagnifiedView def setEnabled(enable: bool) -> None: @@ -153,17 +153,17 @@ def setMagnifiedView(magnifiedView: MagnifiedView) -> None: config.conf["magnifier"]["magnifiedView"] = magnifiedView.value -_FOLLOW_CONFIG_KEYS: dict[MagnifierFollowFocusType, str] = { - MagnifierFollowFocusType.MOUSE: "followMouse", - MagnifierFollowFocusType.SYSTEM_FOCUS: "followSystemFocus", - MagnifierFollowFocusType.REVIEW: "followReviewCursor", - MagnifierFollowFocusType.NAVIGATOR_OBJECT: "followNavigatorObject", +_FOLLOW_CONFIG_KEYS: dict[MagnifierFollowTrackingType, str] = { + MagnifierFollowTrackingType.MOUSE: "followMouse", + MagnifierFollowTrackingType.SYSTEM_FOCUS: "followSystemFocus", + MagnifierFollowTrackingType.REVIEW: "followReviewCursor", + MagnifierFollowTrackingType.NAVIGATOR_OBJECT: "followNavigatorObject", } @dataclass class _FollowStateOverride: - savedStates: dict[MagnifierFollowFocusType, bool] = field(default_factory=dict) + savedStates: dict[MagnifierFollowTrackingType, bool] = field(default_factory=dict) isActive: bool = False @@ -179,30 +179,30 @@ def _ensureSavedStatesInitialized() -> None: saveFollowStates() -def getFollowState(focusType: MagnifierFollowFocusType) -> bool: +def getFollowState(trackingType: MagnifierFollowTrackingType) -> bool: """ Get the current follow state for a given focus type. - :param focusType: The focus type to query. + :param trackingType: The focus type to query. :return: True if the magnifier follows the given focus type, False otherwise. """ - return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[focusType]] + return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] -def setFollowState(focusType: MagnifierFollowFocusType, state: bool) -> None: +def setFollowState(trackingType: MagnifierFollowTrackingType, state: bool) -> None: """ Set the follow state for a given focus type. - :param focusType: The focus type to update. + :param trackingType: The focus type to update. :param state: True to enable following, False to disable. """ - config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[focusType]] = state + config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] = state def saveFollowStates() -> None: """Save current follow states so they can be restored later.""" - for focusType in _FOLLOW_CONFIG_KEYS: - _followStateOverride.savedStates[focusType] = getFollowState(focusType) + for trackingType in _FOLLOW_CONFIG_KEYS: + _followStateOverride.savedStates[trackingType] = getFollowState(trackingType) def toggleAllFollowStates() -> bool: @@ -213,13 +213,13 @@ def toggleAllFollowStates() -> bool: """ _ensureSavedStatesInitialized() if _followStateOverride.isActive: - for focusType, state in _followStateOverride.savedStates.items(): - setFollowState(focusType, state) + for trackingType, state in _followStateOverride.savedStates.items(): + setFollowState(trackingType, state) _followStateOverride.isActive = False else: saveFollowStates() - for focusType in _FOLLOW_CONFIG_KEYS: - setFollowState(focusType, False) + for trackingType in _FOLLOW_CONFIG_KEYS: + setFollowState(trackingType, False) _followStateOverride.isActive = True return _followStateOverride.isActive diff --git a/source/_magnifier/utils/focusManager.py b/source/_magnifier/utils/focusManager.py index 7bfe58ba4dd..cc72a437ffa 100644 --- a/source/_magnifier/utils/focusManager.py +++ b/source/_magnifier/utils/focusManager.py @@ -16,7 +16,7 @@ import locationHelper import textInfos from textInfos.offsets import OffsetsTextInfo -from .types import Coordinates, MagnifierFollowFocusType +from .types import Coordinates, MagnifierFollowTrackingType from ..config import getFollowState @@ -30,7 +30,7 @@ class FocusManager: def __init__(self): """Initialize the focus manager.""" - self._lastFocusedObject: MagnifierFollowFocusType | None = None + self._lastFocusedObject: MagnifierFollowTrackingType | None = None self._lastReportedCoordinates = Coordinates(0, 0) self._lastMousePosition = Coordinates(0, 0) self._lastSystemFocusPosition = Coordinates(0, 0) @@ -62,10 +62,10 @@ def getCurrentFocusCoordinates(self) -> Coordinates: isClickPressed = winUser.getAsyncKeyState(winUser.VK_LBUTTON) < 0 # Cache settings once — each call reads from config.conf - isFollowMouse = getFollowState(MagnifierFollowFocusType.MOUSE) - isFollowSystemFocus = getFollowState(MagnifierFollowFocusType.SYSTEM_FOCUS) - isFollowReviewCursor = getFollowState(MagnifierFollowFocusType.REVIEW) - isFollowNavigatorObject = getFollowState(MagnifierFollowFocusType.NAVIGATOR_OBJECT) + isFollowMouse = getFollowState(MagnifierFollowTrackingType.MOUSE) + isFollowSystemFocus = getFollowState(MagnifierFollowTrackingType.SYSTEM_FOCUS) + isFollowReviewCursor = getFollowState(MagnifierFollowTrackingType.REVIEW) + isFollowNavigatorObject = getFollowState(MagnifierFollowTrackingType.NAVIGATOR_OBJECT) mouseChanged = self._lastMousePosition != mousePosition systemFocusChanged = self._lastSystemFocusPosition != systemFocusPosition @@ -85,7 +85,7 @@ def getCurrentFocusCoordinates(self) -> Coordinates: # Priority 1: Mouse — drag (fires even when stationary) or movement if (isClickPressed or mouseChanged) and isFollowMouse: - self._lastFocusedObject = MagnifierFollowFocusType.MOUSE + self._lastFocusedObject = MagnifierFollowTrackingType.MOUSE return self._rememberAndReturnCoordinates(mousePosition) # Special case: table cell navigation (numpad). @@ -93,22 +93,22 @@ def getCurrentFocusCoordinates(self) -> Coordinates: # review cursor does not, the navigator object reflects the user's explicit navigation # intent and therefore takes priority over the system focus. if navigatorChanged and systemFocusChanged and not reviewChanged and isFollowNavigatorObject: - self._lastFocusedObject = MagnifierFollowFocusType.NAVIGATOR_OBJECT + self._lastFocusedObject = MagnifierFollowTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Priority 2: System focus (focus object + browse mode cursor) if systemFocusChanged and isFollowSystemFocus: - self._lastFocusedObject = MagnifierFollowFocusType.SYSTEM_FOCUS + self._lastFocusedObject = MagnifierFollowTrackingType.SYSTEM_FOCUS return self._rememberAndReturnCoordinates(systemFocusPosition) # Priority 3: Review cursor if reviewChanged and isFollowReviewCursor and reviewPosition is not None: - self._lastFocusedObject = MagnifierFollowFocusType.REVIEW + self._lastFocusedObject = MagnifierFollowTrackingType.REVIEW return self._rememberAndReturnCoordinates(reviewPosition) # Priority 4: Navigator object (NumPad navigation) if navigatorChanged and isFollowNavigatorObject: - self._lastFocusedObject = MagnifierFollowFocusType.NAVIGATOR_OBJECT + self._lastFocusedObject = MagnifierFollowTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Resolve the effective review position once (fallback to last valid when None) @@ -118,10 +118,10 @@ def getCurrentFocusCoordinates(self) -> Coordinates: # All sources in priority order _sources = ( - (MagnifierFollowFocusType.MOUSE, isFollowMouse, mousePosition), - (MagnifierFollowFocusType.SYSTEM_FOCUS, isFollowSystemFocus, systemFocusPosition), - (MagnifierFollowFocusType.REVIEW, isFollowReviewCursor, reviewEffectivePosition), - (MagnifierFollowFocusType.NAVIGATOR_OBJECT, isFollowNavigatorObject, navigatorPosition), + (MagnifierFollowTrackingType.MOUSE, isFollowMouse, mousePosition), + (MagnifierFollowTrackingType.SYSTEM_FOCUS, isFollowSystemFocus, systemFocusPosition), + (MagnifierFollowTrackingType.REVIEW, isFollowReviewCursor, reviewEffectivePosition), + (MagnifierFollowTrackingType.NAVIGATOR_OBJECT, isFollowNavigatorObject, navigatorPosition), ) # Keep current source if still enabled; otherwise clear it and freeze at _lastReportedCoordinates @@ -271,7 +271,7 @@ def _getNavigatorObjectPosition(self) -> Coordinates: return position return self._lastValidNavigatorObjectPosition - def getLastFocusType(self) -> MagnifierFollowFocusType | None: + def getLastFocusType(self) -> MagnifierFollowTrackingType | None: """ Get the type of the last focused object. diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index c1a47364603..1c25b55f628 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -89,7 +89,7 @@ def _displayStringLabels(self) -> dict["MagnifierAction", str]: } -class MagnifierFollowFocusType(DisplayStringEnum): +class MagnifierFollowTrackingType(DisplayStringEnum): """Type of focus the magnifier should follow based on user settings""" MOUSE = auto() @@ -98,16 +98,16 @@ class MagnifierFollowFocusType(DisplayStringEnum): NAVIGATOR_OBJECT = auto() @property - def _displayStringLabels(self) -> dict["MagnifierFollowFocusType", str]: + def _displayStringLabels(self) -> dict["MagnifierFollowTrackingType", str]: return { - # Translators: Focus type for magnifier to follow - mouse cursor. - self.MOUSE: pgettext("magnifier follow focus type", "Mouse"), - # Translators: Focus type for magnifier to follow - system focus (active element). - self.SYSTEM_FOCUS: pgettext("magnifier follow focus type", "System focus"), - # Translators: Focus type for magnifier to follow - review cursor position. - self.REVIEW: pgettext("magnifier follow focus type", "Review cursor"), - # Translators: Focus type for magnifier to follow - navigator object position. - self.NAVIGATOR_OBJECT: pgettext("magnifier follow focus type", "Navigator object"), + # Translators: Tracking type for magnifier to follow - mouse cursor. + self.MOUSE: pgettext("magnifier follow tracking type", "Mouse"), + # Translators: Tracking type for magnifier to follow - system focus (active element). + self.SYSTEM_FOCUS: pgettext("magnifier follow tracking type", "System focus"), + # Translators: Tracking type for magnifier to follow - review cursor position. + self.REVIEW: pgettext("magnifier follow tracking type", "Review cursor"), + # Translators: Tracking type for magnifier to follow - navigator object position. + self.NAVIGATOR_OBJECT: pgettext("magnifier follow tracking type", "Navigator object"), } diff --git a/source/globalCommands.py b/source/globalCommands.py index 57490c715cb..9600602a1a1 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -26,7 +26,7 @@ import review import _magnifier import _magnifier.commands -from _magnifier.utils.types import MagnifierFollowFocusType +from _magnifier.utils.types import MagnifierFollowTrackingType import controlTypes import api import textInfos @@ -5216,7 +5216,7 @@ def script_toggleFollowMouse( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierFollowFocusType.MOUSE) + _magnifier.commands.toggleFollow(MagnifierFollowTrackingType.MOUSE) @script( description=_( @@ -5229,7 +5229,7 @@ def script_toggleFollowSystemFocus( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierFollowFocusType.SYSTEM_FOCUS) + _magnifier.commands.toggleFollow(MagnifierFollowTrackingType.SYSTEM_FOCUS) @script( description=_( @@ -5242,7 +5242,7 @@ def script_toggleFollowReview( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierFollowFocusType.REVIEW) + _magnifier.commands.toggleFollow(MagnifierFollowTrackingType.REVIEW) @script( description=_( @@ -5255,7 +5255,7 @@ def script_toggleFollowNavigatorObject( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierFollowFocusType.NAVIGATOR_OBJECT) + _magnifier.commands.toggleFollow(MagnifierFollowTrackingType.NAVIGATOR_OBJECT) @script( description=_( @@ -5273,15 +5273,15 @@ def script_toggleAllFollow( @script( description=_( # Translators: Describes a command. - "Toggle focus mode for the full-screen magnifier", + "Cycle mode for the full-screen magnifier", ), category=SCRCAT_VISION, ) - def script_toggleFullscreenMode( + def script_cycleFullscreenMode( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFullscreenMode() + _magnifier.commands.cycleFullscreenMode() @script( description=_( diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 3012a48bbc5..f80bb401a2f 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -42,7 +42,7 @@ from _magnifier import getMagnifier from _magnifier.commands import toggleMagnifier import _magnifier.config as magnifierConfig -from _magnifier.utils.types import Filter, FullScreenMode, MagnifierFollowFocusType +from _magnifier.utils.types import Filter, FullScreenMode, MagnifierFollowTrackingType from _magnifier.fullscreenMagnifier import FullScreenMagnifier import queueHandler import requests @@ -2261,10 +2261,10 @@ class ReviewCursorPanel(SettingsPanel): def makeSettings(self, settingsSizer): # Translators: This is the label for a checkbox in the # review cursor settings panel. - self.followFocusCheckBox = wx.CheckBox(self, label=_("Follow system &focus")) - self.bindHelpEvent("ReviewCursorFollowFocus", self.followFocusCheckBox) - self.followFocusCheckBox.SetValue(config.conf["reviewCursor"]["followFocus"]) - settingsSizer.Add(self.followFocusCheckBox, border=10, flag=wx.BOTTOM) + self.followTrackingCheckBox = wx.CheckBox(self, label=_("Follow system &focus")) + self.bindHelpEvent("ReviewCursorFollowFocus", self.followTrackingCheckBox) + self.followTrackingCheckBox.SetValue(config.conf["reviewCursor"]["followTracking"]) + settingsSizer.Add(self.followTrackingCheckBox, border=10, flag=wx.BOTTOM) # Translators: This is the label for a checkbox in the # review cursor settings panel. self.followCaretCheckBox = wx.CheckBox(self, label=_("Follow System &Caret")) @@ -2285,7 +2285,7 @@ def makeSettings(self, settingsSizer): settingsSizer.Add(self.simpleReviewModeCheckBox, border=10, flag=wx.BOTTOM) def onSave(self): - config.conf["reviewCursor"]["followFocus"] = self.followFocusCheckBox.IsChecked() + config.conf["reviewCursor"]["followTracking"] = self.followTrackingCheckBox.IsChecked() config.conf["reviewCursor"]["followCaret"] = self.followCaretCheckBox.IsChecked() config.conf["reviewCursor"]["followMouse"] = self.followMouseCheckBox.IsChecked() config.conf["reviewCursor"]["simpleReviewMode"] = self.simpleReviewModeCheckBox.IsChecked() @@ -6056,8 +6056,8 @@ def _applyCurrentSettingsToConfigAndRuntime(self): magnifierConfig.setFilter(selectedFilter) magnifierConfig.setFullscreenMode(selectedMode) config.conf["magnifier"]["isTrueCentered"] = self.trueCenterCheckBox.GetValue() - for focusType, checkBox in self._followFocusCheckBoxes.items(): - magnifierConfig.setFollowState(focusType, checkBox.GetValue()) + for trackingType, checkBox in self._followTrackingCheckBoxes.items(): + magnifierConfig.setFollowState(trackingType, checkBox.GetValue()) magnifier = getMagnifier() if magnifier: @@ -6179,37 +6179,40 @@ def makeSettings( self.panSpinCtrl.Bind(wx.EVT_SPINCTRL, self._onImmediateSettingChange) self.panSpinCtrl.Bind(wx.EVT_TEXT, self._onImmediateSettingChange) - # FOCUS GROUP + # TRACKING GROUP # Translators: This is the label for a group of focus options in the magnifier settings panel - focusGroupText = _("Focus") - focusGroupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=focusGroupText) - focusGroupBox = focusGroupSizer.GetStaticBox() - focusGroup = guiHelper.BoxSizerHelper(focusGroupBox, sizer=focusGroupSizer) - sHelper.addItem(focusGroup) + trackingGroupText = _("Tracking") + trackingGroupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=trackingGroupText) + trackingGroupBox = trackingGroupSizer.GetStaticBox() + trackingGroup = guiHelper.BoxSizerHelper(trackingGroupBox, sizer=trackingGroupSizer) + sHelper.addItem(trackingGroup) - _followFocusLabels: dict[MagnifierFollowFocusType, tuple[str, str]] = { + _followTrackingLabels: dict[MagnifierFollowTrackingType, tuple[str, str]] = { # Translators: The label for a setting in magnifier settings to select whether the magnifier view should follow the mouse - MagnifierFollowFocusType.MOUSE: (_("Follow &mouse"), "MagnifierFollowMouse"), - # Translators: The label for a setting in magnifier settings to select whether the magnifier view should follow the system focus - MagnifierFollowFocusType.SYSTEM_FOCUS: (_("Follow &system focus"), "MagnifierFollowSystemFocus"), + MagnifierFollowTrackingType.MOUSE: (_("Follow &mouse"), "MagnifierFollowMouse"), + MagnifierFollowTrackingType.SYSTEM_FOCUS: ( + # Translators: The label for a setting in magnifier settings to select whether the magnifier view should follow the system focus + _("Follow &system focus"), + "MagnifierFollowSystemFocus", + ), # Translators: The label for a setting in magnifier settings to select whether the magnifier view should follow the review cursor - MagnifierFollowFocusType.REVIEW: (_("Follow &review cursor"), "MagnifierFollowReviewCursor"), - MagnifierFollowFocusType.NAVIGATOR_OBJECT: ( + MagnifierFollowTrackingType.REVIEW: (_("Follow &review cursor"), "MagnifierFollowReviewCursor"), + MagnifierFollowTrackingType.NAVIGATOR_OBJECT: ( # Translators: The label for a setting in magnifier settings to select whether the magnifier view should follow the navigator object _("Follow &navigator object"), "MagnifierFollowNavigatorObject", ), } - self._followFocusCheckBoxes: dict[MagnifierFollowFocusType, wx.CheckBox] = {} - self._followFocusInitially: dict[MagnifierFollowFocusType, bool] = {} - for focusType, (label, helpId) in _followFocusLabels.items(): - checkBox = focusGroup.addItem(wx.CheckBox(focusGroupBox, label=label)) + self._followTrackingCheckBoxes: dict[MagnifierFollowTrackingType, wx.CheckBox] = {} + self._followTrackingInitially: dict[MagnifierFollowTrackingType, bool] = {} + for trackingType, (label, helpId) in _followTrackingLabels.items(): + checkBox = trackingGroup.addItem(wx.CheckBox(trackingGroupBox, label=label)) self.bindHelpEvent(helpId, checkBox) - followState = magnifierConfig.getFollowState(focusType) - self._followFocusInitially[focusType] = followState + followState = magnifierConfig.getFollowState(trackingType) + self._followTrackingInitially[trackingType] = followState checkBox.SetValue(followState) checkBox.Bind(wx.EVT_CHECKBOX, self._onImmediateSettingChange) - self._followFocusCheckBoxes[focusType] = checkBox + self._followTrackingCheckBoxes[trackingType] = checkBox # FULLSCREEN GROUP # Translators: This is the label for a group of fullscreen magnifier options in the @@ -6250,7 +6253,7 @@ def onSave(self): selectedPanStep = self.panSpinCtrl.GetValue() selectedFilter = list(Filter)[self.filterList.GetSelection()] selectedMode = list(FullScreenMode)[self.fullscreenModeList.GetSelection()] - isTrueCentered = self.trueCenterCheckBox.GetValue() + isTrueCentered = self.trueCenterTrackingCheckBox.GetValue() roundedZoom = magnifierConfig.roundZoomLevel(selectedZoom) self._zoomInitially = roundedZoom @@ -6258,9 +6261,9 @@ def onSave(self): self._filterInitially = selectedFilter self._fullscreenModeInitially = selectedMode self._trueCenterTrackingInitially = isTrueCentered - for focusType, checkBox in self._followFocusCheckBoxes.items(): + for trackingType, checkBox in self._followTrackingCheckBoxes.items(): shouldFollow = checkBox.GetValue() - self._followFocusInitially[focusType] = shouldFollow + self._followTrackingInitially[trackingType] = shouldFollow def onDiscard(self): """Restore magnifier state from original settings from config.""" @@ -6269,8 +6272,8 @@ def onDiscard(self): magnifierConfig.setFilter(self._filterInitially) magnifierConfig.setFullscreenMode(self._fullscreenModeInitially) config.conf["magnifier"]["isTrueCentered"] = self._trueCenterTrackingInitially - for focusType, state in self._followFocusInitially.items(): - magnifierConfig.setFollowState(focusType, state) + for trackingType, state in self._followTrackingInitially.items(): + magnifierConfig.setFollowState(trackingType, state) magnifier = getMagnifier() if magnifier: diff --git a/tests/unit/test_magnifier/test_focusManager.py b/tests/unit/test_magnifier/test_focusManager.py index 829abbb17f4..4574f3de939 100644 --- a/tests/unit/test_magnifier/test_focusManager.py +++ b/tests/unit/test_magnifier/test_focusManager.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from _magnifier.utils.focusManager import FocusManager -from _magnifier.utils.types import Coordinates, MagnifierFollowFocusType +from _magnifier.utils.types import Coordinates, MagnifierFollowTrackingType import unittest from unittest.mock import MagicMock, Mock, patch import _ctypes @@ -19,10 +19,10 @@ def _makeFollowStateSideEffect( ): """Return a side_effect function for patching getFollowState.""" states = { - MagnifierFollowFocusType.MOUSE: followMouse, - MagnifierFollowFocusType.SYSTEM_FOCUS: followSystemFocus, - MagnifierFollowFocusType.REVIEW: followReview, - MagnifierFollowFocusType.NAVIGATOR_OBJECT: followNavigatorObject, + MagnifierFollowTrackingType.MOUSE: followMouse, + MagnifierFollowTrackingType.SYSTEM_FOCUS: followSystemFocus, + MagnifierFollowTrackingType.REVIEW: followReview, + MagnifierFollowTrackingType.NAVIGATOR_OBJECT: followNavigatorObject, } return states.__getitem__ @@ -36,9 +36,9 @@ class FocusTestParam: mousePos: tuple leftPressed: bool expectedCoords: Coordinates - expectedFocus: MagnifierFollowFocusType | None + expectedFocus: MagnifierFollowTrackingType | None description: str = "" - lastFocusedObject: MagnifierFollowFocusType | None = None + lastFocusedObject: MagnifierFollowTrackingType | None = None reviewPos: Coordinates | None = None followMouse: bool = True followSystemFocus: bool = True @@ -184,7 +184,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(0, 0), leftPressed=True, expectedCoords=Coordinates(0, 0), - expectedFocus=MagnifierFollowFocusType.MOUSE, + expectedFocus=MagnifierFollowTrackingType.MOUSE, description="Left click is pressed should return mouse position", ), FocusTestParam( @@ -193,7 +193,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(10, 10), leftPressed=False, expectedCoords=Coordinates(10, 10), - expectedFocus=MagnifierFollowFocusType.MOUSE, + expectedFocus=MagnifierFollowTrackingType.MOUSE, description="Mouse moving (not dragging)", ), FocusTestParam( @@ -202,7 +202,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(0, 0), leftPressed=False, expectedCoords=Coordinates(15, 15), - expectedFocus=MagnifierFollowFocusType.SYSTEM_FOCUS, + expectedFocus=MagnifierFollowTrackingType.SYSTEM_FOCUS, description="System focus changed alone (navigator did not move)", ), FocusTestParam( @@ -211,7 +211,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(0, 0), leftPressed=False, expectedCoords=Coordinates(20, 20), - expectedFocus=MagnifierFollowFocusType.NAVIGATOR_OBJECT, + expectedFocus=MagnifierFollowTrackingType.NAVIGATOR_OBJECT, description="Navigator object changed (NumPad navigation)", ), FocusTestParam( @@ -220,7 +220,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(0, 0), leftPressed=False, expectedCoords=Coordinates(30, 30), - expectedFocus=MagnifierFollowFocusType.NAVIGATOR_OBJECT, + expectedFocus=MagnifierFollowTrackingType.NAVIGATOR_OBJECT, description="Both system focus and navigator changed (table cell navigation): navigator wins", ), FocusTestParam( @@ -230,7 +230,7 @@ def testGetCurrentFocusCoordinates(self): leftPressed=False, reviewPos=Coordinates(30, 30), expectedCoords=Coordinates(30, 30), - expectedFocus=MagnifierFollowFocusType.REVIEW, + expectedFocus=MagnifierFollowTrackingType.REVIEW, description="Review cursor changed with followReview enabled", ), FocusTestParam( @@ -240,7 +240,7 @@ def testGetCurrentFocusCoordinates(self): leftPressed=False, reviewPos=Coordinates(30, 30), expectedCoords=Coordinates(30, 30), - expectedFocus=MagnifierFollowFocusType.REVIEW, + expectedFocus=MagnifierFollowTrackingType.REVIEW, description="Review has higher priority than navigator", ), FocusTestParam( @@ -251,7 +251,7 @@ def testGetCurrentFocusCoordinates(self): reviewPos=Coordinates(30, 30), followReview=False, expectedCoords=Coordinates(20, 20), - expectedFocus=MagnifierFollowFocusType.NAVIGATOR_OBJECT, + expectedFocus=MagnifierFollowTrackingType.NAVIGATOR_OBJECT, description="Review cursor ignored when followReview=False", ), FocusTestParam( @@ -260,9 +260,9 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(0, 0), leftPressed=False, expectedCoords=Coordinates(0, 0), - expectedFocus=MagnifierFollowFocusType.MOUSE, + expectedFocus=MagnifierFollowTrackingType.MOUSE, description="Nothing changed, last was Mouse", - lastFocusedObject=MagnifierFollowFocusType.MOUSE, + lastFocusedObject=MagnifierFollowTrackingType.MOUSE, ), FocusTestParam( navigatorObjectPos=Coordinates(0, 0), @@ -270,9 +270,9 @@ def testGetCurrentFocusCoordinates(self): mousePos=(0, 0), leftPressed=False, expectedCoords=Coordinates(0, 0), - expectedFocus=MagnifierFollowFocusType.NAVIGATOR_OBJECT, + expectedFocus=MagnifierFollowTrackingType.NAVIGATOR_OBJECT, description="Nothing changed, last was NAVIGATOR", - lastFocusedObject=MagnifierFollowFocusType.NAVIGATOR_OBJECT, + lastFocusedObject=MagnifierFollowTrackingType.NAVIGATOR_OBJECT, ), FocusTestParam( navigatorObjectPos=Coordinates(0, 0), @@ -281,9 +281,9 @@ def testGetCurrentFocusCoordinates(self): leftPressed=False, reviewPos=Coordinates(30, 30), expectedCoords=Coordinates(30, 30), - expectedFocus=MagnifierFollowFocusType.REVIEW, + expectedFocus=MagnifierFollowTrackingType.REVIEW, description="Nothing changed, last was REVIEW - returns current review position", - lastFocusedObject=MagnifierFollowFocusType.REVIEW, + lastFocusedObject=MagnifierFollowTrackingType.REVIEW, ), FocusTestParam( navigatorObjectPos=Coordinates(10, 10), @@ -291,7 +291,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(20, 20), leftPressed=False, expectedCoords=Coordinates(20, 20), - expectedFocus=MagnifierFollowFocusType.MOUSE, + expectedFocus=MagnifierFollowTrackingType.MOUSE, description="Both mouse and navigator object moved (mouse has priority)", ), FocusTestParam( @@ -300,7 +300,7 @@ def testGetCurrentFocusCoordinates(self): mousePos=Coordinates(20, 20), leftPressed=True, expectedCoords=Coordinates(20, 20), - expectedFocus=MagnifierFollowFocusType.MOUSE, + expectedFocus=MagnifierFollowTrackingType.MOUSE, description="All three moved while dragging (mouse drag has highest priority)", ), ] @@ -354,7 +354,7 @@ def testGetLastFocusType(self): """Test getting the last focus type.""" self.assertIsNone(self.focusManager.getLastFocusType()) - for focusType in MagnifierFollowFocusType: + for focusType in MagnifierFollowTrackingType: self.focusManager._lastFocusedObject = focusType self.assertEqual(self.focusManager.getLastFocusType(), focusType) @@ -401,7 +401,7 @@ def testFollowMouseDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(20, 20)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowFocusType.SYSTEM_FOCUS) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowTrackingType.SYSTEM_FOCUS) def testFollowSystemFocusDisabled(self): """When followSystemFocus=False, system focus changes are ignored and review wins.""" @@ -412,7 +412,7 @@ def testFollowSystemFocusDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(30, 30)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowFocusType.REVIEW) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowTrackingType.REVIEW) def testFollowReviewDisabled(self): """When followReview=False, review changes are ignored and navigator wins.""" @@ -423,7 +423,7 @@ def testFollowReviewDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(40, 40)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowFocusType.NAVIGATOR_OBJECT) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowTrackingType.NAVIGATOR_OBJECT) def testAllFollowDisabled(self): """When all settings are False, no source fires and focus remains frozen.""" @@ -506,12 +506,12 @@ def testFollowMouseDragIgnoresSettings(self): coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowFocusType.MOUSE) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierFollowTrackingType.MOUSE) def testDisableFollowMouseKeepsViewFrozen(self): """When followMouse is disabled, view remains frozen until a followed source changes.""" # Simulate: mouse was previously the active focus source - self.focusManager._lastFocusedObject = MagnifierFollowFocusType.MOUSE + self.focusManager._lastFocusedObject = MagnifierFollowTrackingType.MOUSE self.focusManager._lastReportedCoordinates = Coordinates(10, 10) # Positions haven't changed from last recorded values (no "change" detected) self.focusManager._lastMousePosition = Coordinates(10, 10) @@ -544,7 +544,7 @@ def testDisableFollowMouseKeepsViewFrozen(self): def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): """When followMouse is disabled, mouse movement alone does not move the view.""" - self.focusManager._lastFocusedObject = MagnifierFollowFocusType.MOUSE + self.focusManager._lastFocusedObject = MagnifierFollowTrackingType.MOUSE self.focusManager._lastReportedCoordinates = Coordinates(10, 10) self.focusManager._lastMousePosition = Coordinates(10, 10) self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) @@ -577,7 +577,7 @@ def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): def testDisableFollowSystemFocusKeepsViewFrozen(self): """When followSystemFocus is disabled, view remains frozen until a followed source changes.""" - self.focusManager._lastFocusedObject = MagnifierFollowFocusType.SYSTEM_FOCUS + self.focusManager._lastFocusedObject = MagnifierFollowTrackingType.SYSTEM_FOCUS self.focusManager._lastReportedCoordinates = Coordinates(20, 20) self.focusManager._lastMousePosition = Coordinates(10, 10) self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) From 1f277f15a449afd565350234df6de48421e8c3f0 Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Wed, 10 Jun 2026 14:04:16 +0200 Subject: [PATCH 3/8] copilot review --- source/_magnifier/config.py | 10 +++++----- source/_magnifier/utils/types.py | 2 +- source/gui/settingsDialogs.py | 14 +++++++------- user_docs/en/userGuide.md | 5 +++-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/source/_magnifier/config.py b/source/_magnifier/config.py index 8f9a49a6912..80cb89e3bcd 100644 --- a/source/_magnifier/config.py +++ b/source/_magnifier/config.py @@ -181,19 +181,19 @@ def _ensureSavedStatesInitialized() -> None: def getFollowState(trackingType: MagnifierFollowTrackingType) -> bool: """ - Get the current follow state for a given focus type. + Get the current follow state for a given tracking type. - :param trackingType: The focus type to query. - :return: True if the magnifier follows the given focus type, False otherwise. + :param trackingType: The tracking type to query. + :return: True if the magnifier follows the given tracking type, False otherwise. """ return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] def setFollowState(trackingType: MagnifierFollowTrackingType, state: bool) -> None: """ - Set the follow state for a given focus type. + Set the follow state for a given tracking type. - :param trackingType: The focus type to update. + :param trackingType: The tracking type to update. :param state: True to enable following, False to disable. """ config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] = state diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index 1c25b55f628..5cb84863555 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -90,7 +90,7 @@ def _displayStringLabels(self) -> dict["MagnifierAction", str]: class MagnifierFollowTrackingType(DisplayStringEnum): - """Type of focus the magnifier should follow based on user settings""" + """Type of tracking the magnifier should follow based on user settings""" MOUSE = auto() SYSTEM_FOCUS = auto() diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index f80bb401a2f..b1fcae4238a 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2261,10 +2261,10 @@ class ReviewCursorPanel(SettingsPanel): def makeSettings(self, settingsSizer): # Translators: This is the label for a checkbox in the # review cursor settings panel. - self.followTrackingCheckBox = wx.CheckBox(self, label=_("Follow system &focus")) - self.bindHelpEvent("ReviewCursorFollowFocus", self.followTrackingCheckBox) - self.followTrackingCheckBox.SetValue(config.conf["reviewCursor"]["followTracking"]) - settingsSizer.Add(self.followTrackingCheckBox, border=10, flag=wx.BOTTOM) + self.followFocusCheckBox = wx.CheckBox(self, label=_("Follow system &focus")) + self.bindHelpEvent("ReviewCursorFollowFocus", self.followFocusCheckBox) + self.followFocusCheckBox.SetValue(config.conf["reviewCursor"]["followFocus"]) + settingsSizer.Add(self.followFocusCheckBox, border=10, flag=wx.BOTTOM) # Translators: This is the label for a checkbox in the # review cursor settings panel. self.followCaretCheckBox = wx.CheckBox(self, label=_("Follow System &Caret")) @@ -2285,7 +2285,7 @@ def makeSettings(self, settingsSizer): settingsSizer.Add(self.simpleReviewModeCheckBox, border=10, flag=wx.BOTTOM) def onSave(self): - config.conf["reviewCursor"]["followTracking"] = self.followTrackingCheckBox.IsChecked() + config.conf["reviewCursor"]["followFocus"] = self.followTrackingCheckBox.IsChecked() config.conf["reviewCursor"]["followCaret"] = self.followCaretCheckBox.IsChecked() config.conf["reviewCursor"]["followMouse"] = self.followMouseCheckBox.IsChecked() config.conf["reviewCursor"]["simpleReviewMode"] = self.simpleReviewModeCheckBox.IsChecked() @@ -6055,7 +6055,7 @@ def _applyCurrentSettingsToConfigAndRuntime(self): magnifierConfig.setPanStep(selectedPanStep) magnifierConfig.setFilter(selectedFilter) magnifierConfig.setFullscreenMode(selectedMode) - config.conf["magnifier"]["isTrueCentered"] = self.trueCenterCheckBox.GetValue() + config.conf["magnifier"]["isTrueCentered"] = self.trueCenterTrackingCheckBox.GetValue() for trackingType, checkBox in self._followTrackingCheckBoxes.items(): magnifierConfig.setFollowState(trackingType, checkBox.GetValue()) @@ -6180,7 +6180,7 @@ def makeSettings( self.panSpinCtrl.Bind(wx.EVT_TEXT, self._onImmediateSettingChange) # TRACKING GROUP - # Translators: This is the label for a group of focus options in the magnifier settings panel + # Translators: This is the label for a group of tracking options in the magnifier settings panel trackingGroupText = _("Tracking") trackingGroupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=trackingGroupText) trackingGroupBox = trackingGroupSizer.GetStaticBox() diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index 03eac7b08b5..d0bcf8c5138 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -1634,7 +1634,8 @@ This mode keeps the followed element at the center of the screen and clamps to t To disable clamping, activate [true center tracking in the Magnifier settings](#MagnifierUseTrueCenterTracking). * Border: The magnified area only moves when the followed element approaches the edge of the visible area. This mode provides a more stable view, only adjusting when necessary. -* Relative: The magnified area maintains the relative position of the followed element with the screen. The magnified area will be centered if the followed element is in the center of the screen, and will be at the edge of the screen if the followed element is at the edge of the screen. +* Relative: The magnified area maintains the relative position of the followed element within the screen. +The magnified area will be centered if the followed element is in the center of the screen, and will be at the edge of the screen if the followed element is at the edge of the screen. This will move the tracking progressively towards the edge of the magnified area as it moves towards the edge of the screen, and will move progressively towards the center of the screen as it moves towards the center. To cycle through the tracking modes, please assign a custom gesture using the [Input Gestures dialog](#InputGestures). @@ -2914,7 +2915,7 @@ The available options are: | Grayscale | Converts all colors to shades of gray, which can help reduce eye strain and improve contrast. | | Inverted | Inverts all colors on the screen, which can be helpful for users who prefer light text on dark backgrounds or have photophobia. | -##### Use true center {#MagnifierUseTrueCenter} +##### Use true center tracking {#MagnifierUseTrueCenterTracking} This checkbox controls whether the magnifier should always keep the followed element centered on the screen, or if it should allow the followed element to move towards the edges of the screen before moving the magnified area. When enabled, the magnifier will always keep the followed element centered on the screen, which can be helpful for users who prefer a consistent position of the followed element within the magnified view. Getting close to the edge of the screen, users will see out of screen areas. From 86de732b61c9dd1605b793f78cb6b03ce8cd3cb8 Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Wed, 10 Jun 2026 14:28:58 +0200 Subject: [PATCH 4/8] little changes --- source/_magnifier/utils/types.py | 6 +++--- source/gui/settingsDialogs.py | 4 ++-- user_docs/en/userGuide.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index 5cb84863555..5085b7bd26e 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -155,11 +155,11 @@ class FullScreenMode(DisplayStringStrEnum): @property def _displayStringLabels(self) -> dict["FullScreenMode", str]: return { - # Translators: Magnifier focus mode - center mouse/focus on screen. + # Translators: Magnifier tracking mode - center on screen. self.CENTER: pgettext("magnifier", "Center"), - # Translators: Magnifier focus mode - follow focus at screen borders. + # Translators: Magnifier tracking mode - follow tracking at screen borders. self.BORDER: pgettext("magnifier", "Border"), - # Translators: Magnifier focus mode - maintain relative position. + # Translators: Magnifier tracking mode - maintain relative position. self.RELATIVE: pgettext("magnifier", "Relative"), } diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index b1fcae4238a..31ea97f5038 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -6225,7 +6225,7 @@ def makeSettings( # FULLSCREEN MODE SETTINGS # Translators: The label for a setting in magnifier settings to select the full-screen mode - fullscreenModeLabelText = _("Focus &mode:") + fullscreenModeLabelText = _("Tracking &mode:") fullscreenModeChoices = [mode.displayString for mode in FullScreenMode] if FullScreenMode else [] self.fullscreenModeList = fullscreenGroup.addLabeledControl( fullscreenModeLabelText, @@ -6233,7 +6233,7 @@ def makeSettings( choices=fullscreenModeChoices, ) self.bindHelpEvent( - "MagnifierFullscreenFocusMode", + "MagnifierFullscreenTrackingMode", self.fullscreenModeList, ) diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index d0bcf8c5138..e53ef3fc435 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -1590,7 +1590,7 @@ Once the magnifier is enabled, you can use the following keyboard commands to co | Increases the magnification level of the magnifier | `NVDA+shift+equals` | Increases the zoom level. Starts the magnifier if it's not already running. | | Decreases the magnification level of the magnifier | `NVDA+shift+minus` | Decreases the zoom level | | Toggle filter of the magnifier | `NVDA+shift+i` | Cycles through available color filters (normal, grayscale, inverted) | -| Toggle focus mode for the full-screen magnifier | None | Cycles through focus tracking modes (center, border, relative) | +| Toggle tracking for the full-screen magnifier | None | Cycles through tracking modes (center, border, relative) | | Launch spotlight if magnifier is full-screen | `NVDA+shift+l` | Activates spotlight mode for focused reading or presentations | | Pan left | `NVDA+alt+leftArrow` | Pan the magnified view to the left by the specified panning step size | | Pan right | `NVDA+alt+rightArrow` | Pan the magnified view to the right by the specified panning step size | From 4687dfe287c78c8dcf9281ad6eddc4a27b8828d0 Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Mon, 15 Jun 2026 11:32:43 +0200 Subject: [PATCH 5/8] revert unwanted change --- source/gui/settingsDialogs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 5139f840398..40c926a1455 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -6276,6 +6276,7 @@ def onDiscard(self): magnifier = getMagnifier() if magnifier: magnifier.zoomLevel = self._zoomInitially + magnifier._panStep = self._panStepInitially magnifier.filterType = self._filterInitially if isinstance(magnifier, FullScreenMagnifier): magnifier._fullscreenMode = self._fullscreenModeInitially From c12cebe67492eb3c5fbd9ff6862c6013d191599e Mon Sep 17 00:00:00 2001 From: Boumtchack <147637328+Boumtchack@users.noreply.github.com> Date: Mon, 15 Jun 2026 17:22:57 +0200 Subject: [PATCH 6/8] Update source/_magnifier/utils/types.py Co-authored-by: Cyrille Bougot --- source/_magnifier/utils/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index a73d201ffcd..599539f28b2 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -101,7 +101,7 @@ class MagnifierTrackingType(DisplayStringEnum): def _displayStringLabels(self) -> dict["MagnifierTrackingType", str]: return { # Translators: Tracking type for magnifier to follow - mouse cursor. - self.MOUSE: pgettext("magnifier follow tracking type", "Mouse"), + self.MOUSE: pgettext("magnifier tracking type", "Mouse"), # Translators: Tracking type for magnifier to follow - system focus (active element). self.SYSTEM_FOCUS: pgettext("magnifier follow tracking type", "System focus"), # Translators: Tracking type for magnifier to follow - review cursor position. From f4d34cfccbcc070f770af5bef63d16cd49389dfc Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Tue, 16 Jun 2026 14:00:23 +0200 Subject: [PATCH 7/8] changed focus or follow to track --- source/_magnifier/commands.py | 52 ++-- source/_magnifier/config.py | 22 +- source/_magnifier/fullscreenMagnifier.py | 18 +- source/_magnifier/magnifier.py | 10 +- source/_magnifier/utils/spotlightManager.py | 16 +- .../{focusManager.py => trackingManager.py} | 52 ++-- source/_magnifier/utils/types.py | 76 ++--- source/config/configSpec.py | 2 +- source/globalCommands.py | 22 +- source/gui/settingsDialogs.py | 60 ++-- .../unit/test_magnifier/test_focusManager.py | 286 +++++++++--------- .../test_fullscreenMagnifier.py | 14 +- tests/unit/test_magnifier/test_magnifier.py | 34 +-- .../test_magnifier/test_spotlightManager.py | 20 +- user_docs/en/userGuide.md | 5 +- 15 files changed, 349 insertions(+), 340 deletions(-) rename source/_magnifier/utils/{focusManager.py => trackingManager.py} (87%) diff --git a/source/_magnifier/commands.py b/source/_magnifier/commands.py index 3dd71c98412..5ee42802ab3 100644 --- a/source/_magnifier/commands.py +++ b/source/_magnifier/commands.py @@ -13,11 +13,11 @@ from . import changeMagnifiedView, getMagnifier, start, stop from .config import ( setMagnifiedView, - getFollowState, + getTrackingState, setFilter, - setFollowState, - setFullscreenMode, - toggleAllFollowStates, + setTrackingState, + setFullscreenTrackingMode, + toggleAllTrackingStates, ZoomLevel, ) from .magnifier import Magnifier @@ -26,7 +26,7 @@ Filter, Direction, MagnifiedView, - FullScreenMode, + FullScreenTrackingMode, MagnifierAction, MagnifierTrackingType, ) @@ -199,67 +199,67 @@ def cycleMagnifiedView() -> None: ) -def toggleFollow(focusType: MagnifierTrackingType) -> None: +def toggleTracking() -> None: """ - Toggle the specified follow mode setting. + Toggle the specified tracking mode setting. - :param MagnifierTrackingType: The follow mode to toggle (mouse, system focus, review cursor, navigator object) + :param MagnifierTrackingType: The tracking mode to toggle (mouse, system focus, review cursor, navigator object) """ magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, - MagnifierAction.TOGGLE_FOLLOW_SETTINGS, + MagnifierAction.TOGGLE_TRACKING_SETTINGS, ): - state = not getFollowState(MagnifierTrackingType) - setFollowState(MagnifierTrackingType, state) + state = not getTrackingState(MagnifierTrackingType) + setTrackingState(MagnifierTrackingType, state) ui.message( pgettext( "magnifier", - # Translators: Message announced when toggling a follow setting with {setting} being the name of the setting and {state} being either "enabled" or "disabled". + # Translators: Message announced when toggling a tracking setting with {setting} being the name of the setting and {state} being either "enabled" or "disabled". "{setting} {state}", ).format( setting=MagnifierTrackingType.displayString, state=pgettext( "magnifier", - # Translators: State of the follow setting being toggled enabled. + # Translators: State of the tracking setting being toggled enabled. "enabled", ) if state else pgettext( "magnifier", - # Translators: State of the follow setting being toggled disabled. + # Translators: State of the tracking setting being toggled disabled. "disabled", ), ), ) -def toggleAllFollow() -> None: - """Toggle all follow settings at once.""" +def toggleAllTracking() -> None: + """Toggle all tracking settings at once.""" magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, - MagnifierAction.TOGGLE_FOLLOW_SETTINGS, + MagnifierAction.TOGGLE_TRACKING_SETTINGS, ): - isDisabledNow = toggleAllFollowStates() + isDisabledNow = toggleAllTrackingStates() if isDisabledNow: stateMessage = pgettext( "magnifier", - # Translators: State of all follow settings being toggled disabled. + # Translators: State of all tracking settings being toggled disabled. "All tracking settings disabled", ) else: stateMessage = pgettext( "magnifier", - # Translators: State of all follow settings being restored. + # Translators: State of all tracking settings being restored. "Tracking settings restored", ) ui.message(stateMessage) -def cycleFullscreenMode() -> None: - """Cycle through full-screen modes (center, border, relative)""" +def cycleFullscreenTrackingMode() -> None: + """Cycle through full-screen tracking modes (center, border, relative)""" magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, @@ -270,13 +270,13 @@ def cycleFullscreenMode() -> None: MagnifierAction.CHANGE_FULLSCREEN_MODE, ): fullscreenMagnifier: FullScreenMagnifier = magnifier - modes = list(FullScreenMode) - currentMode = fullscreenMagnifier._fullscreenMode + modes = list(FullScreenTrackingMode) + currentMode = fullscreenMagnifier._trackingMode idx = modes.index(currentMode) newMode = modes[(idx + 1) % len(modes)] log.debug(f"Changing full-screen mode from {currentMode} to {newMode}") - fullscreenMagnifier._fullscreenMode = newMode - setFullscreenMode(newMode) + fullscreenMagnifier._trackingMode = newMode + setFullscreenTrackingMode(newMode) ui.message( pgettext( "magnifier", diff --git a/source/_magnifier/config.py b/source/_magnifier/config.py index 0bd77cca82a..dd8fc4a3551 100644 --- a/source/_magnifier/config.py +++ b/source/_magnifier/config.py @@ -10,7 +10,7 @@ import config from dataclasses import dataclass, field -from .utils.types import Filter, FullScreenMode, MagnifierTrackingType, MagnifiedView +from .utils.types import Filter, FullScreenTrackingMode, MagnifierTrackingType, MagnifiedView def setEnabled(enable: bool) -> None: @@ -179,7 +179,7 @@ def _ensureSavedStatesInitialized() -> None: saveFollowStates() -def getFollowState(trackingType: MagnifierTrackingType) -> bool: +def getTrackingState(trackingType: MagnifierTrackingType) -> bool: """ Get the current follow state for a given tracking type. @@ -189,7 +189,7 @@ def getFollowState(trackingType: MagnifierTrackingType) -> bool: return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] -def setFollowState(trackingType: MagnifierTrackingType, state: bool) -> None: +def setTrackingState(trackingType: MagnifierTrackingType, state: bool) -> None: """ Set the follow state for a given tracking type. @@ -202,10 +202,10 @@ def setFollowState(trackingType: MagnifierTrackingType, state: bool) -> None: def saveFollowStates() -> None: """Save current follow states so they can be restored later.""" for trackingType in _FOLLOW_CONFIG_KEYS: - _followStateOverride.savedStates[trackingType] = getFollowState(trackingType) + _followStateOverride.savedStates[trackingType] = getTrackingState(trackingType) -def toggleAllFollowStates() -> bool: +def toggleAllTrackingStates() -> bool: """ Toggle all follow states between forced-disabled and previously saved states. @@ -214,12 +214,12 @@ def toggleAllFollowStates() -> bool: _ensureSavedStatesInitialized() if _followStateOverride.isActive: for trackingType, state in _followStateOverride.savedStates.items(): - setFollowState(trackingType, state) + setTrackingState(trackingType, state) _followStateOverride.isActive = False else: saveFollowStates() for trackingType in _FOLLOW_CONFIG_KEYS: - setFollowState(trackingType, False) + setTrackingState(trackingType, False) _followStateOverride.isActive = True return _followStateOverride.isActive @@ -233,19 +233,19 @@ def isTrueCentered() -> bool: return config.conf["magnifier"]["isTrueCentered"] -def getFullscreenMode() -> FullScreenMode: +def getFullscreenTrackingMode() -> FullScreenTrackingMode: """ Get full-screen mode from config. :return: The full-screen mode. """ - return FullScreenMode(config.conf["magnifier"]["fullscreenMode"]) + return FullScreenTrackingMode(config.conf["magnifier"]["fullscreenTrackingMode"]) -def setFullscreenMode(mode: FullScreenMode) -> None: +def setFullscreenTrackingMode(mode: FullScreenTrackingMode) -> None: """ Set full-screen mode from settings. :param mode: The full-screen mode to set. """ - config.conf["magnifier"]["fullscreenMode"] = mode.value + config.conf["magnifier"]["fullscreenTrackingMode"] = mode.value diff --git a/source/_magnifier/fullscreenMagnifier.py b/source/_magnifier/fullscreenMagnifier.py index 95aff7a3b9c..e220066f241 100644 --- a/source/_magnifier/fullscreenMagnifier.py +++ b/source/_magnifier/fullscreenMagnifier.py @@ -19,12 +19,12 @@ from .utils.types import ( Filter, MagnifiedView, - FullScreenMode, + FullScreenTrackingMode, Size, MagnifierParameters, Coordinates, ) -from .config import getFullscreenMode, isTrueCentered +from .config import isTrueCentered, getFullscreenTrackingMode from .utils.errorHandling import trackNativeMagnifierErrors @@ -36,7 +36,7 @@ class FullScreenMagnifier(Magnifier): def __init__(self): super().__init__() - self._fullscreenMode = getFullscreenMode() + self._trackingMode = getFullscreenTrackingMode() self.currentCoordinates = Coordinates(0, 0) self._spotlightManager = SpotlightManager(self) self._displaySize = Size(self._displayOrientation.width, self._displayOrientation.height) @@ -62,7 +62,7 @@ def _startMagnifier(self) -> None: """ super()._startMagnifier() log.debug( - f"Starting magnifier with zoom level {self.zoomLevel} and filter {self.filterType} and full-screen mode {self._fullscreenMode}", + f"Starting magnifier with zoom level {self.zoomLevel} and filter {self.filterType} and full-screen mode {self._trackingMode}", ) try: self._initializeNativeMagnification() @@ -265,12 +265,12 @@ def _getCoordinatesForMode( :return: Adjusted coordinates according to full-screen mode """ - match self._fullscreenMode: - case FullScreenMode.RELATIVE: + match self._trackingMode: + case FullScreenTrackingMode.RELATIVE: return self._relativePos(coordinates) - case FullScreenMode.BORDER: + case FullScreenTrackingMode.BORDER: return self._borderPos(coordinates) - case FullScreenMode.CENTER: + case FullScreenTrackingMode.CENTER: return coordinates def _borderPos( @@ -356,7 +356,7 @@ def _startSpotlight(self) -> None: Launch Spotlight from Full-screen class """ log.debug( - f"Launching spotlight mode from full-screen magnifier with mode {self._fullscreenMode}", + f"Launching spotlight mode from full-screen magnifier with mode {self._trackingMode}", ) self._stopTimer() self._spotlightManager._startSpotlight() diff --git a/source/_magnifier/magnifier.py b/source/_magnifier/magnifier.py index e221bb90573..0a933e3839d 100644 --- a/source/_magnifier/magnifier.py +++ b/source/_magnifier/magnifier.py @@ -33,7 +33,7 @@ setZoomLevel, ZoomLevel, ) -from .utils.focusManager import FocusManager +from .utils.trackingManager import TrackingManager class Magnifier: @@ -48,7 +48,7 @@ def __init__(self): self._zoomLevel: float = getZoomLevel() self._panStep: int = getPanStep() self._timer: None | wx.Timer = None - self._focusManager = FocusManager() + self._trackingManager = TrackingManager() self._lastScreenPosition = Coordinates(0, 0) self._currentCoordinates = Coordinates(0, 0) self._lastFocusCoordinates = Coordinates(0, 0) @@ -182,7 +182,7 @@ def _startMagnifier(self) -> None: return self._isActive = True - self.currentCoordinates = self._focusManager.getCurrentFocusCoordinates() + self.currentCoordinates = self._trackingManager.getCurrentTrackedCoordinates() def _updateMagnifier(self) -> None: """ @@ -196,7 +196,7 @@ def _updateMagnifier(self) -> None: try: self._managePanning() if not self._isManualPanning: - self.currentCoordinates = self._focusManager.getCurrentFocusCoordinates() + self.currentCoordinates = self._trackingManager.getCurrentTrackedCoordinates() self._doUpdate() self._consecutiveErrors = 0 self._recoveryAttempts = 0 @@ -350,7 +350,7 @@ def _managePanning(self) -> None: """ Ensure that manual panning mode (self._isManualPanning) is set to False when focus coordinates change. """ - focusCoordinates = self._focusManager.getCurrentFocusCoordinates() + focusCoordinates = self._trackingManager.getCurrentTrackedCoordinates() if self._isManualPanning: if focusCoordinates != self._lastFocusCoordinates: self._isManualPanning = False diff --git a/source/_magnifier/utils/spotlightManager.py b/source/_magnifier/utils/spotlightManager.py index ff641a979c8..b3b5895fc76 100644 --- a/source/_magnifier/utils/spotlightManager.py +++ b/source/_magnifier/utils/spotlightManager.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Callable import ui -from .types import Coordinates, ZoomHistory, FullScreenMode +from .types import Coordinates, ZoomHistory, FullScreenTrackingMode import wx from logHandler import log @@ -29,10 +29,12 @@ def __init__( self._timer: wx.CallLater | None = None self._animationSteps: int = 40 self._animationStepDelay: int = 12 - self._currentCoordinates: Coordinates = fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() + self._currentCoordinates: Coordinates = ( + fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() + ) self._originalZoomLevel: int = 0 self._currentZoomLevel: float = 0.0 - self._originalMode: FullScreenMode | None = None + self._originalMode: FullScreenTrackingMode | None = None def _startSpotlight(self) -> None: """ @@ -45,7 +47,7 @@ def _startSpotlight(self) -> None: self._spotlightIsActive = True - startCoords = self._fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() + startCoords = self._fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() startCoords = self._fullscreenMagnifier._getCoordinatesForMode(startCoords) centerScreen = Coordinates( self._fullscreenMagnifier._displayOrientation.width // 2, @@ -53,7 +55,7 @@ def _startSpotlight(self) -> None: ) # Save the current mode for zoom back - self._originalMode = self._fullscreenMagnifier._fullscreenMode + self._originalMode = self._fullscreenMagnifier._trackingMode self._currentCoordinates = startCoords self._animateZoom(ZoomHistory(1.0, centerScreen), self._startMouseMonitoring) @@ -160,9 +162,9 @@ def zoomBack(self) -> None: f"zoom back with original zoom level {self._originalZoomLevel} and current zoom level {self._currentZoomLevel}", ) - focus = self._fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() + focus = self._fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() - if self._originalMode == FullScreenMode.RELATIVE: + if self._originalMode == FullScreenTrackingMode.RELATIVE: savedZoom = self._fullscreenMagnifier.zoomLevel self._fullscreenMagnifier.zoomLevel = self._originalZoomLevel endCoordinates = self._fullscreenMagnifier._relativePos(focus) diff --git a/source/_magnifier/utils/focusManager.py b/source/_magnifier/utils/trackingManager.py similarity index 87% rename from source/_magnifier/utils/focusManager.py rename to source/_magnifier/utils/trackingManager.py index 64fd731d05f..5aa6a38437b 100644 --- a/source/_magnifier/utils/focusManager.py +++ b/source/_magnifier/utils/trackingManager.py @@ -4,8 +4,8 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt """ -Focus Manager for the magnifier module. -Handles all focus tracking logic and coordinate calculations. +Tracking Manager for the magnifier module. +Handles all tracking logic and coordinate calculations. """ from comtypes import COMError @@ -17,20 +17,20 @@ import textInfos from textInfos.offsets import OffsetsTextInfo from .types import Coordinates, MagnifierTrackingType -from ..config import getFollowState +from ..config import getTrackingState -class FocusManager: +class TrackingManager: """ - Manages focus tracking for the magnifier. + Manages tracking for the magnifier. Tracks mouse, system focus, and navigator object positions. """ _SYSTEM_FOCUS_STICKINESS_SECONDS: float = 0.12 def __init__(self): - """Initialize the focus manager.""" - self._lastFocusedObject: MagnifierTrackingType | None = None + """Initialize the tracking manager.""" + self._lastTrackedObject: MagnifierTrackingType | None = None self._lastReportedCoordinates = Coordinates(0, 0) self._lastMousePosition = Coordinates(0, 0) self._lastSystemFocusPosition = Coordinates(0, 0) @@ -41,9 +41,9 @@ def __init__(self): self._lastValidNavigatorObjectPosition = Coordinates(0, 0) self._lastSystemFocusChangeTime: float = 0.0 - def getCurrentFocusCoordinates(self) -> Coordinates: + def getCurrentTrackedCoordinates(self) -> Coordinates: """ - Get the current focus coordinates based on priority. + Get the current tracked coordinates based on priority. Priority: Mouse (drag) > Mouse > System Focus > Review > Navigator Object. Special case: when both the system focus and navigator object change simultaneously but the review cursor does not (e.g. table cell navigation via numpad), the navigator @@ -51,7 +51,7 @@ def getCurrentFocusCoordinates(self) -> Coordinates: Each source is only considered when its corresponding setting is enabled. - :return: The (x, y) coordinates of the current focus + :return: The (x, y) coordinates of the current tracked object """ now = time.monotonic() @@ -62,10 +62,10 @@ def getCurrentFocusCoordinates(self) -> Coordinates: isClickPressed = winUser.getAsyncKeyState(winUser.VK_LBUTTON) < 0 # Cache settings once — each call reads from config.conf - isFollowMouse = getFollowState(MagnifierTrackingType.MOUSE) - isFollowSystemFocus = getFollowState(MagnifierTrackingType.SYSTEM_FOCUS) - isFollowReviewCursor = getFollowState(MagnifierTrackingType.REVIEW) - isFollowNavigatorObject = getFollowState(MagnifierTrackingType.NAVIGATOR_OBJECT) + isFollowMouse = getTrackingState(MagnifierTrackingType.MOUSE) + isFollowSystemFocus = getTrackingState(MagnifierTrackingType.SYSTEM_FOCUS) + isFollowReviewCursor = getTrackingState(MagnifierTrackingType.REVIEW) + isFollowNavigatorObject = getTrackingState(MagnifierTrackingType.NAVIGATOR_OBJECT) mouseChanged = self._lastMousePosition != mousePosition systemFocusChanged = self._lastSystemFocusPosition != systemFocusPosition @@ -85,7 +85,7 @@ def getCurrentFocusCoordinates(self) -> Coordinates: # Priority 1: Mouse — drag (fires even when stationary) or movement if (isClickPressed or mouseChanged) and isFollowMouse: - self._lastFocusedObject = MagnifierTrackingType.MOUSE + self._lastTrackedObject = MagnifierTrackingType.MOUSE return self._rememberAndReturnCoordinates(mousePosition) # Special case: table cell navigation (numpad). @@ -93,22 +93,22 @@ def getCurrentFocusCoordinates(self) -> Coordinates: # review cursor does not, the navigator object reflects the user's explicit navigation # intent and therefore takes priority over the system focus. if navigatorChanged and systemFocusChanged and not reviewChanged and isFollowNavigatorObject: - self._lastFocusedObject = MagnifierTrackingType.NAVIGATOR_OBJECT + self._lastTrackedObject = MagnifierTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Priority 2: System focus (focus object + browse mode cursor) if systemFocusChanged and isFollowSystemFocus: - self._lastFocusedObject = MagnifierTrackingType.SYSTEM_FOCUS + self._lastTrackedObject = MagnifierTrackingType.SYSTEM_FOCUS return self._rememberAndReturnCoordinates(systemFocusPosition) # Priority 3: Review cursor if reviewChanged and isFollowReviewCursor and reviewPosition is not None: - self._lastFocusedObject = MagnifierTrackingType.REVIEW + self._lastTrackedObject = MagnifierTrackingType.REVIEW return self._rememberAndReturnCoordinates(reviewPosition) # Priority 4: Navigator object (NumPad navigation) if navigatorChanged and isFollowNavigatorObject: - self._lastFocusedObject = MagnifierTrackingType.NAVIGATOR_OBJECT + self._lastTrackedObject = MagnifierTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Resolve the effective review position once (fallback to last valid when None) @@ -125,11 +125,11 @@ def getCurrentFocusCoordinates(self) -> Coordinates: ) # Keep current source if still enabled; otherwise clear it and freeze at _lastReportedCoordinates - for focusType, isEnabled, position in _sources: - if self._lastFocusedObject == focusType: + for trackedType, isEnabled, position in _sources: + if self._lastTrackedObject == trackedType: if isEnabled: return self._rememberAndReturnCoordinates(position) - self._lastFocusedObject = None + self._lastTrackedObject = None break # No eligible update event from an enabled source. @@ -271,10 +271,10 @@ def _getNavigatorObjectPosition(self) -> Coordinates: return position return self._lastValidNavigatorObjectPosition - def getLastFocusType(self) -> MagnifierTrackingType | None: + def getLastTrackedType(self) -> MagnifierTrackingType | None: """ - Get the type of the last focused object. + Get the type of the last tracked object. - :return: The type of the last focused object, or None when no focus source is active. + :return: The type of the last tracked object, or None when no tracked source is active. """ - return self._lastFocusedObject + return self._lastTrackedObject diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index a73d201ffcd..b6311340228 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -49,48 +49,48 @@ class MagnifierAction(DisplayStringEnum): PAN_BOTTOM_EDGE = auto() TOGGLE_FILTER = auto() CHANGE_MAGNIFIER_VIEW = auto() - TOGGLE_FOLLOW_SETTINGS = auto() + TOGGLE_TRACKING_SETTINGS = auto() CHANGE_FULLSCREEN_MODE = auto() START_SPOTLIGHT = auto() @property def _displayStringLabels(self) -> dict["MagnifierAction", str]: return { - # Translators: Action description for zooming in. + # Translators: Action description for zooming in self.ZOOM_IN: pgettext("magnifier action", "zoom in"), - # Translators: Action description for zooming out. + # Translators: Action description for zooming out self.ZOOM_OUT: pgettext("magnifier action", "zoom out"), - # Translators: Action description for panning left. + # Translators: Action description for panning left self.PAN_LEFT: pgettext("magnifier action", "pan left"), - # Translators: Action description for panning right. + # Translators: Action description for panning right self.PAN_RIGHT: pgettext("magnifier action", "pan right"), - # Translators: Action description for panning up. + # Translators: Action description for panning up self.PAN_UP: pgettext("magnifier action", "pan up"), - # Translators: Action description for panning down. + # Translators: Action description for panning down self.PAN_DOWN: pgettext("magnifier action", "pan down"), - # Translators: Action description for panning to left edge. + # Translators: Action description for panning to left edge self.PAN_LEFT_EDGE: pgettext("magnifier action", "pan to left edge"), - # Translators: Action description for panning to right edge. + # Translators: Action description for panning to right edge self.PAN_RIGHT_EDGE: pgettext("magnifier action", "pan to right edge"), - # Translators: Action description for panning to top edge. + # Translators: Action description for panning to top edge self.PAN_TOP_EDGE: pgettext("magnifier action", "pan to top edge"), - # Translators: Action description for panning to bottom edge. + # Translators: Action description for panning to bottom edge self.PAN_BOTTOM_EDGE: pgettext("magnifier action", "pan to bottom edge"), - # Translators: Action description for toggling settings. - self.TOGGLE_FOLLOW_SETTINGS: pgettext("magnifier action", "toggle tracking settings"), - # Translators: Action description for toggling color filters. + # Translators: Action description for toggling settings + self.TOGGLE_TRACKING_SETTINGS: pgettext("magnifier action", "toggle tracking settings"), + # Translators: Action description for toggling color filters self.TOGGLE_FILTER: pgettext("magnifier action", "cycle color filters"), - # Translators: Action description for changing magnifier view. + # Translators: Action description for changing magnifier view self.CHANGE_MAGNIFIER_VIEW: pgettext("magnifier action", "change magnifier view"), - # Translators: Action description for changing full-screen mode. + # Translators: Action description for changing full-screen mode self.CHANGE_FULLSCREEN_MODE: pgettext("magnifier action", "change full-screen mode"), - # Translators: Action description for showing entire screen overview. + # Translators: Action description for showing entire screen overview self.START_SPOTLIGHT: pgettext("magnifier action", "show screen overview"), } class MagnifierTrackingType(DisplayStringEnum): - """Type of focus the magnifier should follow based on user settings""" + """Type of tracking the magnifier should follow based on user settings""" MOUSE = auto() SYSTEM_FOCUS = auto() @@ -100,14 +100,14 @@ class MagnifierTrackingType(DisplayStringEnum): @property def _displayStringLabels(self) -> dict["MagnifierTrackingType", str]: return { - # Translators: Tracking type for magnifier to follow - mouse cursor. - self.MOUSE: pgettext("magnifier follow tracking type", "Mouse"), - # Translators: Tracking type for magnifier to follow - system focus (active element). - self.SYSTEM_FOCUS: pgettext("magnifier follow tracking type", "System focus"), - # Translators: Tracking type for magnifier to follow - review cursor position. - self.REVIEW: pgettext("magnifier follow tracking type", "Review cursor"), - # Translators: Tracking type for magnifier to follow - navigator object position. - self.NAVIGATOR_OBJECT: pgettext("magnifier follow tracking type", "Navigator object"), + # Translators: Type of item tracked by the Magnifier - mouse cursor + self.MOUSE: pgettext("magnifier", "Mouse"), + # Translators: Type of item tracked by the Magnifier - system focus (active element) + self.SYSTEM_FOCUS: pgettext("magnifier", "System focus"), + # Translators: Type of item tracked by the Magnifier - review cursor position + self.REVIEW: pgettext("magnifier", "Review cursor"), + # Translators: Type of item tracked by the Magnifier - navigator object position + self.NAVIGATOR_OBJECT: pgettext("magnifier", "Navigator object"), } @@ -122,13 +122,13 @@ class MagnifiedView(DisplayStringStrEnum): @property def _displayStringLabels(self) -> dict["MagnifiedView", str]: return { - # Translators: Magnifier view - full-screen mode. + # Translators: Magnifier view - full-screen mode self.FULLSCREEN: pgettext("magnifier", "Fullscreen"), - # Translators: Magnifier view - fixed mode. + # Translators: Magnifier view - fixed mode self.FIXED: pgettext("magnifier", "Fixed"), - # Translators: Magnifier view - docked mode. + # Translators: Magnifier view - docked mode self.DOCKED: pgettext("magnifier", "Docked"), - # Translators: Magnifier view - lens mode. + # Translators: Magnifier view - lens mode self.LENS: pgettext("magnifier", "Lens"), } @@ -147,19 +147,19 @@ class ZoomHistory(NamedTuple): coordinates: Coordinates -class FullScreenMode(DisplayStringStrEnum): +class FullScreenTrackingMode(DisplayStringStrEnum): CENTER = "center" BORDER = "border" RELATIVE = "relative" @property - def _displayStringLabels(self) -> dict["FullScreenMode", str]: + def _displayStringLabels(self) -> dict["FullScreenTrackingMode", str]: return { - # Translators: Magnifier tracking mode - center on screen. + # Translators: Magnifier tracking mode - center the tracked item on screen self.CENTER: pgettext("magnifier", "Center"), - # Translators: Magnifier tracking mode - follow tracking at screen borders. + # Translators: Magnifier tracking mode - follow the tracked item only when it reaches screen borders self.BORDER: pgettext("magnifier", "Border"), - # Translators: Magnifier tracking mode - maintain relative position. + # Translators: Magnifier tracking mode - preserve the tracked item's screen position within the magnified view (same relative screen location) self.RELATIVE: pgettext("magnifier", "Relative"), } @@ -172,10 +172,10 @@ class Filter(DisplayStringStrEnum): @property def _displayStringLabels(self) -> dict["Filter", str]: return { - # Translators: Magnifier color filter - no filter applied. + # Translators: Magnifier color filter - no filter applied self.NORMAL: pgettext("magnifier", "Normal"), - # Translators: Magnifier color filter - grayscale/black and white. + # Translators: Magnifier color filter - grayscale/black and white self.GRAYSCALE: pgettext("magnifier", "Grayscale"), - # Translators: Magnifier color filter - inverted colors. + # Translators: Magnifier color filter - inverted colors self.INVERTED: pgettext("magnifier", "Inverted"), } diff --git a/source/config/configSpec.py b/source/config/configSpec.py index e65a1679661..a8d05d34bbb 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -126,7 +126,7 @@ followReviewCursor = boolean(default=True) followNavigatorObject = boolean(default=True) panStep = integer(min=1, max=100, default=10) - fullscreenMode = string(default="center") + fullscreenTrackingMode = string(default="center") # Presentation settings [presentation] diff --git a/source/globalCommands.py b/source/globalCommands.py index 9baba481b64..c74a95ce0cc 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -5215,11 +5215,11 @@ def script_cycleFilters( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleFollowMouse( + def script_toggleTrackingMouse( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierTrackingType.MOUSE) + _magnifier.commands.toggleTracking(MagnifierTrackingType.MOUSE) @script( description=_( @@ -5228,11 +5228,11 @@ def script_toggleFollowMouse( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleFollowSystemFocus( + def script_toggleTrackingSystemFocus( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierTrackingType.SYSTEM_FOCUS) + _magnifier.commands.toggleTracking(MagnifierTrackingType.SYSTEM_FOCUS) @script( description=_( @@ -5241,11 +5241,11 @@ def script_toggleFollowSystemFocus( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleFollowReview( + def script_toggleTrackingReview( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierTrackingType.REVIEW) + _magnifier.commands.toggleTracking(MagnifierTrackingType.REVIEW) @script( description=_( @@ -5254,11 +5254,11 @@ def script_toggleFollowReview( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleFollowNavigatorObject( + def script_toggleTrackingNavigatorObject( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleFollow(MagnifierTrackingType.NAVIGATOR_OBJECT) + _magnifier.commands.toggleTracking(MagnifierTrackingType.NAVIGATOR_OBJECT) @script( description=_( @@ -5267,11 +5267,11 @@ def script_toggleFollowNavigatorObject( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleAllFollow( + def script_toggleAllTracking( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleAllFollow() + _magnifier.commands.toggleAllTracking() @script( description=_( @@ -5284,7 +5284,7 @@ def script_cycleTrackingModes( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.cycleFullscreenMode() + _magnifier.commands.cycleFullscreenTrackingMode() @script( description=_( diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 40c926a1455..1ed65f30b61 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -42,7 +42,7 @@ from _magnifier import getMagnifier from _magnifier.commands import toggleMagnifier import _magnifier.config as magnifierConfig -from _magnifier.utils.types import Filter, FullScreenMode, MagnifierTrackingType +from _magnifier.utils.types import Filter, FullScreenTrackingMode, MagnifierTrackingType from _magnifier.fullscreenMagnifier import FullScreenMagnifier import queueHandler import requests @@ -6047,18 +6047,18 @@ def _applyCurrentSettingsToConfigAndRuntime(self): selectedZoom = self.zoomCtrl.GetValue() selectedPanStep = self.panSpinCtrl.GetValue() selectedFilter = list(Filter)[self.filterList.GetSelection()] - selectedMode = list(FullScreenMode)[self.fullscreenModeList.GetSelection()] + selectedMode = list(FullScreenTrackingMode)[self.trackingModeList.GetSelection()] roundedZoom = magnifierConfig.roundZoomLevel(selectedZoom) magnifierConfig.setZoomLevel(roundedZoom) self.zoomCtrl.SetValue(roundedZoom) magnifierConfig.setPanStep(selectedPanStep) magnifierConfig.setFilter(selectedFilter) - magnifierConfig.setFullscreenMode(selectedMode) + magnifierConfig.setFullscreenTrackingMode(selectedMode) config.conf["magnifier"]["isTrueCentered"] = self.trueCenterTrackingCheckBox.GetValue() for trackingType, checkBox in self._trackingTypeCheckBoxes.items(): - magnifierConfig.setFollowState(trackingType, checkBox.GetValue()) + magnifierConfig.setTrackingState(trackingType, checkBox.GetValue()) magnifier = getMagnifier() if magnifier: @@ -6066,7 +6066,7 @@ def _applyCurrentSettingsToConfigAndRuntime(self): magnifier._panStep = selectedPanStep magnifier.filterType = selectedFilter if isinstance(magnifier, FullScreenMagnifier): - magnifier._fullscreenMode = selectedMode + magnifier._trackingMode = selectedMode def _onImmediateSettingChange(self, evt: wx.CommandEvent): """Handle immediate updates for non-enable magnifier settings.""" @@ -6082,7 +6082,7 @@ def makeSettings( sizer=settingsSizer, ) - # GENERAL GROUP + # General group # Translators: This is the label for a group of general magnifier options in the # magnifier settings panel generalGroupText = _("General") @@ -6105,7 +6105,7 @@ def makeSettings( self.enableMagnifierCheckBox.Bind(wx.EVT_CHECKBOX, self.onEnableMagnifierChange) self.enableMagnifierCheckBox.SetValue(self._magnifierEnabledInitially) - # ZOOM SETTINGS + # Zoom settings # Translators: The label for a setting in magnifier settings to select the zoom level. zoomLabelText = _("&Zoom (%):") @@ -6127,7 +6127,7 @@ def makeSettings( self.zoomCtrl.SetValue(zoomLevel) self.zoomCtrl.Bind(wx.EVT_SPINCTRL, self._onImmediateSettingChange) - # FILTER SETTINGS + # Filter settings # Translators: The label for a setting in magnifier settings to select the filter filterLabelText = _("Color f&ilter:") filterChoices = [f.displayString for f in Filter] @@ -6144,7 +6144,7 @@ def makeSettings( self.filterList.SetSelection(list(Filter).index(filterValue)) self.filterList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) - # TRUE CENTER TRACKING + # True center tracking settings # Translators: The label for a setting in magnifier settings to select whether true center tracking is used trueCenterTrackingText = _("&True center tracking") self.trueCenterTrackingCheckBox = generalGroup.addItem( @@ -6158,7 +6158,7 @@ def makeSettings( self._trueCenterTrackingInitially = self.trueCenterTrackingCheckBox.GetValue() self.trueCenterTrackingCheckBox.Bind(wx.EVT_CHECKBOX, self._onImmediateSettingChange) - # PAN SETTINGS + # Pan settings # Translators: The label for a setting in magnifier settings to select the pan step size (in percentage). panStepSizeLabelText = _("&Panning step size (%):") @@ -6180,7 +6180,7 @@ def makeSettings( self.panSpinCtrl.Bind(wx.EVT_SPINCTRL, self._onImmediateSettingChange) self.panSpinCtrl.Bind(wx.EVT_TEXT, self._onImmediateSettingChange) - # TRACKING GROUP + # Tracking group # Translators: This is the label for a group of tracking options in the magnifier settings panel trackingGroupText = _("Tracking") trackingGroupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=trackingGroupText) @@ -6206,13 +6206,13 @@ def makeSettings( for trackingType, (label, helpId) in _trackingTypeLabels.items(): checkBox = trackingGroup.addItem(wx.CheckBox(trackingGroupBox, label=label)) self.bindHelpEvent(helpId, checkBox) - followState = magnifierConfig.getFollowState(trackingType) + followState = magnifierConfig.getTrackingState(trackingType) self._trackingTypeInitially[trackingType] = followState checkBox.SetValue(followState) checkBox.Bind(wx.EVT_CHECKBOX, self._onImmediateSettingChange) self._trackingTypeCheckBoxes[trackingType] = checkBox - # FULLSCREEN GROUP + # Fullscreen group # Translators: This is the label for a group of fullscreen magnifier options in the # magnifier settings panel fullscreenGroupText = _("Fullscreen") @@ -6221,25 +6221,27 @@ def makeSettings( fullscreenGroup = guiHelper.BoxSizerHelper(fullscreenGroupBox, sizer=self.fullscreenGroupSizer) sHelper.addItem(fullscreenGroup) - # FULLSCREEN MODE SETTINGS + # Fullscreen mode settings # Translators: The label for a setting in magnifier settings to select the full-screen mode - fullscreenModeLabelText = _("Tracking &mode:") - fullscreenModeChoices = [mode.displayString for mode in FullScreenMode] if FullScreenMode else [] - self.fullscreenModeList = fullscreenGroup.addLabeledControl( - fullscreenModeLabelText, + FullscreenTrackingModeLabelText = _("Tracking &mode:") + FullscreenTrackingModeChoices = ( + [mode.displayString for mode in FullScreenTrackingMode] if FullScreenTrackingMode else [] + ) + self.trackingModeList = fullscreenGroup.addLabeledControl( + FullscreenTrackingModeLabelText, wx.Choice, - choices=fullscreenModeChoices, + choices=FullscreenTrackingModeChoices, ) self.bindHelpEvent( "MagnifierTrackingMode", - self.fullscreenModeList, + self.trackingModeList, ) # Set value from config - fullscreenMode = magnifierConfig.getFullscreenMode() - self._fullscreenModeInitially = fullscreenMode - self.fullscreenModeList.SetSelection(list(FullScreenMode).index(fullscreenMode)) - self.fullscreenModeList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) + trackingMode = magnifierConfig.getFullscreenTrackingMode() + self._trackingModeInitially = trackingMode + self.trackingModeList.SetSelection(list(FullScreenTrackingMode).index(trackingMode)) + self.trackingModeList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) def onSave(self): """Save the current selections to config.""" @@ -6250,14 +6252,14 @@ def onSave(self): selectedZoom = self.zoomCtrl.GetValue() selectedPanStep = self.panSpinCtrl.GetValue() selectedFilter = list(Filter)[self.filterList.GetSelection()] - selectedMode = list(FullScreenMode)[self.fullscreenModeList.GetSelection()] + selectedMode = list(FullScreenTrackingMode)[self.trackingModeList.GetSelection()] isTrueCentered = self.trueCenterTrackingCheckBox.GetValue() roundedZoom = magnifierConfig.roundZoomLevel(selectedZoom) self._zoomInitially = roundedZoom self._panStepInitially = selectedPanStep self._filterInitially = selectedFilter - self._fullscreenModeInitially = selectedMode + self._FullscreenTrackingModeInitially = selectedMode self._trueCenterTrackingInitially = isTrueCentered for trackingType, checkBox in self._trackingTypeCheckBoxes.items(): shouldFollow = checkBox.GetValue() @@ -6268,10 +6270,10 @@ def onDiscard(self): magnifierConfig.setZoomLevel(self._zoomInitially) magnifierConfig.setPanStep(self._panStepInitially) magnifierConfig.setFilter(self._filterInitially) - magnifierConfig.setFullscreenMode(self._fullscreenModeInitially) + magnifierConfig.setFullscreenTrackingMode(self._FullscreenTrackingModeInitially) config.conf["magnifier"]["isTrueCentered"] = self._trueCenterTrackingInitially for trackingType, state in self._trackingTypeInitially.items(): - magnifierConfig.setFollowState(trackingType, state) + magnifierConfig.setTrackingState(trackingType, state) magnifier = getMagnifier() if magnifier: @@ -6279,7 +6281,7 @@ def onDiscard(self): magnifier._panStep = self._panStepInitially magnifier.filterType = self._filterInitially if isinstance(magnifier, FullScreenMagnifier): - magnifier._fullscreenMode = self._fullscreenModeInitially + magnifier._trackingMode = self._FullscreenTrackingModeInitially if self._magnifierEnabledInitially != magnifierConfig.getEnabled(): toggleMagnifier() diff --git a/tests/unit/test_magnifier/test_focusManager.py b/tests/unit/test_magnifier/test_focusManager.py index 0ea9226b9d5..c4a47bffc9e 100644 --- a/tests/unit/test_magnifier/test_focusManager.py +++ b/tests/unit/test_magnifier/test_focusManager.py @@ -4,7 +4,7 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt from dataclasses import dataclass -from _magnifier.utils.focusManager import FocusManager +from _magnifier.utils.trackingManager import TrackingManager from _magnifier.utils.types import Coordinates, MagnifierTrackingType import unittest from unittest.mock import MagicMock, Mock, patch @@ -17,7 +17,7 @@ def _makeFollowStateSideEffect( followReview: bool = True, followNavigatorObject: bool = True, ): - """Return a side_effect function for patching getFollowState.""" + """Return a side_effect function for patching getTrackingState.""" states = { MagnifierTrackingType.MOUSE: followMouse, MagnifierTrackingType.SYSTEM_FOCUS: followSystemFocus, @@ -46,76 +46,76 @@ class FocusTestParam: followNavigatorObject: bool = True -class TestFocusManager(unittest.TestCase): - """Tests for the FocusManager class.""" +class TestTrackingManager(unittest.TestCase): + """Tests for the TrackingManager class.""" def setUp(self): """Setup before each test.""" - self.focusManager = FocusManager() - - def testFocusManagerCreation(self): - """Can we create a FocusManager with initialized values?""" - self.assertIsNone(self.focusManager._lastFocusedObject) - self.assertEqual(self.focusManager._lastReportedCoordinates, Coordinates(0, 0)) - self.assertIsNone(self.focusManager._lastReviewPosition) - self.assertEqual(self.focusManager._lastMousePosition, Coordinates(0, 0)) - self.assertEqual(self.focusManager._lastSystemFocusPosition, Coordinates(0, 0)) - self.assertEqual(self.focusManager._lastNavigatorObjectPosition, Coordinates(0, 0)) - self.assertEqual(self.focusManager._lastValidSystemFocusPosition, Coordinates(0, 0)) - self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(0, 0)) - self.assertEqual(self.focusManager._lastValidNavigatorObjectPosition, Coordinates(0, 0)) + self.trackingManager = TrackingManager() + + def testTrackingManagerCreation(self): + """Can we create a TrackingManager with initialized values?""" + self.assertIsNone(self.trackingManager._lastTrackedObject) + self.assertEqual(self.trackingManager._lastReportedCoordinates, Coordinates(0, 0)) + self.assertIsNone(self.trackingManager._lastReviewPosition) + self.assertEqual(self.trackingManager._lastMousePosition, Coordinates(0, 0)) + self.assertEqual(self.trackingManager._lastSystemFocusPosition, Coordinates(0, 0)) + self.assertEqual(self.trackingManager._lastNavigatorObjectPosition, Coordinates(0, 0)) + self.assertEqual(self.trackingManager._lastValidSystemFocusPosition, Coordinates(0, 0)) + self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(0, 0)) + self.assertEqual(self.trackingManager._lastValidNavigatorObjectPosition, Coordinates(0, 0)) def testGetNavigatorObjectPosition(self): """Getting navigator object position with different API responses.""" # Case 1: Navigator object location available - with patch("_magnifier.utils.focusManager.api.getNavigatorObject") as mock_navigator: + with patch("_magnifier.utils.trackingManager.api.getNavigatorObject") as mock_navigator: mock_navigator.return_value.location = (100, 150, 200, 300) - coords = self.focusManager._getNavigatorObjectPosition() + coords = self.trackingManager._getNavigatorObjectPosition() # Center: (100 + 200//2, 150 + 300//2) = (200, 300) self.assertEqual(coords, Coordinates(200, 300)) # Case 2: Navigator object fails - should return last valid position from Case 1 - with patch("_magnifier.utils.focusManager.api.getNavigatorObject") as mock_navigator: + with patch("_magnifier.utils.trackingManager.api.getNavigatorObject") as mock_navigator: mock_navigator.return_value.location = Mock(side_effect=Exception()) - coords = self.focusManager._getNavigatorObjectPosition() + coords = self.trackingManager._getNavigatorObjectPosition() # Should return last valid position (200, 300) self.assertEqual(coords, Coordinates(200, 300)) # Case 3: Navigator object is None - should return last valid position - with patch("_magnifier.utils.focusManager.api.getNavigatorObject", return_value=None): - coords = self.focusManager._getNavigatorObjectPosition() + with patch("_magnifier.utils.trackingManager.api.getNavigatorObject", return_value=None): + coords = self.trackingManager._getNavigatorObjectPosition() self.assertEqual(coords, Coordinates(200, 300)) def testGetReviewPosition(self): """Getting review cursor position with different API responses.""" # Case 1: Review position available - with patch("_magnifier.utils.focusManager.api.getReviewPosition") as mock_review: + with patch("_magnifier.utils.trackingManager.api.getReviewPosition") as mock_review: mock_point = Mock() mock_point.x = 300 mock_point.y = 400 mock_review.return_value.pointAtStart = mock_point - coords = self.focusManager._getReviewPosition() + coords = self.trackingManager._getReviewPosition() self.assertEqual(coords, Coordinates(300, 400)) # _lastValidReviewPosition must be updated - self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(300, 400)) + self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(300, 400)) # Case 2: pointAtStart raises NotImplementedError → returns None - with patch("_magnifier.utils.focusManager.api.getReviewPosition") as mock_review: + with patch("_magnifier.utils.trackingManager.api.getReviewPosition") as mock_review: type(mock_review.return_value).pointAtStart = property( fget=Mock(side_effect=NotImplementedError), ) - coords = self.focusManager._getReviewPosition() + coords = self.trackingManager._getReviewPosition() self.assertIsNone(coords) # _lastValidReviewPosition must NOT change - self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(300, 400)) + self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(300, 400)) # Case 3: getReviewPosition returns None → returns None - with patch("_magnifier.utils.focusManager.api.getReviewPosition", return_value=None): - coords = self.focusManager._getReviewPosition() + with patch("_magnifier.utils.trackingManager.api.getReviewPosition", return_value=None): + coords = self.trackingManager._getReviewPosition() self.assertIsNone(coords) def testGetReviewPositionSurvivesCOMError(self): @@ -125,54 +125,54 @@ def testGetReviewPositionSurvivesCOMError(self): mockReviewPos = Mock() type(mockReviewPos).pointAtStart = property(lambda self: (_ for _ in ()).throw(comError)) - with patch("_magnifier.utils.focusManager.api.getReviewPosition", return_value=mockReviewPos): - result = self.focusManager._getReviewPosition() + with patch("_magnifier.utils.trackingManager.api.getReviewPosition", return_value=mockReviewPos): + result = self.trackingManager._getReviewPosition() self.assertIsNone(result) def testGetSystemFocusPosition(self): """Getting system focus position with different API responses.""" # Case 1: Caret position successful (browse mode) - with patch("_magnifier.utils.focusManager.api.getCaretPosition") as mock_caret: + with patch("_magnifier.utils.trackingManager.api.getCaretPosition") as mock_caret: mock_point = Mock() mock_point.x = 500 mock_point.y = 600 mock_caret.return_value.pointAtStart = mock_point - coords = self.focusManager._getSystemFocusPosition() + coords = self.trackingManager._getSystemFocusPosition() self.assertEqual(coords, Coordinates(500, 600)) # Case 2: Caret fails, focus object works - with patch("_magnifier.utils.focusManager.api.getCaretPosition", side_effect=RuntimeError): - with patch("_magnifier.utils.focusManager.api.getFocusObject") as mock_focus: + with patch("_magnifier.utils.trackingManager.api.getCaretPosition", side_effect=RuntimeError): + with patch("_magnifier.utils.trackingManager.api.getFocusObject") as mock_focus: mock_focus.return_value.location = (200, 300, 100, 80) - coords = self.focusManager._getSystemFocusPosition() + coords = self.trackingManager._getSystemFocusPosition() # Center: (200 + 100//2, 300 + 80//2) = (250, 340) self.assertEqual(coords, Coordinates(250, 340)) # Case 3: Everything fails - should return last valid position from Case 2 - with patch("_magnifier.utils.focusManager.api.getCaretPosition", side_effect=RuntimeError): - with patch("_magnifier.utils.focusManager.api.getFocusObject", return_value=None): - coords = self.focusManager._getSystemFocusPosition() + with patch("_magnifier.utils.trackingManager.api.getCaretPosition", side_effect=RuntimeError): + with patch("_magnifier.utils.trackingManager.api.getFocusObject", return_value=None): + coords = self.trackingManager._getSystemFocusPosition() # Should return last valid position (250, 340) self.assertEqual(coords, Coordinates(250, 340)) def testGetSystemFocusPositionSurvivesOSError(self): """OSError from magnification API calls must be caught.""" with patch( - "_magnifier.utils.focusManager.api.getCaretPosition", + "_magnifier.utils.trackingManager.api.getCaretPosition", side_effect=OSError("WinError"), ): - with patch("_magnifier.utils.focusManager.api.getFocusObject") as mock_focus: + with patch("_magnifier.utils.trackingManager.api.getFocusObject") as mock_focus: mock_focus.return_value.location = (10, 20, 30, 40) - coords = self.focusManager._getSystemFocusPosition() + coords = self.trackingManager._getSystemFocusPosition() self.assertEqual(coords, Coordinates(25, 40)) def testGetMousePosition(self): """Getting mouse position.""" - with patch("_magnifier.utils.focusManager.winUser.getCursorPos", return_value=(123, 456)): - coords = self.focusManager._getMousePosition() + with patch("_magnifier.utils.trackingManager.winUser.getCursorPos", return_value=(123, 456)): + coords = self.trackingManager._getMousePosition() self.assertEqual(coords, Coordinates(123, 456)) def testGetCurrentFocusCoordinates(self): @@ -308,19 +308,19 @@ def testGetCurrentFocusCoordinates(self): for param in subTestParams: with self.subTest(description=param.description): # Reset focus manager state - self.focusManager._lastNavigatorObjectPosition = Coordinates(0, 0) - self.focusManager._lastSystemFocusPosition = Coordinates(0, 0) - self.focusManager._lastMousePosition = Coordinates(0, 0) - self.focusManager._lastReviewPosition = None - self.focusManager._lastSystemFocusChangeTime = 0.0 - self.focusManager._lastFocusedObject = param.lastFocusedObject + self.trackingManager._lastNavigatorObjectPosition = Coordinates(0, 0) + self.trackingManager._lastSystemFocusPosition = Coordinates(0, 0) + self.trackingManager._lastMousePosition = Coordinates(0, 0) + self.trackingManager._lastReviewPosition = None + self.trackingManager._lastSystemFocusChangeTime = 0.0 + self.trackingManager._lastTrackedObject = param.lastFocusedObject # Mock instance methods - self.focusManager._getNavigatorObjectPosition = MagicMock( + self.trackingManager._getNavigatorObjectPosition = MagicMock( return_value=param.navigatorObjectPos, ) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=param.systemFocusPos) - self.focusManager._getReviewPosition = MagicMock(return_value=param.reviewPos) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=param.systemFocusPos) + self.trackingManager._getReviewPosition = MagicMock(return_value=param.reviewPos) followStateSideEffect = _makeFollowStateSideEffect( followMouse=param.followMouse, @@ -331,50 +331,50 @@ def testGetCurrentFocusCoordinates(self): with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), patch( - "_magnifier.utils.focusManager.winUser.getAsyncKeyState", + "_magnifier.utils.trackingManager.winUser.getAsyncKeyState", side_effect=lambda _key: -1 if param.leftPressed else 0, ), patch( - "_magnifier.utils.focusManager.winUser.getCursorPos", + "_magnifier.utils.trackingManager.winUser.getCursorPos", return_value=param.mousePos, ), ): # Execute - focusCoordinates = self.focusManager.getCurrentFocusCoordinates() + focusCoordinates = self.trackingManager.getCurrentTrackedCoordinates() # Assert self.assertEqual(focusCoordinates, param.expectedCoords) - self.assertEqual(self.focusManager.getLastFocusType(), param.expectedFocus) + self.assertEqual(self.trackingManager.getLastTrackedType(), param.expectedFocus) def testGetLastFocusType(self): """Test getting the last focus type.""" - self.assertIsNone(self.focusManager.getLastFocusType()) + self.assertIsNone(self.trackingManager.getLastTrackedType()) for focusType in MagnifierTrackingType: - self.focusManager._lastFocusedObject = focusType - self.assertEqual(self.focusManager.getLastFocusType(), focusType) + self.trackingManager._lastTrackedObject = focusType + self.assertEqual(self.trackingManager.getLastTrackedType(), focusType) class TestFollowSettings(unittest.TestCase): """Verify that each follow* setting actually gates its source.""" def setUp(self): - self.focusManager = FocusManager() - self.focusManager._lastMousePosition = Coordinates(0, 0) - self.focusManager._lastSystemFocusPosition = Coordinates(0, 0) - self.focusManager._lastReviewPosition = None - self.focusManager._lastNavigatorObjectPosition = Coordinates(0, 0) + self.trackingManager = TrackingManager() + self.trackingManager._lastMousePosition = Coordinates(0, 0) + self.trackingManager._lastSystemFocusPosition = Coordinates(0, 0) + self.trackingManager._lastReviewPosition = None + self.trackingManager._lastNavigatorObjectPosition = Coordinates(0, 0) def _run(self, *, followMouse, followSystemFocus, followReview, followNavigatorObject): - """Run getCurrentFocusCoordinates with all sources moved and the given settings.""" - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + """Run getCurrentTrackedCoordinates with all sources moved and the given settings.""" + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=followMouse, @@ -385,12 +385,12 @@ def _run(self, *, followMouse, followSystemFocus, followReview, followNavigatorO with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - return self.focusManager.getCurrentFocusCoordinates() + return self.trackingManager.getCurrentTrackedCoordinates() def testFollowMouseDisabled(self): """When followMouse=False, mouse changes are ignored and system focus wins.""" @@ -401,7 +401,7 @@ def testFollowMouseDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(20, 20)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.SYSTEM_FOCUS) + self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.SYSTEM_FOCUS) def testFollowSystemFocusDisabled(self): """When followSystemFocus=False, system focus changes are ignored and review wins.""" @@ -412,7 +412,7 @@ def testFollowSystemFocusDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(30, 30)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.REVIEW) + self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.REVIEW) def testFollowReviewDisabled(self): """When followReview=False, review changes are ignored and navigator wins.""" @@ -423,7 +423,7 @@ def testFollowReviewDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(40, 40)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.NAVIGATOR_OBJECT) + self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.NAVIGATOR_OBJECT) def testAllFollowDisabled(self): """When all settings are False, no source fires and focus remains frozen.""" @@ -438,10 +438,10 @@ def testAllFollowDisabled(self): def testAllFollowDisabledKeepsLastTrackedPosition(self): """Disabling all follow modes keeps the most recently tracked coordinates.""" - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followEnabledSideEffect = _makeFollowStateSideEffect( followMouse=True, @@ -451,19 +451,19 @@ def testAllFollowDisabledKeepsLastTrackedPosition(self): ) with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followEnabledSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(10, 10)) # Move all sources, but disable every follow setting. - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(99, 99)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(88, 88)) - self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(77, 77)) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(66, 66)) + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(99, 99)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(88, 88)) + self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(77, 77)) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(66, 66)) followDisabledSideEffect = _makeFollowStateSideEffect( followMouse=False, followSystemFocus=False, @@ -472,22 +472,22 @@ def testAllFollowDisabledKeepsLastTrackedPosition(self): ) with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followDisabledSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.focusManager.getLastFocusType()) + self.assertIsNone(self.trackingManager.getLastTrackedType()) def testFollowMouseDragIgnoresSettings(self): """Mouse drag (left click held) with followMouse=True always wins regardless of others.""" - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=True, @@ -498,30 +498,30 @@ def testFollowMouseDragIgnoresSettings(self): with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=-1), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=-1), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.MOUSE) + self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.MOUSE) def testDisableFollowMouseKeepsViewFrozen(self): """When followMouse is disabled, view remains frozen until a followed source changes.""" # Simulate: mouse was previously the active focus source - self.focusManager._lastFocusedObject = MagnifierTrackingType.MOUSE - self.focusManager._lastReportedCoordinates = Coordinates(10, 10) + self.trackingManager._lastTrackedObject = MagnifierTrackingType.MOUSE + self.trackingManager._lastReportedCoordinates = Coordinates(10, 10) # Positions haven't changed from last recorded values (no "change" detected) - self.focusManager._lastMousePosition = Coordinates(10, 10) - self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) - self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) + self.trackingManager._lastMousePosition = Coordinates(10, 10) + self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) + self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=None) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=None) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -532,29 +532,29 @@ def testDisableFollowMouseKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.focusManager.getLastFocusType()) + self.assertIsNone(self.trackingManager.getLastTrackedType()) def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): """When followMouse is disabled, mouse movement alone does not move the view.""" - self.focusManager._lastFocusedObject = MagnifierTrackingType.MOUSE - self.focusManager._lastReportedCoordinates = Coordinates(10, 10) - self.focusManager._lastMousePosition = Coordinates(10, 10) - self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) - self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) + self.trackingManager._lastTrackedObject = MagnifierTrackingType.MOUSE + self.trackingManager._lastReportedCoordinates = Coordinates(10, 10) + self.trackingManager._lastMousePosition = Coordinates(10, 10) + self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) + self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) # Mouse has moved but followMouse is False - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(15, 15)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=None) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(15, 15)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=None) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -565,29 +565,29 @@ def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.focusManager.getLastFocusType()) + self.assertIsNone(self.trackingManager.getLastTrackedType()) def testDisableFollowSystemFocusKeepsViewFrozen(self): """When followSystemFocus is disabled, view remains frozen until a followed source changes.""" - self.focusManager._lastFocusedObject = MagnifierTrackingType.SYSTEM_FOCUS - self.focusManager._lastReportedCoordinates = Coordinates(20, 20) - self.focusManager._lastMousePosition = Coordinates(10, 10) - self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) - self.focusManager._lastReviewPosition = Coordinates(30, 30) - self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) - - self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.trackingManager._lastTrackedObject = MagnifierTrackingType.SYSTEM_FOCUS + self.trackingManager._lastReportedCoordinates = Coordinates(20, 20) + self.trackingManager._lastMousePosition = Coordinates(10, 10) + self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) + self.trackingManager._lastReviewPosition = Coordinates(30, 30) + self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) + + self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -598,12 +598,12 @@ def testDisableFollowSystemFocusKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.focusManager.getFollowState", + "_magnifier.utils.trackingManager.getTrackingState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.focusManager.getCurrentFocusCoordinates() + coords = self.trackingManager.getCurrentTrackedCoordinates() self.assertEqual(coords, Coordinates(20, 20)) - self.assertIsNone(self.focusManager.getLastFocusType()) + self.assertIsNone(self.trackingManager.getLastTrackedType()) diff --git a/tests/unit/test_magnifier/test_fullscreenMagnifier.py b/tests/unit/test_magnifier/test_fullscreenMagnifier.py index 9f7f348aaa9..11a853ac2db 100644 --- a/tests/unit/test_magnifier/test_fullscreenMagnifier.py +++ b/tests/unit/test_magnifier/test_fullscreenMagnifier.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch from _magnifier.config import ZoomLevel from _magnifier.magnifier import Magnifier -from _magnifier.utils.types import Filter, FullScreenMode, MagnifiedView, Direction, Coordinates +from _magnifier.utils.types import Filter, FullScreenTrackingMode, MagnifiedView, Direction, Coordinates from _magnifier.fullscreenMagnifier import FullScreenMagnifier from tests.unit.test_magnifier.test_magnifier import _TestMagnifier @@ -21,7 +21,7 @@ def testMagnifierCreation(self): self.assertEqual(magnifier.zoomLevel, 200) self.assertEqual(magnifier.filterType, Filter.NORMAL) - self.assertEqual(magnifier._fullscreenMode, FullScreenMode.CENTER) + self.assertEqual(magnifier._trackingMode, FullScreenTrackingMode.CENTER) self.assertEqual(magnifier._MAGNIFIED_VIEW, MagnifiedView.FULLSCREEN) self.assertTrue(magnifier._isActive) @@ -171,7 +171,7 @@ def testMagnifierInheritance(self): self.assertTrue(hasattr(magnifier, "zoomLevel")) self.assertTrue(hasattr(magnifier, "filterType")) self.assertTrue(hasattr(magnifier, "_MAGNIFIED_VIEW")) - self.assertTrue(hasattr(magnifier, "_fullscreenMode")) + self.assertTrue(hasattr(magnifier, "_trackingMode")) self.assertTrue(hasattr(magnifier, "_isActive")) self.assertTrue(hasattr(magnifier, "_currentCoordinates")) @@ -213,8 +213,8 @@ def testMagnifierSimpleLifecycle(self): self.assertEqual(magnifier._currentCoordinates, (200, 300)) # Change mode - magnifier._fullscreenMode = FullScreenMode.RELATIVE - self.assertEqual(magnifier._fullscreenMode, FullScreenMode.RELATIVE) + magnifier._trackingMode = FullScreenTrackingMode.RELATIVE + self.assertEqual(magnifier._trackingMode, FullScreenTrackingMode.RELATIVE) # Change filter magnifier.filterType = Filter.INVERTED @@ -227,7 +227,7 @@ def testMagnifierSimpleLifecycle(self): def testAttemptRecoverySuccess(self): """FullScreenMagnifier._attemptRecovery reinitialises API and restarts timer on success.""" with patch( - "_magnifier.magnifier.FocusManager.getCurrentFocusCoordinates", + "_magnifier.magnifier.TrackingManager.getCurrentTrackedCoordinates", return_value=Coordinates(0, 0), ): magnifier = FullScreenMagnifier() @@ -267,7 +267,7 @@ def testUpdateLoopSurvivesSingleDoUpdateError(self): magnifier = FullScreenMagnifier() magnifier._startMagnifier() magnifier._startTimer = MagicMock() - magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=(100, 200), ) diff --git a/tests/unit/test_magnifier/test_magnifier.py b/tests/unit/test_magnifier/test_magnifier.py index 7a26dd883c3..3d00134cb61 100644 --- a/tests/unit/test_magnifier/test_magnifier.py +++ b/tests/unit/test_magnifier/test_magnifier.py @@ -69,7 +69,7 @@ def testMagnifierCreation(self): self.assertEqual(self.magnifier.zoomLevel, 200) self.assertEqual(self.magnifier._filterType, Filter.NORMAL) self.assertFalse(self.magnifier._isActive) - self.assertIsNotNone(self.magnifier._focusManager) + self.assertIsNotNone(self.magnifier._trackingManager) self.assertEqual(self.magnifier._consecutiveErrors, 0) def testZoomLevelProperty(self): @@ -99,7 +99,7 @@ def testStartMagnifier(self): """Activating the magnifier.""" # Use center coordinates which will always be within bounds focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) @@ -109,20 +109,20 @@ def testStartMagnifier(self): self.assertTrue(self.magnifier._isActive) self.assertEqual(self.magnifier.currentCoordinates, focusCoords) - self.magnifier._focusManager.getCurrentFocusCoordinates.assert_called_once() + self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_called_once() - # Test starting when already active (should not call getCurrentFocusCoordinates again) - self.magnifier._focusManager.getCurrentFocusCoordinates.reset_mock() + # Test starting when already active (should not call getCurrentTrackedCoordinates again) + self.magnifier._trackingManager.getCurrentTrackedCoordinates.reset_mock() self.magnifier._startMagnifier() self.assertTrue(self.magnifier._isActive) - self.magnifier._focusManager.getCurrentFocusCoordinates.assert_not_called() + self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_not_called() def testUpdateMagnifier(self): """Updating the magnifier's properties.""" # Use center coordinates which will always be within bounds focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock() @@ -130,7 +130,7 @@ def testUpdateMagnifier(self): # Call the update function without activation self.magnifier._updateMagnifier() - self.magnifier._focusManager.getCurrentFocusCoordinates.assert_not_called() + self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_not_called() self.magnifier._doUpdate.assert_not_called() self.magnifier._startTimer.assert_not_called() @@ -138,9 +138,9 @@ def testUpdateMagnifier(self): self.magnifier._isActive = True self.magnifier._updateMagnifier() - # getCurrentFocusCoordinates is called twice: once in _managePanning and once to update currentCoordinates + # getCurrentTrackedCoordinates is called twice: once in _managePanning and once to update currentCoordinates self.assertEqual( - self.magnifier._focusManager.getCurrentFocusCoordinates.call_count, + self.magnifier._trackingManager.getCurrentTrackedCoordinates.call_count, 2, ) self.magnifier._doUpdate.assert_called_once() @@ -155,7 +155,7 @@ def testUpdateMagnifierResumesAfterSingleError(self): """Timer must always be rescheduled even when _doUpdate raises an exception.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("COM failure")) @@ -173,7 +173,7 @@ def testUpdateMagnifierTriggersRecoveryAfterMaxErrors(self): """After _MAX_CONSECUTIVE_ERRORS failures, _attemptRecovery is called instead of restarting timer.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("COM failure")) @@ -192,7 +192,7 @@ def testUpdateMagnifierCatchesCOMError(self): """COMError from UIA must be caught and the timer rescheduled.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=COMError(-2147417848, "RPC_E_DISCONNECTED", None)) @@ -207,7 +207,7 @@ def testUpdateMagnifierRecoveryFailureSafelyRestartsTimer(self): """If _attemptRecovery itself raises, the timer must still be restarted to prevent a freeze.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("API failure")) @@ -226,7 +226,7 @@ def testUpdateMagnifierResetsErrorCountOnSuccess(self): self.magnifier._isActive = True self.magnifier._consecutiveErrors = 2 focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock() # Success @@ -453,7 +453,7 @@ def testManagePanning(self): focusA = Coordinates(100, 200) focusB = Coordinates(300, 400) - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusA) + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock(return_value=focusA) # When not panning, _lastFocusCoordinates is updated every cycle self.magnifier._isManualPanning = False @@ -468,7 +468,7 @@ def testManagePanning(self): self.assertEqual(self.magnifier._lastFocusCoordinates, focusA) # When focus changes while panning, manual panning ends - self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusB) + self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock(return_value=focusB) self.magnifier._managePanning() self.assertFalse(self.magnifier._isManualPanning) self.assertEqual(self.magnifier._lastFocusCoordinates, focusB) diff --git a/tests/unit/test_magnifier/test_spotlightManager.py b/tests/unit/test_magnifier/test_spotlightManager.py index e62fe60f205..77e02697d7b 100644 --- a/tests/unit/test_magnifier/test_spotlightManager.py +++ b/tests/unit/test_magnifier/test_spotlightManager.py @@ -4,7 +4,7 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt from unittest.mock import MagicMock, patch -from _magnifier.utils.types import FullScreenMode, Coordinates +from _magnifier.utils.types import FullScreenTrackingMode, Coordinates from _magnifier.fullscreenMagnifier import FullScreenMagnifier from tests.unit.test_magnifier.test_magnifier import _TestMagnifier @@ -31,7 +31,9 @@ def testSpotlightActivation(self): spotlightManager = magnifier._spotlightManager # Mock required methods - magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) + magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + return_value=Coordinates(500, 400), + ) magnifier._getCoordinatesForMode = MagicMock(return_value=Coordinates(500, 400)) spotlightManager._animateZoom = MagicMock() @@ -176,8 +178,10 @@ def testZoomBack(self): # Set original zoom level spotlightManager._originalZoomLevel = 3.0 - # Mock getCurrentFocusCoordinates to return expected position - magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) + # Mock getCurrentTrackedCoordinates to return expected position + magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + return_value=Coordinates(500, 400), + ) spotlightManager._animateZoom = MagicMock() # Trigger zoom back @@ -194,7 +198,7 @@ def testZoomBack(self): def testZoomBackRelativeMode(self): """Test zoom back in RELATIVE mode.""" magnifier = FullScreenMagnifier() - magnifier._fullscreenMode = FullScreenMode.RELATIVE + magnifier._trackingMode = FullScreenTrackingMode.RELATIVE spotlightManager = magnifier._spotlightManager # Set original zoom level @@ -210,7 +214,7 @@ def testZoomBackRelativeMode(self): spotlightManager.zoomBack() # Should use _getCoordinatesForMode for RELATIVE mode - # Note: The code has a bug checking magnifier.FullScreenMode instead of magnifier._fullscreenMode + # Note: The code has a bug checking magnifier.FullScreenTrackingMode instead of magnifier._trackingMode # But we test the current behavior spotlightManager._animateZoom.assert_called_once() @@ -226,7 +230,9 @@ def testSpotlightFullLifecycle(self): self.assertEqual(spotlightManager._originalZoomLevel, 0.0) # Mock methods for full test - magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) + magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + return_value=Coordinates(500, 400), + ) magnifier._getCoordinatesForMode = MagicMock(return_value=Coordinates(500, 400)) magnifier._stopSpotlight = MagicMock() diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index 60a435cc46e..42b4fd25803 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -1625,8 +1625,8 @@ To cycle through the available filters press `NVDA+shift+i`. The magnifier offers three different modes for tracking mouse, focus, review cursor and navigatore object, and determining which part of the screen to magnify: * Center: The magnified area is centered on the currently tracked position. -This mode keeps the tracked element at the center of the screen and clamps to the screen edge. -To disable clamping, activate [true center tracking in the Magnifier settings](#MagnifierUseTrueCenterTracking). +This mode keeps the tracked element at the center of the screen when possible. +When the tracked element is next to a screen edge, the tracked element may or may not remain centered on the screen, depending on [true center tracking in the Magnifier settings](#MagnifierUseTrueCenterTracking). * Border: The magnified area only moves when the tracked position approaches the edge of the visible area. This mode provides a more stable view, only adjusting when necessary. * Relative: The magnified area maintains the relative position of the tracked element within the screen. @@ -2937,7 +2937,6 @@ Higher percentages cause larger movements, making it faster to navigate across t The actual pixel distance will automatically adjust based on your current zoom level. Note: Pan commands allow you to manually move the magnified view in any direction, independent of the tracking mode. -Available pan actions include: | . {.hideHeaderRow} |.| |---|---| From 1c79e8590fdc89692c03aeb9ad84ed8b521fe49e Mon Sep 17 00:00:00 2001 From: Antoine HAFFREINGUE Date: Wed, 17 Jun 2026 13:13:34 +0200 Subject: [PATCH 8/8] revert changes for simplification --- source/_magnifier/commands.py | 52 ++-- source/_magnifier/config.py | 22 +- source/_magnifier/fullscreenMagnifier.py | 18 +- source/_magnifier/magnifier.py | 10 +- .../{trackingManager.py => focusManager.py} | 52 ++-- source/_magnifier/utils/spotlightManager.py | 16 +- source/_magnifier/utils/types.py | 76 ++--- source/config/configSpec.py | 2 +- source/globalCommands.py | 22 +- source/gui/settingsDialogs.py | 60 ++-- .../unit/test_magnifier/test_focusManager.py | 286 +++++++++--------- .../test_fullscreenMagnifier.py | 14 +- tests/unit/test_magnifier/test_magnifier.py | 34 +-- .../test_magnifier/test_spotlightManager.py | 20 +- 14 files changed, 337 insertions(+), 347 deletions(-) rename source/_magnifier/utils/{trackingManager.py => focusManager.py} (87%) diff --git a/source/_magnifier/commands.py b/source/_magnifier/commands.py index 5ee42802ab3..3dd71c98412 100644 --- a/source/_magnifier/commands.py +++ b/source/_magnifier/commands.py @@ -13,11 +13,11 @@ from . import changeMagnifiedView, getMagnifier, start, stop from .config import ( setMagnifiedView, - getTrackingState, + getFollowState, setFilter, - setTrackingState, - setFullscreenTrackingMode, - toggleAllTrackingStates, + setFollowState, + setFullscreenMode, + toggleAllFollowStates, ZoomLevel, ) from .magnifier import Magnifier @@ -26,7 +26,7 @@ Filter, Direction, MagnifiedView, - FullScreenTrackingMode, + FullScreenMode, MagnifierAction, MagnifierTrackingType, ) @@ -199,67 +199,67 @@ def cycleMagnifiedView() -> None: ) -def toggleTracking() -> None: +def toggleFollow(focusType: MagnifierTrackingType) -> None: """ - Toggle the specified tracking mode setting. + Toggle the specified follow mode setting. - :param MagnifierTrackingType: The tracking mode to toggle (mouse, system focus, review cursor, navigator object) + :param MagnifierTrackingType: The follow mode to toggle (mouse, system focus, review cursor, navigator object) """ magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, - MagnifierAction.TOGGLE_TRACKING_SETTINGS, + MagnifierAction.TOGGLE_FOLLOW_SETTINGS, ): - state = not getTrackingState(MagnifierTrackingType) - setTrackingState(MagnifierTrackingType, state) + state = not getFollowState(MagnifierTrackingType) + setFollowState(MagnifierTrackingType, state) ui.message( pgettext( "magnifier", - # Translators: Message announced when toggling a tracking setting with {setting} being the name of the setting and {state} being either "enabled" or "disabled". + # Translators: Message announced when toggling a follow setting with {setting} being the name of the setting and {state} being either "enabled" or "disabled". "{setting} {state}", ).format( setting=MagnifierTrackingType.displayString, state=pgettext( "magnifier", - # Translators: State of the tracking setting being toggled enabled. + # Translators: State of the follow setting being toggled enabled. "enabled", ) if state else pgettext( "magnifier", - # Translators: State of the tracking setting being toggled disabled. + # Translators: State of the follow setting being toggled disabled. "disabled", ), ), ) -def toggleAllTracking() -> None: - """Toggle all tracking settings at once.""" +def toggleAllFollow() -> None: + """Toggle all follow settings at once.""" magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, - MagnifierAction.TOGGLE_TRACKING_SETTINGS, + MagnifierAction.TOGGLE_FOLLOW_SETTINGS, ): - isDisabledNow = toggleAllTrackingStates() + isDisabledNow = toggleAllFollowStates() if isDisabledNow: stateMessage = pgettext( "magnifier", - # Translators: State of all tracking settings being toggled disabled. + # Translators: State of all follow settings being toggled disabled. "All tracking settings disabled", ) else: stateMessage = pgettext( "magnifier", - # Translators: State of all tracking settings being restored. + # Translators: State of all follow settings being restored. "Tracking settings restored", ) ui.message(stateMessage) -def cycleFullscreenTrackingMode() -> None: - """Cycle through full-screen tracking modes (center, border, relative)""" +def cycleFullscreenMode() -> None: + """Cycle through full-screen modes (center, border, relative)""" magnifier: Magnifier = getMagnifier() if magnifierIsActiveVerify( magnifier, @@ -270,13 +270,13 @@ def cycleFullscreenTrackingMode() -> None: MagnifierAction.CHANGE_FULLSCREEN_MODE, ): fullscreenMagnifier: FullScreenMagnifier = magnifier - modes = list(FullScreenTrackingMode) - currentMode = fullscreenMagnifier._trackingMode + modes = list(FullScreenMode) + currentMode = fullscreenMagnifier._fullscreenMode idx = modes.index(currentMode) newMode = modes[(idx + 1) % len(modes)] log.debug(f"Changing full-screen mode from {currentMode} to {newMode}") - fullscreenMagnifier._trackingMode = newMode - setFullscreenTrackingMode(newMode) + fullscreenMagnifier._fullscreenMode = newMode + setFullscreenMode(newMode) ui.message( pgettext( "magnifier", diff --git a/source/_magnifier/config.py b/source/_magnifier/config.py index dd8fc4a3551..0bd77cca82a 100644 --- a/source/_magnifier/config.py +++ b/source/_magnifier/config.py @@ -10,7 +10,7 @@ import config from dataclasses import dataclass, field -from .utils.types import Filter, FullScreenTrackingMode, MagnifierTrackingType, MagnifiedView +from .utils.types import Filter, FullScreenMode, MagnifierTrackingType, MagnifiedView def setEnabled(enable: bool) -> None: @@ -179,7 +179,7 @@ def _ensureSavedStatesInitialized() -> None: saveFollowStates() -def getTrackingState(trackingType: MagnifierTrackingType) -> bool: +def getFollowState(trackingType: MagnifierTrackingType) -> bool: """ Get the current follow state for a given tracking type. @@ -189,7 +189,7 @@ def getTrackingState(trackingType: MagnifierTrackingType) -> bool: return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] -def setTrackingState(trackingType: MagnifierTrackingType, state: bool) -> None: +def setFollowState(trackingType: MagnifierTrackingType, state: bool) -> None: """ Set the follow state for a given tracking type. @@ -202,10 +202,10 @@ def setTrackingState(trackingType: MagnifierTrackingType, state: bool) -> None: def saveFollowStates() -> None: """Save current follow states so they can be restored later.""" for trackingType in _FOLLOW_CONFIG_KEYS: - _followStateOverride.savedStates[trackingType] = getTrackingState(trackingType) + _followStateOverride.savedStates[trackingType] = getFollowState(trackingType) -def toggleAllTrackingStates() -> bool: +def toggleAllFollowStates() -> bool: """ Toggle all follow states between forced-disabled and previously saved states. @@ -214,12 +214,12 @@ def toggleAllTrackingStates() -> bool: _ensureSavedStatesInitialized() if _followStateOverride.isActive: for trackingType, state in _followStateOverride.savedStates.items(): - setTrackingState(trackingType, state) + setFollowState(trackingType, state) _followStateOverride.isActive = False else: saveFollowStates() for trackingType in _FOLLOW_CONFIG_KEYS: - setTrackingState(trackingType, False) + setFollowState(trackingType, False) _followStateOverride.isActive = True return _followStateOverride.isActive @@ -233,19 +233,19 @@ def isTrueCentered() -> bool: return config.conf["magnifier"]["isTrueCentered"] -def getFullscreenTrackingMode() -> FullScreenTrackingMode: +def getFullscreenMode() -> FullScreenMode: """ Get full-screen mode from config. :return: The full-screen mode. """ - return FullScreenTrackingMode(config.conf["magnifier"]["fullscreenTrackingMode"]) + return FullScreenMode(config.conf["magnifier"]["fullscreenMode"]) -def setFullscreenTrackingMode(mode: FullScreenTrackingMode) -> None: +def setFullscreenMode(mode: FullScreenMode) -> None: """ Set full-screen mode from settings. :param mode: The full-screen mode to set. """ - config.conf["magnifier"]["fullscreenTrackingMode"] = mode.value + config.conf["magnifier"]["fullscreenMode"] = mode.value diff --git a/source/_magnifier/fullscreenMagnifier.py b/source/_magnifier/fullscreenMagnifier.py index e220066f241..95aff7a3b9c 100644 --- a/source/_magnifier/fullscreenMagnifier.py +++ b/source/_magnifier/fullscreenMagnifier.py @@ -19,12 +19,12 @@ from .utils.types import ( Filter, MagnifiedView, - FullScreenTrackingMode, + FullScreenMode, Size, MagnifierParameters, Coordinates, ) -from .config import isTrueCentered, getFullscreenTrackingMode +from .config import getFullscreenMode, isTrueCentered from .utils.errorHandling import trackNativeMagnifierErrors @@ -36,7 +36,7 @@ class FullScreenMagnifier(Magnifier): def __init__(self): super().__init__() - self._trackingMode = getFullscreenTrackingMode() + self._fullscreenMode = getFullscreenMode() self.currentCoordinates = Coordinates(0, 0) self._spotlightManager = SpotlightManager(self) self._displaySize = Size(self._displayOrientation.width, self._displayOrientation.height) @@ -62,7 +62,7 @@ def _startMagnifier(self) -> None: """ super()._startMagnifier() log.debug( - f"Starting magnifier with zoom level {self.zoomLevel} and filter {self.filterType} and full-screen mode {self._trackingMode}", + f"Starting magnifier with zoom level {self.zoomLevel} and filter {self.filterType} and full-screen mode {self._fullscreenMode}", ) try: self._initializeNativeMagnification() @@ -265,12 +265,12 @@ def _getCoordinatesForMode( :return: Adjusted coordinates according to full-screen mode """ - match self._trackingMode: - case FullScreenTrackingMode.RELATIVE: + match self._fullscreenMode: + case FullScreenMode.RELATIVE: return self._relativePos(coordinates) - case FullScreenTrackingMode.BORDER: + case FullScreenMode.BORDER: return self._borderPos(coordinates) - case FullScreenTrackingMode.CENTER: + case FullScreenMode.CENTER: return coordinates def _borderPos( @@ -356,7 +356,7 @@ def _startSpotlight(self) -> None: Launch Spotlight from Full-screen class """ log.debug( - f"Launching spotlight mode from full-screen magnifier with mode {self._trackingMode}", + f"Launching spotlight mode from full-screen magnifier with mode {self._fullscreenMode}", ) self._stopTimer() self._spotlightManager._startSpotlight() diff --git a/source/_magnifier/magnifier.py b/source/_magnifier/magnifier.py index 0a933e3839d..e221bb90573 100644 --- a/source/_magnifier/magnifier.py +++ b/source/_magnifier/magnifier.py @@ -33,7 +33,7 @@ setZoomLevel, ZoomLevel, ) -from .utils.trackingManager import TrackingManager +from .utils.focusManager import FocusManager class Magnifier: @@ -48,7 +48,7 @@ def __init__(self): self._zoomLevel: float = getZoomLevel() self._panStep: int = getPanStep() self._timer: None | wx.Timer = None - self._trackingManager = TrackingManager() + self._focusManager = FocusManager() self._lastScreenPosition = Coordinates(0, 0) self._currentCoordinates = Coordinates(0, 0) self._lastFocusCoordinates = Coordinates(0, 0) @@ -182,7 +182,7 @@ def _startMagnifier(self) -> None: return self._isActive = True - self.currentCoordinates = self._trackingManager.getCurrentTrackedCoordinates() + self.currentCoordinates = self._focusManager.getCurrentFocusCoordinates() def _updateMagnifier(self) -> None: """ @@ -196,7 +196,7 @@ def _updateMagnifier(self) -> None: try: self._managePanning() if not self._isManualPanning: - self.currentCoordinates = self._trackingManager.getCurrentTrackedCoordinates() + self.currentCoordinates = self._focusManager.getCurrentFocusCoordinates() self._doUpdate() self._consecutiveErrors = 0 self._recoveryAttempts = 0 @@ -350,7 +350,7 @@ def _managePanning(self) -> None: """ Ensure that manual panning mode (self._isManualPanning) is set to False when focus coordinates change. """ - focusCoordinates = self._trackingManager.getCurrentTrackedCoordinates() + focusCoordinates = self._focusManager.getCurrentFocusCoordinates() if self._isManualPanning: if focusCoordinates != self._lastFocusCoordinates: self._isManualPanning = False diff --git a/source/_magnifier/utils/trackingManager.py b/source/_magnifier/utils/focusManager.py similarity index 87% rename from source/_magnifier/utils/trackingManager.py rename to source/_magnifier/utils/focusManager.py index 5aa6a38437b..64fd731d05f 100644 --- a/source/_magnifier/utils/trackingManager.py +++ b/source/_magnifier/utils/focusManager.py @@ -4,8 +4,8 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt """ -Tracking Manager for the magnifier module. -Handles all tracking logic and coordinate calculations. +Focus Manager for the magnifier module. +Handles all focus tracking logic and coordinate calculations. """ from comtypes import COMError @@ -17,20 +17,20 @@ import textInfos from textInfos.offsets import OffsetsTextInfo from .types import Coordinates, MagnifierTrackingType -from ..config import getTrackingState +from ..config import getFollowState -class TrackingManager: +class FocusManager: """ - Manages tracking for the magnifier. + Manages focus tracking for the magnifier. Tracks mouse, system focus, and navigator object positions. """ _SYSTEM_FOCUS_STICKINESS_SECONDS: float = 0.12 def __init__(self): - """Initialize the tracking manager.""" - self._lastTrackedObject: MagnifierTrackingType | None = None + """Initialize the focus manager.""" + self._lastFocusedObject: MagnifierTrackingType | None = None self._lastReportedCoordinates = Coordinates(0, 0) self._lastMousePosition = Coordinates(0, 0) self._lastSystemFocusPosition = Coordinates(0, 0) @@ -41,9 +41,9 @@ def __init__(self): self._lastValidNavigatorObjectPosition = Coordinates(0, 0) self._lastSystemFocusChangeTime: float = 0.0 - def getCurrentTrackedCoordinates(self) -> Coordinates: + def getCurrentFocusCoordinates(self) -> Coordinates: """ - Get the current tracked coordinates based on priority. + Get the current focus coordinates based on priority. Priority: Mouse (drag) > Mouse > System Focus > Review > Navigator Object. Special case: when both the system focus and navigator object change simultaneously but the review cursor does not (e.g. table cell navigation via numpad), the navigator @@ -51,7 +51,7 @@ def getCurrentTrackedCoordinates(self) -> Coordinates: Each source is only considered when its corresponding setting is enabled. - :return: The (x, y) coordinates of the current tracked object + :return: The (x, y) coordinates of the current focus """ now = time.monotonic() @@ -62,10 +62,10 @@ def getCurrentTrackedCoordinates(self) -> Coordinates: isClickPressed = winUser.getAsyncKeyState(winUser.VK_LBUTTON) < 0 # Cache settings once — each call reads from config.conf - isFollowMouse = getTrackingState(MagnifierTrackingType.MOUSE) - isFollowSystemFocus = getTrackingState(MagnifierTrackingType.SYSTEM_FOCUS) - isFollowReviewCursor = getTrackingState(MagnifierTrackingType.REVIEW) - isFollowNavigatorObject = getTrackingState(MagnifierTrackingType.NAVIGATOR_OBJECT) + isFollowMouse = getFollowState(MagnifierTrackingType.MOUSE) + isFollowSystemFocus = getFollowState(MagnifierTrackingType.SYSTEM_FOCUS) + isFollowReviewCursor = getFollowState(MagnifierTrackingType.REVIEW) + isFollowNavigatorObject = getFollowState(MagnifierTrackingType.NAVIGATOR_OBJECT) mouseChanged = self._lastMousePosition != mousePosition systemFocusChanged = self._lastSystemFocusPosition != systemFocusPosition @@ -85,7 +85,7 @@ def getCurrentTrackedCoordinates(self) -> Coordinates: # Priority 1: Mouse — drag (fires even when stationary) or movement if (isClickPressed or mouseChanged) and isFollowMouse: - self._lastTrackedObject = MagnifierTrackingType.MOUSE + self._lastFocusedObject = MagnifierTrackingType.MOUSE return self._rememberAndReturnCoordinates(mousePosition) # Special case: table cell navigation (numpad). @@ -93,22 +93,22 @@ def getCurrentTrackedCoordinates(self) -> Coordinates: # review cursor does not, the navigator object reflects the user's explicit navigation # intent and therefore takes priority over the system focus. if navigatorChanged and systemFocusChanged and not reviewChanged and isFollowNavigatorObject: - self._lastTrackedObject = MagnifierTrackingType.NAVIGATOR_OBJECT + self._lastFocusedObject = MagnifierTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Priority 2: System focus (focus object + browse mode cursor) if systemFocusChanged and isFollowSystemFocus: - self._lastTrackedObject = MagnifierTrackingType.SYSTEM_FOCUS + self._lastFocusedObject = MagnifierTrackingType.SYSTEM_FOCUS return self._rememberAndReturnCoordinates(systemFocusPosition) # Priority 3: Review cursor if reviewChanged and isFollowReviewCursor and reviewPosition is not None: - self._lastTrackedObject = MagnifierTrackingType.REVIEW + self._lastFocusedObject = MagnifierTrackingType.REVIEW return self._rememberAndReturnCoordinates(reviewPosition) # Priority 4: Navigator object (NumPad navigation) if navigatorChanged and isFollowNavigatorObject: - self._lastTrackedObject = MagnifierTrackingType.NAVIGATOR_OBJECT + self._lastFocusedObject = MagnifierTrackingType.NAVIGATOR_OBJECT return self._rememberAndReturnCoordinates(navigatorPosition) # Resolve the effective review position once (fallback to last valid when None) @@ -125,11 +125,11 @@ def getCurrentTrackedCoordinates(self) -> Coordinates: ) # Keep current source if still enabled; otherwise clear it and freeze at _lastReportedCoordinates - for trackedType, isEnabled, position in _sources: - if self._lastTrackedObject == trackedType: + for focusType, isEnabled, position in _sources: + if self._lastFocusedObject == focusType: if isEnabled: return self._rememberAndReturnCoordinates(position) - self._lastTrackedObject = None + self._lastFocusedObject = None break # No eligible update event from an enabled source. @@ -271,10 +271,10 @@ def _getNavigatorObjectPosition(self) -> Coordinates: return position return self._lastValidNavigatorObjectPosition - def getLastTrackedType(self) -> MagnifierTrackingType | None: + def getLastFocusType(self) -> MagnifierTrackingType | None: """ - Get the type of the last tracked object. + Get the type of the last focused object. - :return: The type of the last tracked object, or None when no tracked source is active. + :return: The type of the last focused object, or None when no focus source is active. """ - return self._lastTrackedObject + return self._lastFocusedObject diff --git a/source/_magnifier/utils/spotlightManager.py b/source/_magnifier/utils/spotlightManager.py index b3b5895fc76..ff641a979c8 100644 --- a/source/_magnifier/utils/spotlightManager.py +++ b/source/_magnifier/utils/spotlightManager.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Callable import ui -from .types import Coordinates, ZoomHistory, FullScreenTrackingMode +from .types import Coordinates, ZoomHistory, FullScreenMode import wx from logHandler import log @@ -29,12 +29,10 @@ def __init__( self._timer: wx.CallLater | None = None self._animationSteps: int = 40 self._animationStepDelay: int = 12 - self._currentCoordinates: Coordinates = ( - fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() - ) + self._currentCoordinates: Coordinates = fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() self._originalZoomLevel: int = 0 self._currentZoomLevel: float = 0.0 - self._originalMode: FullScreenTrackingMode | None = None + self._originalMode: FullScreenMode | None = None def _startSpotlight(self) -> None: """ @@ -47,7 +45,7 @@ def _startSpotlight(self) -> None: self._spotlightIsActive = True - startCoords = self._fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() + startCoords = self._fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() startCoords = self._fullscreenMagnifier._getCoordinatesForMode(startCoords) centerScreen = Coordinates( self._fullscreenMagnifier._displayOrientation.width // 2, @@ -55,7 +53,7 @@ def _startSpotlight(self) -> None: ) # Save the current mode for zoom back - self._originalMode = self._fullscreenMagnifier._trackingMode + self._originalMode = self._fullscreenMagnifier._fullscreenMode self._currentCoordinates = startCoords self._animateZoom(ZoomHistory(1.0, centerScreen), self._startMouseMonitoring) @@ -162,9 +160,9 @@ def zoomBack(self) -> None: f"zoom back with original zoom level {self._originalZoomLevel} and current zoom level {self._currentZoomLevel}", ) - focus = self._fullscreenMagnifier._trackingManager.getCurrentTrackedCoordinates() + focus = self._fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() - if self._originalMode == FullScreenTrackingMode.RELATIVE: + if self._originalMode == FullScreenMode.RELATIVE: savedZoom = self._fullscreenMagnifier.zoomLevel self._fullscreenMagnifier.zoomLevel = self._originalZoomLevel endCoordinates = self._fullscreenMagnifier._relativePos(focus) diff --git a/source/_magnifier/utils/types.py b/source/_magnifier/utils/types.py index b6311340228..a73d201ffcd 100644 --- a/source/_magnifier/utils/types.py +++ b/source/_magnifier/utils/types.py @@ -49,48 +49,48 @@ class MagnifierAction(DisplayStringEnum): PAN_BOTTOM_EDGE = auto() TOGGLE_FILTER = auto() CHANGE_MAGNIFIER_VIEW = auto() - TOGGLE_TRACKING_SETTINGS = auto() + TOGGLE_FOLLOW_SETTINGS = auto() CHANGE_FULLSCREEN_MODE = auto() START_SPOTLIGHT = auto() @property def _displayStringLabels(self) -> dict["MagnifierAction", str]: return { - # Translators: Action description for zooming in + # Translators: Action description for zooming in. self.ZOOM_IN: pgettext("magnifier action", "zoom in"), - # Translators: Action description for zooming out + # Translators: Action description for zooming out. self.ZOOM_OUT: pgettext("magnifier action", "zoom out"), - # Translators: Action description for panning left + # Translators: Action description for panning left. self.PAN_LEFT: pgettext("magnifier action", "pan left"), - # Translators: Action description for panning right + # Translators: Action description for panning right. self.PAN_RIGHT: pgettext("magnifier action", "pan right"), - # Translators: Action description for panning up + # Translators: Action description for panning up. self.PAN_UP: pgettext("magnifier action", "pan up"), - # Translators: Action description for panning down + # Translators: Action description for panning down. self.PAN_DOWN: pgettext("magnifier action", "pan down"), - # Translators: Action description for panning to left edge + # Translators: Action description for panning to left edge. self.PAN_LEFT_EDGE: pgettext("magnifier action", "pan to left edge"), - # Translators: Action description for panning to right edge + # Translators: Action description for panning to right edge. self.PAN_RIGHT_EDGE: pgettext("magnifier action", "pan to right edge"), - # Translators: Action description for panning to top edge + # Translators: Action description for panning to top edge. self.PAN_TOP_EDGE: pgettext("magnifier action", "pan to top edge"), - # Translators: Action description for panning to bottom edge + # Translators: Action description for panning to bottom edge. self.PAN_BOTTOM_EDGE: pgettext("magnifier action", "pan to bottom edge"), - # Translators: Action description for toggling settings - self.TOGGLE_TRACKING_SETTINGS: pgettext("magnifier action", "toggle tracking settings"), - # Translators: Action description for toggling color filters + # Translators: Action description for toggling settings. + self.TOGGLE_FOLLOW_SETTINGS: pgettext("magnifier action", "toggle tracking settings"), + # Translators: Action description for toggling color filters. self.TOGGLE_FILTER: pgettext("magnifier action", "cycle color filters"), - # Translators: Action description for changing magnifier view + # Translators: Action description for changing magnifier view. self.CHANGE_MAGNIFIER_VIEW: pgettext("magnifier action", "change magnifier view"), - # Translators: Action description for changing full-screen mode + # Translators: Action description for changing full-screen mode. self.CHANGE_FULLSCREEN_MODE: pgettext("magnifier action", "change full-screen mode"), - # Translators: Action description for showing entire screen overview + # Translators: Action description for showing entire screen overview. self.START_SPOTLIGHT: pgettext("magnifier action", "show screen overview"), } class MagnifierTrackingType(DisplayStringEnum): - """Type of tracking the magnifier should follow based on user settings""" + """Type of focus the magnifier should follow based on user settings""" MOUSE = auto() SYSTEM_FOCUS = auto() @@ -100,14 +100,14 @@ class MagnifierTrackingType(DisplayStringEnum): @property def _displayStringLabels(self) -> dict["MagnifierTrackingType", str]: return { - # Translators: Type of item tracked by the Magnifier - mouse cursor - self.MOUSE: pgettext("magnifier", "Mouse"), - # Translators: Type of item tracked by the Magnifier - system focus (active element) - self.SYSTEM_FOCUS: pgettext("magnifier", "System focus"), - # Translators: Type of item tracked by the Magnifier - review cursor position - self.REVIEW: pgettext("magnifier", "Review cursor"), - # Translators: Type of item tracked by the Magnifier - navigator object position - self.NAVIGATOR_OBJECT: pgettext("magnifier", "Navigator object"), + # Translators: Tracking type for magnifier to follow - mouse cursor. + self.MOUSE: pgettext("magnifier follow tracking type", "Mouse"), + # Translators: Tracking type for magnifier to follow - system focus (active element). + self.SYSTEM_FOCUS: pgettext("magnifier follow tracking type", "System focus"), + # Translators: Tracking type for magnifier to follow - review cursor position. + self.REVIEW: pgettext("magnifier follow tracking type", "Review cursor"), + # Translators: Tracking type for magnifier to follow - navigator object position. + self.NAVIGATOR_OBJECT: pgettext("magnifier follow tracking type", "Navigator object"), } @@ -122,13 +122,13 @@ class MagnifiedView(DisplayStringStrEnum): @property def _displayStringLabels(self) -> dict["MagnifiedView", str]: return { - # Translators: Magnifier view - full-screen mode + # Translators: Magnifier view - full-screen mode. self.FULLSCREEN: pgettext("magnifier", "Fullscreen"), - # Translators: Magnifier view - fixed mode + # Translators: Magnifier view - fixed mode. self.FIXED: pgettext("magnifier", "Fixed"), - # Translators: Magnifier view - docked mode + # Translators: Magnifier view - docked mode. self.DOCKED: pgettext("magnifier", "Docked"), - # Translators: Magnifier view - lens mode + # Translators: Magnifier view - lens mode. self.LENS: pgettext("magnifier", "Lens"), } @@ -147,19 +147,19 @@ class ZoomHistory(NamedTuple): coordinates: Coordinates -class FullScreenTrackingMode(DisplayStringStrEnum): +class FullScreenMode(DisplayStringStrEnum): CENTER = "center" BORDER = "border" RELATIVE = "relative" @property - def _displayStringLabels(self) -> dict["FullScreenTrackingMode", str]: + def _displayStringLabels(self) -> dict["FullScreenMode", str]: return { - # Translators: Magnifier tracking mode - center the tracked item on screen + # Translators: Magnifier tracking mode - center on screen. self.CENTER: pgettext("magnifier", "Center"), - # Translators: Magnifier tracking mode - follow the tracked item only when it reaches screen borders + # Translators: Magnifier tracking mode - follow tracking at screen borders. self.BORDER: pgettext("magnifier", "Border"), - # Translators: Magnifier tracking mode - preserve the tracked item's screen position within the magnified view (same relative screen location) + # Translators: Magnifier tracking mode - maintain relative position. self.RELATIVE: pgettext("magnifier", "Relative"), } @@ -172,10 +172,10 @@ class Filter(DisplayStringStrEnum): @property def _displayStringLabels(self) -> dict["Filter", str]: return { - # Translators: Magnifier color filter - no filter applied + # Translators: Magnifier color filter - no filter applied. self.NORMAL: pgettext("magnifier", "Normal"), - # Translators: Magnifier color filter - grayscale/black and white + # Translators: Magnifier color filter - grayscale/black and white. self.GRAYSCALE: pgettext("magnifier", "Grayscale"), - # Translators: Magnifier color filter - inverted colors + # Translators: Magnifier color filter - inverted colors. self.INVERTED: pgettext("magnifier", "Inverted"), } diff --git a/source/config/configSpec.py b/source/config/configSpec.py index a8d05d34bbb..e65a1679661 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -126,7 +126,7 @@ followReviewCursor = boolean(default=True) followNavigatorObject = boolean(default=True) panStep = integer(min=1, max=100, default=10) - fullscreenTrackingMode = string(default="center") + fullscreenMode = string(default="center") # Presentation settings [presentation] diff --git a/source/globalCommands.py b/source/globalCommands.py index c74a95ce0cc..9baba481b64 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -5215,11 +5215,11 @@ def script_cycleFilters( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleTrackingMouse( + def script_toggleFollowMouse( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleTracking(MagnifierTrackingType.MOUSE) + _magnifier.commands.toggleFollow(MagnifierTrackingType.MOUSE) @script( description=_( @@ -5228,11 +5228,11 @@ def script_toggleTrackingMouse( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleTrackingSystemFocus( + def script_toggleFollowSystemFocus( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleTracking(MagnifierTrackingType.SYSTEM_FOCUS) + _magnifier.commands.toggleFollow(MagnifierTrackingType.SYSTEM_FOCUS) @script( description=_( @@ -5241,11 +5241,11 @@ def script_toggleTrackingSystemFocus( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleTrackingReview( + def script_toggleFollowReview( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleTracking(MagnifierTrackingType.REVIEW) + _magnifier.commands.toggleFollow(MagnifierTrackingType.REVIEW) @script( description=_( @@ -5254,11 +5254,11 @@ def script_toggleTrackingReview( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleTrackingNavigatorObject( + def script_toggleFollowNavigatorObject( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleTracking(MagnifierTrackingType.NAVIGATOR_OBJECT) + _magnifier.commands.toggleFollow(MagnifierTrackingType.NAVIGATOR_OBJECT) @script( description=_( @@ -5267,11 +5267,11 @@ def script_toggleTrackingNavigatorObject( ), category=SCRCAT_MAGNIFIER, ) - def script_toggleAllTracking( + def script_toggleAllFollow( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.toggleAllTracking() + _magnifier.commands.toggleAllFollow() @script( description=_( @@ -5284,7 +5284,7 @@ def script_cycleTrackingModes( self, gesture: inputCore.InputGesture, ) -> None: - _magnifier.commands.cycleFullscreenTrackingMode() + _magnifier.commands.cycleFullscreenMode() @script( description=_( diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 1ed65f30b61..40c926a1455 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -42,7 +42,7 @@ from _magnifier import getMagnifier from _magnifier.commands import toggleMagnifier import _magnifier.config as magnifierConfig -from _magnifier.utils.types import Filter, FullScreenTrackingMode, MagnifierTrackingType +from _magnifier.utils.types import Filter, FullScreenMode, MagnifierTrackingType from _magnifier.fullscreenMagnifier import FullScreenMagnifier import queueHandler import requests @@ -6047,18 +6047,18 @@ def _applyCurrentSettingsToConfigAndRuntime(self): selectedZoom = self.zoomCtrl.GetValue() selectedPanStep = self.panSpinCtrl.GetValue() selectedFilter = list(Filter)[self.filterList.GetSelection()] - selectedMode = list(FullScreenTrackingMode)[self.trackingModeList.GetSelection()] + selectedMode = list(FullScreenMode)[self.fullscreenModeList.GetSelection()] roundedZoom = magnifierConfig.roundZoomLevel(selectedZoom) magnifierConfig.setZoomLevel(roundedZoom) self.zoomCtrl.SetValue(roundedZoom) magnifierConfig.setPanStep(selectedPanStep) magnifierConfig.setFilter(selectedFilter) - magnifierConfig.setFullscreenTrackingMode(selectedMode) + magnifierConfig.setFullscreenMode(selectedMode) config.conf["magnifier"]["isTrueCentered"] = self.trueCenterTrackingCheckBox.GetValue() for trackingType, checkBox in self._trackingTypeCheckBoxes.items(): - magnifierConfig.setTrackingState(trackingType, checkBox.GetValue()) + magnifierConfig.setFollowState(trackingType, checkBox.GetValue()) magnifier = getMagnifier() if magnifier: @@ -6066,7 +6066,7 @@ def _applyCurrentSettingsToConfigAndRuntime(self): magnifier._panStep = selectedPanStep magnifier.filterType = selectedFilter if isinstance(magnifier, FullScreenMagnifier): - magnifier._trackingMode = selectedMode + magnifier._fullscreenMode = selectedMode def _onImmediateSettingChange(self, evt: wx.CommandEvent): """Handle immediate updates for non-enable magnifier settings.""" @@ -6082,7 +6082,7 @@ def makeSettings( sizer=settingsSizer, ) - # General group + # GENERAL GROUP # Translators: This is the label for a group of general magnifier options in the # magnifier settings panel generalGroupText = _("General") @@ -6105,7 +6105,7 @@ def makeSettings( self.enableMagnifierCheckBox.Bind(wx.EVT_CHECKBOX, self.onEnableMagnifierChange) self.enableMagnifierCheckBox.SetValue(self._magnifierEnabledInitially) - # Zoom settings + # ZOOM SETTINGS # Translators: The label for a setting in magnifier settings to select the zoom level. zoomLabelText = _("&Zoom (%):") @@ -6127,7 +6127,7 @@ def makeSettings( self.zoomCtrl.SetValue(zoomLevel) self.zoomCtrl.Bind(wx.EVT_SPINCTRL, self._onImmediateSettingChange) - # Filter settings + # FILTER SETTINGS # Translators: The label for a setting in magnifier settings to select the filter filterLabelText = _("Color f&ilter:") filterChoices = [f.displayString for f in Filter] @@ -6144,7 +6144,7 @@ def makeSettings( self.filterList.SetSelection(list(Filter).index(filterValue)) self.filterList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) - # True center tracking settings + # TRUE CENTER TRACKING # Translators: The label for a setting in magnifier settings to select whether true center tracking is used trueCenterTrackingText = _("&True center tracking") self.trueCenterTrackingCheckBox = generalGroup.addItem( @@ -6158,7 +6158,7 @@ def makeSettings( self._trueCenterTrackingInitially = self.trueCenterTrackingCheckBox.GetValue() self.trueCenterTrackingCheckBox.Bind(wx.EVT_CHECKBOX, self._onImmediateSettingChange) - # Pan settings + # PAN SETTINGS # Translators: The label for a setting in magnifier settings to select the pan step size (in percentage). panStepSizeLabelText = _("&Panning step size (%):") @@ -6180,7 +6180,7 @@ def makeSettings( self.panSpinCtrl.Bind(wx.EVT_SPINCTRL, self._onImmediateSettingChange) self.panSpinCtrl.Bind(wx.EVT_TEXT, self._onImmediateSettingChange) - # Tracking group + # TRACKING GROUP # Translators: This is the label for a group of tracking options in the magnifier settings panel trackingGroupText = _("Tracking") trackingGroupSizer = wx.StaticBoxSizer(wx.VERTICAL, self, label=trackingGroupText) @@ -6206,13 +6206,13 @@ def makeSettings( for trackingType, (label, helpId) in _trackingTypeLabels.items(): checkBox = trackingGroup.addItem(wx.CheckBox(trackingGroupBox, label=label)) self.bindHelpEvent(helpId, checkBox) - followState = magnifierConfig.getTrackingState(trackingType) + followState = magnifierConfig.getFollowState(trackingType) self._trackingTypeInitially[trackingType] = followState checkBox.SetValue(followState) checkBox.Bind(wx.EVT_CHECKBOX, self._onImmediateSettingChange) self._trackingTypeCheckBoxes[trackingType] = checkBox - # Fullscreen group + # FULLSCREEN GROUP # Translators: This is the label for a group of fullscreen magnifier options in the # magnifier settings panel fullscreenGroupText = _("Fullscreen") @@ -6221,27 +6221,25 @@ def makeSettings( fullscreenGroup = guiHelper.BoxSizerHelper(fullscreenGroupBox, sizer=self.fullscreenGroupSizer) sHelper.addItem(fullscreenGroup) - # Fullscreen mode settings + # FULLSCREEN MODE SETTINGS # Translators: The label for a setting in magnifier settings to select the full-screen mode - FullscreenTrackingModeLabelText = _("Tracking &mode:") - FullscreenTrackingModeChoices = ( - [mode.displayString for mode in FullScreenTrackingMode] if FullScreenTrackingMode else [] - ) - self.trackingModeList = fullscreenGroup.addLabeledControl( - FullscreenTrackingModeLabelText, + fullscreenModeLabelText = _("Tracking &mode:") + fullscreenModeChoices = [mode.displayString for mode in FullScreenMode] if FullScreenMode else [] + self.fullscreenModeList = fullscreenGroup.addLabeledControl( + fullscreenModeLabelText, wx.Choice, - choices=FullscreenTrackingModeChoices, + choices=fullscreenModeChoices, ) self.bindHelpEvent( "MagnifierTrackingMode", - self.trackingModeList, + self.fullscreenModeList, ) # Set value from config - trackingMode = magnifierConfig.getFullscreenTrackingMode() - self._trackingModeInitially = trackingMode - self.trackingModeList.SetSelection(list(FullScreenTrackingMode).index(trackingMode)) - self.trackingModeList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) + fullscreenMode = magnifierConfig.getFullscreenMode() + self._fullscreenModeInitially = fullscreenMode + self.fullscreenModeList.SetSelection(list(FullScreenMode).index(fullscreenMode)) + self.fullscreenModeList.Bind(wx.EVT_CHOICE, self._onImmediateSettingChange) def onSave(self): """Save the current selections to config.""" @@ -6252,14 +6250,14 @@ def onSave(self): selectedZoom = self.zoomCtrl.GetValue() selectedPanStep = self.panSpinCtrl.GetValue() selectedFilter = list(Filter)[self.filterList.GetSelection()] - selectedMode = list(FullScreenTrackingMode)[self.trackingModeList.GetSelection()] + selectedMode = list(FullScreenMode)[self.fullscreenModeList.GetSelection()] isTrueCentered = self.trueCenterTrackingCheckBox.GetValue() roundedZoom = magnifierConfig.roundZoomLevel(selectedZoom) self._zoomInitially = roundedZoom self._panStepInitially = selectedPanStep self._filterInitially = selectedFilter - self._FullscreenTrackingModeInitially = selectedMode + self._fullscreenModeInitially = selectedMode self._trueCenterTrackingInitially = isTrueCentered for trackingType, checkBox in self._trackingTypeCheckBoxes.items(): shouldFollow = checkBox.GetValue() @@ -6270,10 +6268,10 @@ def onDiscard(self): magnifierConfig.setZoomLevel(self._zoomInitially) magnifierConfig.setPanStep(self._panStepInitially) magnifierConfig.setFilter(self._filterInitially) - magnifierConfig.setFullscreenTrackingMode(self._FullscreenTrackingModeInitially) + magnifierConfig.setFullscreenMode(self._fullscreenModeInitially) config.conf["magnifier"]["isTrueCentered"] = self._trueCenterTrackingInitially for trackingType, state in self._trackingTypeInitially.items(): - magnifierConfig.setTrackingState(trackingType, state) + magnifierConfig.setFollowState(trackingType, state) magnifier = getMagnifier() if magnifier: @@ -6281,7 +6279,7 @@ def onDiscard(self): magnifier._panStep = self._panStepInitially magnifier.filterType = self._filterInitially if isinstance(magnifier, FullScreenMagnifier): - magnifier._trackingMode = self._FullscreenTrackingModeInitially + magnifier._fullscreenMode = self._fullscreenModeInitially if self._magnifierEnabledInitially != magnifierConfig.getEnabled(): toggleMagnifier() diff --git a/tests/unit/test_magnifier/test_focusManager.py b/tests/unit/test_magnifier/test_focusManager.py index c4a47bffc9e..0ea9226b9d5 100644 --- a/tests/unit/test_magnifier/test_focusManager.py +++ b/tests/unit/test_magnifier/test_focusManager.py @@ -4,7 +4,7 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt from dataclasses import dataclass -from _magnifier.utils.trackingManager import TrackingManager +from _magnifier.utils.focusManager import FocusManager from _magnifier.utils.types import Coordinates, MagnifierTrackingType import unittest from unittest.mock import MagicMock, Mock, patch @@ -17,7 +17,7 @@ def _makeFollowStateSideEffect( followReview: bool = True, followNavigatorObject: bool = True, ): - """Return a side_effect function for patching getTrackingState.""" + """Return a side_effect function for patching getFollowState.""" states = { MagnifierTrackingType.MOUSE: followMouse, MagnifierTrackingType.SYSTEM_FOCUS: followSystemFocus, @@ -46,76 +46,76 @@ class FocusTestParam: followNavigatorObject: bool = True -class TestTrackingManager(unittest.TestCase): - """Tests for the TrackingManager class.""" +class TestFocusManager(unittest.TestCase): + """Tests for the FocusManager class.""" def setUp(self): """Setup before each test.""" - self.trackingManager = TrackingManager() - - def testTrackingManagerCreation(self): - """Can we create a TrackingManager with initialized values?""" - self.assertIsNone(self.trackingManager._lastTrackedObject) - self.assertEqual(self.trackingManager._lastReportedCoordinates, Coordinates(0, 0)) - self.assertIsNone(self.trackingManager._lastReviewPosition) - self.assertEqual(self.trackingManager._lastMousePosition, Coordinates(0, 0)) - self.assertEqual(self.trackingManager._lastSystemFocusPosition, Coordinates(0, 0)) - self.assertEqual(self.trackingManager._lastNavigatorObjectPosition, Coordinates(0, 0)) - self.assertEqual(self.trackingManager._lastValidSystemFocusPosition, Coordinates(0, 0)) - self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(0, 0)) - self.assertEqual(self.trackingManager._lastValidNavigatorObjectPosition, Coordinates(0, 0)) + self.focusManager = FocusManager() + + def testFocusManagerCreation(self): + """Can we create a FocusManager with initialized values?""" + self.assertIsNone(self.focusManager._lastFocusedObject) + self.assertEqual(self.focusManager._lastReportedCoordinates, Coordinates(0, 0)) + self.assertIsNone(self.focusManager._lastReviewPosition) + self.assertEqual(self.focusManager._lastMousePosition, Coordinates(0, 0)) + self.assertEqual(self.focusManager._lastSystemFocusPosition, Coordinates(0, 0)) + self.assertEqual(self.focusManager._lastNavigatorObjectPosition, Coordinates(0, 0)) + self.assertEqual(self.focusManager._lastValidSystemFocusPosition, Coordinates(0, 0)) + self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(0, 0)) + self.assertEqual(self.focusManager._lastValidNavigatorObjectPosition, Coordinates(0, 0)) def testGetNavigatorObjectPosition(self): """Getting navigator object position with different API responses.""" # Case 1: Navigator object location available - with patch("_magnifier.utils.trackingManager.api.getNavigatorObject") as mock_navigator: + with patch("_magnifier.utils.focusManager.api.getNavigatorObject") as mock_navigator: mock_navigator.return_value.location = (100, 150, 200, 300) - coords = self.trackingManager._getNavigatorObjectPosition() + coords = self.focusManager._getNavigatorObjectPosition() # Center: (100 + 200//2, 150 + 300//2) = (200, 300) self.assertEqual(coords, Coordinates(200, 300)) # Case 2: Navigator object fails - should return last valid position from Case 1 - with patch("_magnifier.utils.trackingManager.api.getNavigatorObject") as mock_navigator: + with patch("_magnifier.utils.focusManager.api.getNavigatorObject") as mock_navigator: mock_navigator.return_value.location = Mock(side_effect=Exception()) - coords = self.trackingManager._getNavigatorObjectPosition() + coords = self.focusManager._getNavigatorObjectPosition() # Should return last valid position (200, 300) self.assertEqual(coords, Coordinates(200, 300)) # Case 3: Navigator object is None - should return last valid position - with patch("_magnifier.utils.trackingManager.api.getNavigatorObject", return_value=None): - coords = self.trackingManager._getNavigatorObjectPosition() + with patch("_magnifier.utils.focusManager.api.getNavigatorObject", return_value=None): + coords = self.focusManager._getNavigatorObjectPosition() self.assertEqual(coords, Coordinates(200, 300)) def testGetReviewPosition(self): """Getting review cursor position with different API responses.""" # Case 1: Review position available - with patch("_magnifier.utils.trackingManager.api.getReviewPosition") as mock_review: + with patch("_magnifier.utils.focusManager.api.getReviewPosition") as mock_review: mock_point = Mock() mock_point.x = 300 mock_point.y = 400 mock_review.return_value.pointAtStart = mock_point - coords = self.trackingManager._getReviewPosition() + coords = self.focusManager._getReviewPosition() self.assertEqual(coords, Coordinates(300, 400)) # _lastValidReviewPosition must be updated - self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(300, 400)) + self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(300, 400)) # Case 2: pointAtStart raises NotImplementedError → returns None - with patch("_magnifier.utils.trackingManager.api.getReviewPosition") as mock_review: + with patch("_magnifier.utils.focusManager.api.getReviewPosition") as mock_review: type(mock_review.return_value).pointAtStart = property( fget=Mock(side_effect=NotImplementedError), ) - coords = self.trackingManager._getReviewPosition() + coords = self.focusManager._getReviewPosition() self.assertIsNone(coords) # _lastValidReviewPosition must NOT change - self.assertEqual(self.trackingManager._lastValidReviewPosition, Coordinates(300, 400)) + self.assertEqual(self.focusManager._lastValidReviewPosition, Coordinates(300, 400)) # Case 3: getReviewPosition returns None → returns None - with patch("_magnifier.utils.trackingManager.api.getReviewPosition", return_value=None): - coords = self.trackingManager._getReviewPosition() + with patch("_magnifier.utils.focusManager.api.getReviewPosition", return_value=None): + coords = self.focusManager._getReviewPosition() self.assertIsNone(coords) def testGetReviewPositionSurvivesCOMError(self): @@ -125,54 +125,54 @@ def testGetReviewPositionSurvivesCOMError(self): mockReviewPos = Mock() type(mockReviewPos).pointAtStart = property(lambda self: (_ for _ in ()).throw(comError)) - with patch("_magnifier.utils.trackingManager.api.getReviewPosition", return_value=mockReviewPos): - result = self.trackingManager._getReviewPosition() + with patch("_magnifier.utils.focusManager.api.getReviewPosition", return_value=mockReviewPos): + result = self.focusManager._getReviewPosition() self.assertIsNone(result) def testGetSystemFocusPosition(self): """Getting system focus position with different API responses.""" # Case 1: Caret position successful (browse mode) - with patch("_magnifier.utils.trackingManager.api.getCaretPosition") as mock_caret: + with patch("_magnifier.utils.focusManager.api.getCaretPosition") as mock_caret: mock_point = Mock() mock_point.x = 500 mock_point.y = 600 mock_caret.return_value.pointAtStart = mock_point - coords = self.trackingManager._getSystemFocusPosition() + coords = self.focusManager._getSystemFocusPosition() self.assertEqual(coords, Coordinates(500, 600)) # Case 2: Caret fails, focus object works - with patch("_magnifier.utils.trackingManager.api.getCaretPosition", side_effect=RuntimeError): - with patch("_magnifier.utils.trackingManager.api.getFocusObject") as mock_focus: + with patch("_magnifier.utils.focusManager.api.getCaretPosition", side_effect=RuntimeError): + with patch("_magnifier.utils.focusManager.api.getFocusObject") as mock_focus: mock_focus.return_value.location = (200, 300, 100, 80) - coords = self.trackingManager._getSystemFocusPosition() + coords = self.focusManager._getSystemFocusPosition() # Center: (200 + 100//2, 300 + 80//2) = (250, 340) self.assertEqual(coords, Coordinates(250, 340)) # Case 3: Everything fails - should return last valid position from Case 2 - with patch("_magnifier.utils.trackingManager.api.getCaretPosition", side_effect=RuntimeError): - with patch("_magnifier.utils.trackingManager.api.getFocusObject", return_value=None): - coords = self.trackingManager._getSystemFocusPosition() + with patch("_magnifier.utils.focusManager.api.getCaretPosition", side_effect=RuntimeError): + with patch("_magnifier.utils.focusManager.api.getFocusObject", return_value=None): + coords = self.focusManager._getSystemFocusPosition() # Should return last valid position (250, 340) self.assertEqual(coords, Coordinates(250, 340)) def testGetSystemFocusPositionSurvivesOSError(self): """OSError from magnification API calls must be caught.""" with patch( - "_magnifier.utils.trackingManager.api.getCaretPosition", + "_magnifier.utils.focusManager.api.getCaretPosition", side_effect=OSError("WinError"), ): - with patch("_magnifier.utils.trackingManager.api.getFocusObject") as mock_focus: + with patch("_magnifier.utils.focusManager.api.getFocusObject") as mock_focus: mock_focus.return_value.location = (10, 20, 30, 40) - coords = self.trackingManager._getSystemFocusPosition() + coords = self.focusManager._getSystemFocusPosition() self.assertEqual(coords, Coordinates(25, 40)) def testGetMousePosition(self): """Getting mouse position.""" - with patch("_magnifier.utils.trackingManager.winUser.getCursorPos", return_value=(123, 456)): - coords = self.trackingManager._getMousePosition() + with patch("_magnifier.utils.focusManager.winUser.getCursorPos", return_value=(123, 456)): + coords = self.focusManager._getMousePosition() self.assertEqual(coords, Coordinates(123, 456)) def testGetCurrentFocusCoordinates(self): @@ -308,19 +308,19 @@ def testGetCurrentFocusCoordinates(self): for param in subTestParams: with self.subTest(description=param.description): # Reset focus manager state - self.trackingManager._lastNavigatorObjectPosition = Coordinates(0, 0) - self.trackingManager._lastSystemFocusPosition = Coordinates(0, 0) - self.trackingManager._lastMousePosition = Coordinates(0, 0) - self.trackingManager._lastReviewPosition = None - self.trackingManager._lastSystemFocusChangeTime = 0.0 - self.trackingManager._lastTrackedObject = param.lastFocusedObject + self.focusManager._lastNavigatorObjectPosition = Coordinates(0, 0) + self.focusManager._lastSystemFocusPosition = Coordinates(0, 0) + self.focusManager._lastMousePosition = Coordinates(0, 0) + self.focusManager._lastReviewPosition = None + self.focusManager._lastSystemFocusChangeTime = 0.0 + self.focusManager._lastFocusedObject = param.lastFocusedObject # Mock instance methods - self.trackingManager._getNavigatorObjectPosition = MagicMock( + self.focusManager._getNavigatorObjectPosition = MagicMock( return_value=param.navigatorObjectPos, ) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=param.systemFocusPos) - self.trackingManager._getReviewPosition = MagicMock(return_value=param.reviewPos) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=param.systemFocusPos) + self.focusManager._getReviewPosition = MagicMock(return_value=param.reviewPos) followStateSideEffect = _makeFollowStateSideEffect( followMouse=param.followMouse, @@ -331,50 +331,50 @@ def testGetCurrentFocusCoordinates(self): with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), patch( - "_magnifier.utils.trackingManager.winUser.getAsyncKeyState", + "_magnifier.utils.focusManager.winUser.getAsyncKeyState", side_effect=lambda _key: -1 if param.leftPressed else 0, ), patch( - "_magnifier.utils.trackingManager.winUser.getCursorPos", + "_magnifier.utils.focusManager.winUser.getCursorPos", return_value=param.mousePos, ), ): # Execute - focusCoordinates = self.trackingManager.getCurrentTrackedCoordinates() + focusCoordinates = self.focusManager.getCurrentFocusCoordinates() # Assert self.assertEqual(focusCoordinates, param.expectedCoords) - self.assertEqual(self.trackingManager.getLastTrackedType(), param.expectedFocus) + self.assertEqual(self.focusManager.getLastFocusType(), param.expectedFocus) def testGetLastFocusType(self): """Test getting the last focus type.""" - self.assertIsNone(self.trackingManager.getLastTrackedType()) + self.assertIsNone(self.focusManager.getLastFocusType()) for focusType in MagnifierTrackingType: - self.trackingManager._lastTrackedObject = focusType - self.assertEqual(self.trackingManager.getLastTrackedType(), focusType) + self.focusManager._lastFocusedObject = focusType + self.assertEqual(self.focusManager.getLastFocusType(), focusType) class TestFollowSettings(unittest.TestCase): """Verify that each follow* setting actually gates its source.""" def setUp(self): - self.trackingManager = TrackingManager() - self.trackingManager._lastMousePosition = Coordinates(0, 0) - self.trackingManager._lastSystemFocusPosition = Coordinates(0, 0) - self.trackingManager._lastReviewPosition = None - self.trackingManager._lastNavigatorObjectPosition = Coordinates(0, 0) + self.focusManager = FocusManager() + self.focusManager._lastMousePosition = Coordinates(0, 0) + self.focusManager._lastSystemFocusPosition = Coordinates(0, 0) + self.focusManager._lastReviewPosition = None + self.focusManager._lastNavigatorObjectPosition = Coordinates(0, 0) def _run(self, *, followMouse, followSystemFocus, followReview, followNavigatorObject): - """Run getCurrentTrackedCoordinates with all sources moved and the given settings.""" - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + """Run getCurrentFocusCoordinates with all sources moved and the given settings.""" + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=followMouse, @@ -385,12 +385,12 @@ def _run(self, *, followMouse, followSystemFocus, followReview, followNavigatorO with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - return self.trackingManager.getCurrentTrackedCoordinates() + return self.focusManager.getCurrentFocusCoordinates() def testFollowMouseDisabled(self): """When followMouse=False, mouse changes are ignored and system focus wins.""" @@ -401,7 +401,7 @@ def testFollowMouseDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(20, 20)) - self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.SYSTEM_FOCUS) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.SYSTEM_FOCUS) def testFollowSystemFocusDisabled(self): """When followSystemFocus=False, system focus changes are ignored and review wins.""" @@ -412,7 +412,7 @@ def testFollowSystemFocusDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(30, 30)) - self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.REVIEW) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.REVIEW) def testFollowReviewDisabled(self): """When followReview=False, review changes are ignored and navigator wins.""" @@ -423,7 +423,7 @@ def testFollowReviewDisabled(self): followNavigatorObject=True, ) self.assertEqual(coords, Coordinates(40, 40)) - self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.NAVIGATOR_OBJECT) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.NAVIGATOR_OBJECT) def testAllFollowDisabled(self): """When all settings are False, no source fires and focus remains frozen.""" @@ -438,10 +438,10 @@ def testAllFollowDisabled(self): def testAllFollowDisabledKeepsLastTrackedPosition(self): """Disabling all follow modes keeps the most recently tracked coordinates.""" - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followEnabledSideEffect = _makeFollowStateSideEffect( followMouse=True, @@ -451,19 +451,19 @@ def testAllFollowDisabledKeepsLastTrackedPosition(self): ) with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followEnabledSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) # Move all sources, but disable every follow setting. - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(99, 99)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(88, 88)) - self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(77, 77)) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(66, 66)) + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(99, 99)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(88, 88)) + self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(77, 77)) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(66, 66)) followDisabledSideEffect = _makeFollowStateSideEffect( followMouse=False, followSystemFocus=False, @@ -472,22 +472,22 @@ def testAllFollowDisabledKeepsLastTrackedPosition(self): ) with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followDisabledSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.trackingManager.getLastTrackedType()) + self.assertIsNone(self.focusManager.getLastFocusType()) def testFollowMouseDragIgnoresSettings(self): """Mouse drag (left click held) with followMouse=True always wins regardless of others.""" - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=True, @@ -498,30 +498,30 @@ def testFollowMouseDragIgnoresSettings(self): with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=-1), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=-1), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertEqual(self.trackingManager.getLastTrackedType(), MagnifierTrackingType.MOUSE) + self.assertEqual(self.focusManager.getLastFocusType(), MagnifierTrackingType.MOUSE) def testDisableFollowMouseKeepsViewFrozen(self): """When followMouse is disabled, view remains frozen until a followed source changes.""" # Simulate: mouse was previously the active focus source - self.trackingManager._lastTrackedObject = MagnifierTrackingType.MOUSE - self.trackingManager._lastReportedCoordinates = Coordinates(10, 10) + self.focusManager._lastFocusedObject = MagnifierTrackingType.MOUSE + self.focusManager._lastReportedCoordinates = Coordinates(10, 10) # Positions haven't changed from last recorded values (no "change" detected) - self.trackingManager._lastMousePosition = Coordinates(10, 10) - self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) - self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) + self.focusManager._lastMousePosition = Coordinates(10, 10) + self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) + self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=None) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=None) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -532,29 +532,29 @@ def testDisableFollowMouseKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.trackingManager.getLastTrackedType()) + self.assertIsNone(self.focusManager.getLastFocusType()) def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): """When followMouse is disabled, mouse movement alone does not move the view.""" - self.trackingManager._lastTrackedObject = MagnifierTrackingType.MOUSE - self.trackingManager._lastReportedCoordinates = Coordinates(10, 10) - self.trackingManager._lastMousePosition = Coordinates(10, 10) - self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) - self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) + self.focusManager._lastFocusedObject = MagnifierTrackingType.MOUSE + self.focusManager._lastReportedCoordinates = Coordinates(10, 10) + self.focusManager._lastMousePosition = Coordinates(10, 10) + self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) + self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) # Mouse has moved but followMouse is False - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(15, 15)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=None) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(15, 15)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=None) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -565,29 +565,29 @@ def testDisableFollowMouseWhileMouseMovingKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(10, 10)) - self.assertIsNone(self.trackingManager.getLastTrackedType()) + self.assertIsNone(self.focusManager.getLastFocusType()) def testDisableFollowSystemFocusKeepsViewFrozen(self): """When followSystemFocus is disabled, view remains frozen until a followed source changes.""" - self.trackingManager._lastTrackedObject = MagnifierTrackingType.SYSTEM_FOCUS - self.trackingManager._lastReportedCoordinates = Coordinates(20, 20) - self.trackingManager._lastMousePosition = Coordinates(10, 10) - self.trackingManager._lastSystemFocusPosition = Coordinates(20, 20) - self.trackingManager._lastReviewPosition = Coordinates(30, 30) - self.trackingManager._lastNavigatorObjectPosition = Coordinates(40, 40) - - self.trackingManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) - self.trackingManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) - self.trackingManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) - self.trackingManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) + self.focusManager._lastFocusedObject = MagnifierTrackingType.SYSTEM_FOCUS + self.focusManager._lastReportedCoordinates = Coordinates(20, 20) + self.focusManager._lastMousePosition = Coordinates(10, 10) + self.focusManager._lastSystemFocusPosition = Coordinates(20, 20) + self.focusManager._lastReviewPosition = Coordinates(30, 30) + self.focusManager._lastNavigatorObjectPosition = Coordinates(40, 40) + + self.focusManager._getMousePosition = MagicMock(return_value=Coordinates(10, 10)) + self.focusManager._getSystemFocusPosition = MagicMock(return_value=Coordinates(20, 20)) + self.focusManager._getReviewPosition = MagicMock(return_value=Coordinates(30, 30)) + self.focusManager._getNavigatorObjectPosition = MagicMock(return_value=Coordinates(40, 40)) followStateSideEffect = _makeFollowStateSideEffect( followMouse=False, @@ -598,12 +598,12 @@ def testDisableFollowSystemFocusKeepsViewFrozen(self): with ( patch( - "_magnifier.utils.trackingManager.getTrackingState", + "_magnifier.utils.focusManager.getFollowState", side_effect=followStateSideEffect, ), - patch("_magnifier.utils.trackingManager.winUser.getAsyncKeyState", return_value=0), + patch("_magnifier.utils.focusManager.winUser.getAsyncKeyState", return_value=0), ): - coords = self.trackingManager.getCurrentTrackedCoordinates() + coords = self.focusManager.getCurrentFocusCoordinates() self.assertEqual(coords, Coordinates(20, 20)) - self.assertIsNone(self.trackingManager.getLastTrackedType()) + self.assertIsNone(self.focusManager.getLastFocusType()) diff --git a/tests/unit/test_magnifier/test_fullscreenMagnifier.py b/tests/unit/test_magnifier/test_fullscreenMagnifier.py index 11a853ac2db..9f7f348aaa9 100644 --- a/tests/unit/test_magnifier/test_fullscreenMagnifier.py +++ b/tests/unit/test_magnifier/test_fullscreenMagnifier.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch from _magnifier.config import ZoomLevel from _magnifier.magnifier import Magnifier -from _magnifier.utils.types import Filter, FullScreenTrackingMode, MagnifiedView, Direction, Coordinates +from _magnifier.utils.types import Filter, FullScreenMode, MagnifiedView, Direction, Coordinates from _magnifier.fullscreenMagnifier import FullScreenMagnifier from tests.unit.test_magnifier.test_magnifier import _TestMagnifier @@ -21,7 +21,7 @@ def testMagnifierCreation(self): self.assertEqual(magnifier.zoomLevel, 200) self.assertEqual(magnifier.filterType, Filter.NORMAL) - self.assertEqual(magnifier._trackingMode, FullScreenTrackingMode.CENTER) + self.assertEqual(magnifier._fullscreenMode, FullScreenMode.CENTER) self.assertEqual(magnifier._MAGNIFIED_VIEW, MagnifiedView.FULLSCREEN) self.assertTrue(magnifier._isActive) @@ -171,7 +171,7 @@ def testMagnifierInheritance(self): self.assertTrue(hasattr(magnifier, "zoomLevel")) self.assertTrue(hasattr(magnifier, "filterType")) self.assertTrue(hasattr(magnifier, "_MAGNIFIED_VIEW")) - self.assertTrue(hasattr(magnifier, "_trackingMode")) + self.assertTrue(hasattr(magnifier, "_fullscreenMode")) self.assertTrue(hasattr(magnifier, "_isActive")) self.assertTrue(hasattr(magnifier, "_currentCoordinates")) @@ -213,8 +213,8 @@ def testMagnifierSimpleLifecycle(self): self.assertEqual(magnifier._currentCoordinates, (200, 300)) # Change mode - magnifier._trackingMode = FullScreenTrackingMode.RELATIVE - self.assertEqual(magnifier._trackingMode, FullScreenTrackingMode.RELATIVE) + magnifier._fullscreenMode = FullScreenMode.RELATIVE + self.assertEqual(magnifier._fullscreenMode, FullScreenMode.RELATIVE) # Change filter magnifier.filterType = Filter.INVERTED @@ -227,7 +227,7 @@ def testMagnifierSimpleLifecycle(self): def testAttemptRecoverySuccess(self): """FullScreenMagnifier._attemptRecovery reinitialises API and restarts timer on success.""" with patch( - "_magnifier.magnifier.TrackingManager.getCurrentTrackedCoordinates", + "_magnifier.magnifier.FocusManager.getCurrentFocusCoordinates", return_value=Coordinates(0, 0), ): magnifier = FullScreenMagnifier() @@ -267,7 +267,7 @@ def testUpdateLoopSurvivesSingleDoUpdateError(self): magnifier = FullScreenMagnifier() magnifier._startMagnifier() magnifier._startTimer = MagicMock() - magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=(100, 200), ) diff --git a/tests/unit/test_magnifier/test_magnifier.py b/tests/unit/test_magnifier/test_magnifier.py index 3d00134cb61..7a26dd883c3 100644 --- a/tests/unit/test_magnifier/test_magnifier.py +++ b/tests/unit/test_magnifier/test_magnifier.py @@ -69,7 +69,7 @@ def testMagnifierCreation(self): self.assertEqual(self.magnifier.zoomLevel, 200) self.assertEqual(self.magnifier._filterType, Filter.NORMAL) self.assertFalse(self.magnifier._isActive) - self.assertIsNotNone(self.magnifier._trackingManager) + self.assertIsNotNone(self.magnifier._focusManager) self.assertEqual(self.magnifier._consecutiveErrors, 0) def testZoomLevelProperty(self): @@ -99,7 +99,7 @@ def testStartMagnifier(self): """Activating the magnifier.""" # Use center coordinates which will always be within bounds focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) @@ -109,20 +109,20 @@ def testStartMagnifier(self): self.assertTrue(self.magnifier._isActive) self.assertEqual(self.magnifier.currentCoordinates, focusCoords) - self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_called_once() + self.magnifier._focusManager.getCurrentFocusCoordinates.assert_called_once() - # Test starting when already active (should not call getCurrentTrackedCoordinates again) - self.magnifier._trackingManager.getCurrentTrackedCoordinates.reset_mock() + # Test starting when already active (should not call getCurrentFocusCoordinates again) + self.magnifier._focusManager.getCurrentFocusCoordinates.reset_mock() self.magnifier._startMagnifier() self.assertTrue(self.magnifier._isActive) - self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_not_called() + self.magnifier._focusManager.getCurrentFocusCoordinates.assert_not_called() def testUpdateMagnifier(self): """Updating the magnifier's properties.""" # Use center coordinates which will always be within bounds focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock() @@ -130,7 +130,7 @@ def testUpdateMagnifier(self): # Call the update function without activation self.magnifier._updateMagnifier() - self.magnifier._trackingManager.getCurrentTrackedCoordinates.assert_not_called() + self.magnifier._focusManager.getCurrentFocusCoordinates.assert_not_called() self.magnifier._doUpdate.assert_not_called() self.magnifier._startTimer.assert_not_called() @@ -138,9 +138,9 @@ def testUpdateMagnifier(self): self.magnifier._isActive = True self.magnifier._updateMagnifier() - # getCurrentTrackedCoordinates is called twice: once in _managePanning and once to update currentCoordinates + # getCurrentFocusCoordinates is called twice: once in _managePanning and once to update currentCoordinates self.assertEqual( - self.magnifier._trackingManager.getCurrentTrackedCoordinates.call_count, + self.magnifier._focusManager.getCurrentFocusCoordinates.call_count, 2, ) self.magnifier._doUpdate.assert_called_once() @@ -155,7 +155,7 @@ def testUpdateMagnifierResumesAfterSingleError(self): """Timer must always be rescheduled even when _doUpdate raises an exception.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("COM failure")) @@ -173,7 +173,7 @@ def testUpdateMagnifierTriggersRecoveryAfterMaxErrors(self): """After _MAX_CONSECUTIVE_ERRORS failures, _attemptRecovery is called instead of restarting timer.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("COM failure")) @@ -192,7 +192,7 @@ def testUpdateMagnifierCatchesCOMError(self): """COMError from UIA must be caught and the timer rescheduled.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=COMError(-2147417848, "RPC_E_DISCONNECTED", None)) @@ -207,7 +207,7 @@ def testUpdateMagnifierRecoveryFailureSafelyRestartsTimer(self): """If _attemptRecovery itself raises, the timer must still be restarted to prevent a freeze.""" self.magnifier._isActive = True focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock(side_effect=OSError("API failure")) @@ -226,7 +226,7 @@ def testUpdateMagnifierResetsErrorCountOnSuccess(self): self.magnifier._isActive = True self.magnifier._consecutiveErrors = 2 focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock( return_value=focusCoords, ) self.magnifier._doUpdate = MagicMock() # Success @@ -453,7 +453,7 @@ def testManagePanning(self): focusA = Coordinates(100, 200) focusB = Coordinates(300, 400) - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock(return_value=focusA) + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusA) # When not panning, _lastFocusCoordinates is updated every cycle self.magnifier._isManualPanning = False @@ -468,7 +468,7 @@ def testManagePanning(self): self.assertEqual(self.magnifier._lastFocusCoordinates, focusA) # When focus changes while panning, manual panning ends - self.magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock(return_value=focusB) + self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusB) self.magnifier._managePanning() self.assertFalse(self.magnifier._isManualPanning) self.assertEqual(self.magnifier._lastFocusCoordinates, focusB) diff --git a/tests/unit/test_magnifier/test_spotlightManager.py b/tests/unit/test_magnifier/test_spotlightManager.py index 77e02697d7b..e62fe60f205 100644 --- a/tests/unit/test_magnifier/test_spotlightManager.py +++ b/tests/unit/test_magnifier/test_spotlightManager.py @@ -4,7 +4,7 @@ # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt from unittest.mock import MagicMock, patch -from _magnifier.utils.types import FullScreenTrackingMode, Coordinates +from _magnifier.utils.types import FullScreenMode, Coordinates from _magnifier.fullscreenMagnifier import FullScreenMagnifier from tests.unit.test_magnifier.test_magnifier import _TestMagnifier @@ -31,9 +31,7 @@ def testSpotlightActivation(self): spotlightManager = magnifier._spotlightManager # Mock required methods - magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( - return_value=Coordinates(500, 400), - ) + magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) magnifier._getCoordinatesForMode = MagicMock(return_value=Coordinates(500, 400)) spotlightManager._animateZoom = MagicMock() @@ -178,10 +176,8 @@ def testZoomBack(self): # Set original zoom level spotlightManager._originalZoomLevel = 3.0 - # Mock getCurrentTrackedCoordinates to return expected position - magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( - return_value=Coordinates(500, 400), - ) + # Mock getCurrentFocusCoordinates to return expected position + magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) spotlightManager._animateZoom = MagicMock() # Trigger zoom back @@ -198,7 +194,7 @@ def testZoomBack(self): def testZoomBackRelativeMode(self): """Test zoom back in RELATIVE mode.""" magnifier = FullScreenMagnifier() - magnifier._trackingMode = FullScreenTrackingMode.RELATIVE + magnifier._fullscreenMode = FullScreenMode.RELATIVE spotlightManager = magnifier._spotlightManager # Set original zoom level @@ -214,7 +210,7 @@ def testZoomBackRelativeMode(self): spotlightManager.zoomBack() # Should use _getCoordinatesForMode for RELATIVE mode - # Note: The code has a bug checking magnifier.FullScreenTrackingMode instead of magnifier._trackingMode + # Note: The code has a bug checking magnifier.FullScreenMode instead of magnifier._fullscreenMode # But we test the current behavior spotlightManager._animateZoom.assert_called_once() @@ -230,9 +226,7 @@ def testSpotlightFullLifecycle(self): self.assertEqual(spotlightManager._originalZoomLevel, 0.0) # Mock methods for full test - magnifier._trackingManager.getCurrentTrackedCoordinates = MagicMock( - return_value=Coordinates(500, 400), - ) + magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=Coordinates(500, 400)) magnifier._getCoordinatesForMode = MagicMock(return_value=Coordinates(500, 400)) magnifier._stopSpotlight = MagicMock()