diff --git a/source/_magnifier/commands.py b/source/_magnifier/commands.py index 11bb29ea7ab..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 focusType: 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(focusType) - setFollowState(focusType, 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=focusType.displayString, + 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 toggleFullscreenMode() -> None: - """Cycle through full-screen focus 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 toggleFullscreenMode() -> 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 51f2c8a4d9d..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,33 +179,33 @@ def _ensureSavedStatesInitialized() -> None: saveFollowStates() -def getFollowState(focusType: MagnifierTrackingType) -> bool: +def getTrackingState(trackingType: MagnifierTrackingType) -> bool: """ - Get the current follow state for a given focus type. + Get the current follow state for a given tracking type. - :param focusType: 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[focusType]] + return config.conf["magnifier"][_FOLLOW_CONFIG_KEYS[trackingType]] -def setFollowState(focusType: MagnifierTrackingType, state: bool) -> None: +def setTrackingState(trackingType: MagnifierTrackingType, state: bool) -> None: """ - Set the follow state for a given focus type. + Set the follow state for a given tracking type. - :param focusType: 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[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] = getTrackingState(trackingType) -def toggleAllFollowStates() -> bool: +def toggleAllTrackingStates() -> bool: """ Toggle all follow states between forced-disabled and previously saved states. @@ -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(): + setTrackingState(trackingType, state) _followStateOverride.isActive = False else: saveFollowStates() - for focusType in _FOLLOW_CONFIG_KEYS: - setFollowState(focusType, False) + for trackingType in _FOLLOW_CONFIG_KEYS: + 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 791b97435c1..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: 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: 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 focus mode - center mouse/focus on screen. + # Translators: Magnifier tracking mode - center the tracked item on screen self.CENTER: pgettext("magnifier", "Center"), - # Translators: Magnifier focus mode - follow focus at screen borders. + # Translators: Magnifier tracking mode - follow the tracked item only when it reaches screen borders self.BORDER: pgettext("magnifier", "Border"), - # Translators: Magnifier focus 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 41c601e5ef6..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.toggleFullscreenMode() + _magnifier.commands.cycleFullscreenTrackingMode() @script( description=_( diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 8a8e01e0e20..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 @@ -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"]["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() @@ -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 = _("Focus &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 951f30e5e2e..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. @@ -2934,6 +2934,9 @@ When enabled, the magnifier will always keep the tracked position centered on th This option allows you to set the panning step size as a percentage of the visible magnified window. This means that when you use manual pan commands, the magnified view will move by the specified percentage of the current visible window size. 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 tracking mode. | . {.hideHeaderRow} |.| |---|---| @@ -2982,8 +2985,8 @@ When enabled, the magnified area will automatically move to follow the navigator ##### Tracking mode {#MagnifierTrackingMode} -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} |.| @@ -2995,7 +2998,7 @@ The available options are: |---|---| | Center | The magnified area is always centered on the currently tracked position. | | Border | The magnified area only moves when the tracked element approaches the edge of the visible area. | -| Relative | The magnified area maintains the relative position of the tracked element within the screen. | +| Relative | The magnified area maintains the relative position of the tracked element based on its position on the screen. | #### Keyboard {#KeyboardSettings}