diff --git a/source/gui/nvdaControls.py b/source/gui/nvdaControls.py index b3ae5f682c2..4bb34341927 100644 --- a/source/gui/nvdaControls.py +++ b/source/gui/nvdaControls.py @@ -1,21 +1,20 @@ # -*- coding: UTF-8 -*- -#A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2016-2018 NV Access Limited, Derek Riemer -#This file is covered by the GNU General Public License. -#See the file COPYING for more details. +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2016-2021 NV Access Limited, Derek Riemer +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. -from ctypes.wintypes import BOOL -from typing import Any, Tuple, Optional import wx -from comtypes import GUID +from wx.lib import scrolledpanel from wx.lib.mixins import listctrl as listmix from .dpiScalingHelper import DpiScalingHelperMixin from . import guiHelper -import oleacc import winUser import winsound + from collections.abc import Callable + class AutoWidthColumnListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): """ A list control that allows you to specify a column to resize to take up the remaining width of a wx.ListCtrl. @@ -355,3 +354,40 @@ def onSliderChar(self, evt): evt.Skip() return self.SetValue(newValue) + + +class TabbableScrolledPanel(scrolledpanel.ScrolledPanel): + """ + This class was created to ensure a ScrolledPanel scrolls to nested children of the panel when navigating + with tabs (#12224). A PR to wxPython implementing this fix can be tracked on + https://github.com/wxWidgets/Phoenix/pull/1950 + """ + def GetChildRectRelativeToSelf(self, child: wx.Window) -> wx.Rect: + """ + window.GetRect returns the size of a window, and its position relative to its parent. + When calculating ScrollChildIntoView, the position relative to its parent is not relevant unless the + parent is the ScrolledPanel itself. Instead, calculate the position relative to scrolledPanel + """ + childRectRelativeToScreen = child.GetScreenRect() + scrolledPanelScreenPosition = self.GetScreenPosition() + return wx.Rect( + childRectRelativeToScreen.x - scrolledPanelScreenPosition.x, + childRectRelativeToScreen.y - scrolledPanelScreenPosition.y, + childRectRelativeToScreen.width, + childRectRelativeToScreen.height + ) + + def ScrollChildIntoView(self, child: wx.Window) -> None: + """ + Overrides child.GetRect with `GetChildRectRelativeToSelf` before calling + `super().ScrollChildIntoView`. `super().ScrollChildIntoView` incorrectly uses child.GetRect to + navigate scrolling, which is relative to the parent, where it should instead be relative to this + ScrolledPanel. + """ + oldChildGetRectFunction = child.GetRect + child.GetRect = lambda: self.GetChildRectRelativeToSelf(child) + try: + super().ScrollChildIntoView(child) + finally: + # ensure child.GetRect is reset properly even if super().ScrollChildIntoView throws an exception + child.GetRect = oldChildGetRectFunction diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 36c51cd7803..9863cb6a5cd 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -14,7 +14,6 @@ import typing import wx from vision.providerBase import VisionEnhancementProviderSettings -from wx.lib import scrolledpanel from wx.lib.expando import ExpandoTextCtrl import wx.lib.newevent import winUser @@ -483,7 +482,7 @@ def makeSettings(self, settingsSizer): # The provided column header is just a placeholder, as it is hidden due to the wx.LC_NO_HEADER style flag. self.catListCtrl.InsertColumn(0,categoriesLabelText) - self.container = scrolledpanel.ScrolledPanel( + self.container = nvdaControls.TabbableScrolledPanel( parent = self, style = wx.TAB_TRAVERSAL | wx.BORDER_THEME, size=containerDim