diff --git a/addon/globalPlugins/webAccess/__init__.py b/addon/globalPlugins/webAccess/__init__.py index 6855822b..dbb0b454 100644 --- a/addon/globalPlugins/webAccess/__init__.py +++ b/addon/globalPlugins/webAccess/__init__.py @@ -65,7 +65,6 @@ import addonHandler import api import baseObject -from buildVersion import version_detailed as NVDA_VERSION import controlTypes import core import eventHandler @@ -285,9 +284,9 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): if cls in clsList ): if obj.role in ( - controlTypes.ROLE_APPLICATION, - controlTypes.ROLE_DIALOG, - controlTypes.ROLE_DOCUMENT, + controlTypes.Role.APPLICATION, + controlTypes.Role.DIALOG, + controlTypes.Role.DOCUMENT, ): clsList.insert(0, overlay.WebAccessDocument) return @@ -296,7 +295,7 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): from .gui import inspector if ( inspector.outputWindowHandle is not None - and obj.role == controlTypes.ROLE_EDITABLETEXT + and obj.role == controlTypes.Role.EDITABLETEXT and obj.windowHandle == inspector.outputWindowHandle and obj.appModule.appName == "nvda" ): @@ -356,12 +355,7 @@ def script_showWebAccessSettings(self, gesture): # @UnusedVariable # After the above import, it appears that the `gui` name now points to the `.gui` module # rather than NVDA's `gui`… No clue why… import gui - if NVDA_VERSION < "2023.2": - # Deprecated as of NVDA 2023.2 - gui.mainFrame._popupSettingsDialog(WebAccessSettingsDialog) - else: - # Now part of the public API as of NVDA PR #15121 - gui.mainFrame.popupSettingsDialog(WebAccessSettingsDialog) + gui.mainFrame.popupSettingsDialog(WebAccessSettingsDialog) @script( # Translators: Input help mode message for a WebAccess command @@ -501,7 +495,7 @@ def appModule_nvda_event_NVDAObject_init(self, obj): "popupContextMenuName" in globals() and popupContextMenuName is not None and isinstance(obj, IAccessible) - and obj.role == controlTypes.ROLE_POPUPMENU + and obj.role == controlTypes.Role.POPUPMENU ): obj.name = popupContextMenuName popupContextMenuName = None diff --git a/addon/globalPlugins/webAccess/gui/__init__.py b/addon/globalPlugins/webAccess/gui/__init__.py index 2a67a897..f57f8c5a 100644 --- a/addon/globalPlugins/webAccess/gui/__init__.py +++ b/addon/globalPlugins/webAccess/gui/__init__.py @@ -28,15 +28,14 @@ from abc import abstractmethod -from buildVersion import version_detailed as NVDA_VERSION from collections import OrderedDict from dataclasses import dataclass from enum import Enum, auto import re -import sys from typing import Any, Callable import wx import wx.lib.mixins.listctrl as listmix +from wx.lib.scrolledpanel import ScrolledPanel import gui from gui import guiHelper, nvdaControls @@ -56,11 +55,11 @@ from ..utils import guarded, logException, notifyError, updateOrDrop +# TabbableScrolledPanel was removed in NVDA 2026.1 (wxPython 4.2.3 update). +# Fall back to wx.lib.scrolledpanel.ScrolledPanel which now has the fix built-in. +_TabbableScrolledPanel = getattr(nvdaControls, "TabbableScrolledPanel", ScrolledPanel) -if sys.version_info[1] < 9: - from typing import Iterable, Mapping, Sequence, Set -else: - from collections.abc import Iterable, Mapping, Sequence, Set +from collections.abc import Iterable, Mapping, Sequence, Set addonHandler.initTranslation() @@ -251,7 +250,7 @@ def _buildGui(self): self.SetSizer(self.mainSizer) -# TODO: Consider migrating to NVDA's SettingsDialog once we hit 2023.2 as minimum version +# TODO: Consider migrating to NVDA's SettingsDialog class ContextualDialog(ScalingMixin, wx.Dialog): def initData(self, context): @@ -367,12 +366,9 @@ def configuredSettingsDialogType(hasApplyButton: bool) -> type(SettingsDialog): class Type(SettingsDialog): def __init__(self, *args, **kwargs): - if NVDA_VERSION < "2023.2": - kwargs["hasApplyButton"] = hasApplyButton - else: - buttons: Set[int] = kwargs.get("buttons", {wx.OK, wx.CANCEL}) - if not hasApplyButton: - buttons -= {wx.APPLY} + buttons: Set[int] = kwargs.get("buttons", {wx.OK, wx.CANCEL}) + if not hasApplyButton: + buttons -= {wx.APPLY} super().__init__(*args, **kwargs) return Type @@ -572,7 +568,7 @@ def makeSettings(self, settingsSizer): style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT ) - self.container = nvdaControls.TabbableScrolledPanel( + self.container = _TabbableScrolledPanel( parent = self, style = wx.TAB_TRAVERSAL | wx.BORDER_THEME, size=containerDim diff --git a/addon/globalPlugins/webAccess/gui/inspector.py b/addon/globalPlugins/webAccess/gui/inspector.py index 0c26067b..b40bed00 100644 --- a/addon/globalPlugins/webAccess/gui/inspector.py +++ b/addon/globalPlugins/webAccess/gui/inspector.py @@ -177,11 +177,11 @@ def getNodeDescription(node, root=None, identifier=None): parts.append(f"{labels['text']} {getText(node)}") else: parts.append(f"{labels['tag']} {node.tag}") - parts.append(f"{labels['role']} {controlTypes.roleLabels[node.role]}") + parts.append(f"{labels['role']} {node.role.displayString}") parts.append(f"{labels['id']} {coalesce(node.id, '')}") parts.append(f"{labels['className']} {coalesce(node.className, '')}") states = ", ".join(sorted(( - controlTypes.stateLabels.get(state, state) + state.displayString if isinstance(state, controlTypes.State) else str(state) for state in node.states ))) parts.append(f"{labels['states']} {states}") @@ -270,7 +270,7 @@ def computeRelativePath(node1, node2): if index1 < index2: link = "r" * (index2 - index1) else: - link = "l" * (index1 - index2) + link = "l" * (index1 - index2) return path1 + link + path2 diff --git a/addon/globalPlugins/webAccess/gui/rule/__init__.py b/addon/globalPlugins/webAccess/gui/rule/__init__.py index 37fe9e81..a4a94805 100644 --- a/addon/globalPlugins/webAccess/gui/rule/__init__.py +++ b/addon/globalPlugins/webAccess/gui/rule/__init__.py @@ -23,7 +23,6 @@ __author__ = "Julien Cochuyt " -import sys from typing import Any import wx @@ -37,11 +36,7 @@ from ...utils import updateOrDrop from .properties import Properties - -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py b/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py index 2e96a6b5..e95f90ec 100644 --- a/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py +++ b/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py @@ -33,7 +33,6 @@ from collections import OrderedDict from copy import deepcopy import re -import sys from typing import Any import wx from wx.lib.expando import EVT_ETC_LAYOUT_NEEDED, ExpandoTextCtrl @@ -66,16 +65,11 @@ from .gestures import GesturesPanelBase from .properties import Properties, PropertiesPanelBase, Property - -if sys.version_info[1] < 9: - from typing import Mapping, Sequence -else: - from collections.abc import Mapping, Sequence +from collections.abc import Mapping, Sequence addonHandler.initTranslation() -from six import iteritems, text_type EXPR_VALUE = re.compile("(([^!&| ])+( (?=[^!&|]))*)+") """ @@ -104,7 +98,7 @@ def captureValues(expr): def getStatesLblExprForSet(states): return " & ".join(( - controlTypes.stateLabels.get(state, state) + state.displayString if isinstance(state, controlTypes.State) else str(state) for state in states )) @@ -113,7 +107,7 @@ def translateExprValues(expr, func): buf = list(expr) offset = 0 for src, start, end in captureValues(expr): - dest = text_type(func(src)) + dest = str(func(src)) start += offset end += offset buf[start:end] = dest @@ -124,7 +118,7 @@ def translateExprValues(expr, func): def translateRoleIdToLbl(expr): def translate(value): try: - return controlTypes.roleLabels[int(value)] + return controlTypes.Role(int(value)).displayString except (KeyError, ValueError): return value return translateExprValues(expr, translate) @@ -132,9 +126,9 @@ def translate(value): def translateRoleLblToId(expr, raiseOnError=True): def translate(value): - for key, candidate in iteritems(controlTypes.roleLabels): - if candidate == value: - return text_type(key.value) + for role in controlTypes.Role: + if role.displayString == value: + return str(role.value) if raiseOnError: raise ValidationError(value) return value @@ -144,7 +138,7 @@ def translate(value): def translateStatesIdToLbl(expr): def translate(value): try: - return controlTypes.stateLabels[int(value)] + return controlTypes.State(int(value)).displayString except (KeyError, ValueError): return value return translateExprValues(expr, translate) @@ -152,9 +146,9 @@ def translate(value): def translateStatesLblToId(expr, raiseOnError=True): def translate(value): - for key, candidate in iteritems(controlTypes.stateLabels): - if candidate == value: - return text_type(key.value) + for state in controlTypes.State: + if state.displayString == value: + return str(state.value) if raiseOnError: raise ValidationError(value) return value @@ -814,7 +808,7 @@ def initData_others(self, context): urlChoices = [] # todo: actually there are empty choices created while node is not None: - roleChoices.append(controlTypes.roleLabels.get(node.role, "") or "") + roleChoices.append(node.role.displayString if node.role else "") tagChoices.append(node.tag or "") idChoices.append(node.id or "") classChoices.append(node.className or "") @@ -952,7 +946,7 @@ def onTestCriteria(self, evt): testCriteria(self.context) def isValid(self): - self.updateData() + self.updateData() return self.isValid_context() and self.isValid_others() def isValid_context(self): diff --git a/addon/globalPlugins/webAccess/gui/rule/editor.py b/addon/globalPlugins/webAccess/gui/rule/editor.py index c97a942f..ca536444 100644 --- a/addon/globalPlugins/webAccess/gui/rule/editor.py +++ b/addon/globalPlugins/webAccess/gui/rule/editor.py @@ -35,7 +35,6 @@ from dataclasses import dataclass from enum import Enum from functools import partial -import sys from typing import Any import wx from wx.lib.expando import EVT_ETC_LAYOUT_NEEDED, ExpandoTextCtrl @@ -80,18 +79,14 @@ SinglePropertyEditorPanelBase, ) - -if sys.version_info[1] < 9: - from typing import Mapping, Sequence -else: - from collections.abc import Mapping, Sequence +from collections.abc import Mapping, Sequence addonHandler.initTranslation() formModeRoles = [ - controlTypes.ROLE_EDITABLETEXT, - controlTypes.ROLE_COMBOBOX, + controlTypes.Role.EDITABLETEXT, + controlTypes.Role.COMBOBOX, ] SHARED_LABELS: Mapping[str, str] = { @@ -632,7 +627,7 @@ def delete(self): def onGestureChange(self, change: Change, id: str): super().onGestureChange(change, id) prm = self.categoryParams - self.refreshParent(prm.treeNode) + self.refreshParent(prm.treeNode) def spaceIsPressedOnTreeNode(self, withShift=False): self.gesturesListBox.SetFocus() @@ -753,6 +748,7 @@ def makeSettings_buttons(self, gbSizer, row, col, full=True): row += 1 gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS), pos=(row, col)) + def spaceIsPressedOnTreeNode(self, withShift=False): diff --git a/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py b/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py index 3b74c02b..1f42b8a7 100644 --- a/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py +++ b/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py @@ -30,7 +30,6 @@ ) -import sys from typing import Any import wx @@ -45,11 +44,7 @@ from ...utils import guarded, logException from .. import ScalingMixin, showContextualDialog - -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/rule/gestures.py b/addon/globalPlugins/webAccess/gui/rule/gestures.py index 557cba16..de7c3fa5 100644 --- a/addon/globalPlugins/webAccess/gui/rule/gestures.py +++ b/addon/globalPlugins/webAccess/gui/rule/gestures.py @@ -29,7 +29,6 @@ ) -import sys from typing import Any import wx @@ -44,11 +43,7 @@ from . import gestureBinding from .abc import RuleAwarePanelBase - -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/rule/manager.py b/addon/globalPlugins/webAccess/gui/rule/manager.py index 35b249cf..78dbf120 100644 --- a/addon/globalPlugins/webAccess/gui/rule/manager.py +++ b/addon/globalPlugins/webAccess/gui/rule/manager.py @@ -30,7 +30,6 @@ from collections import namedtuple -import sys import wx import addonHandler @@ -47,18 +46,7 @@ from .. import ContextualDialog, showContextualDialog, stripAccel from .editor import getSummary - -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping - - -try: - from six import iteritems -except ImportError: - # NVDA version < 2018.3 - iteritems = dict.iteritems +from collections.abc import Mapping addonHandler.initTranslation() @@ -345,7 +333,7 @@ def getRulesByType(ruleManager, filter=None, active=False): obj=rule, children=[] )) - for ruleType, label in iteritems(ruleTypes.ruleTypeLabels): + for ruleType, label in ruleTypes.ruleTypeLabels.items(): try: tids = types[ruleType] except KeyError: @@ -693,7 +681,7 @@ def onCharHook(self, evt: wx.KeyEvent): self.tree.SetFocus() return elif keyCode == wx.WXK_RETURN and mods == wx.MOD_NONE: - # filterEdit is handled separately (TE_PROCESS_ENTER) + # filterEdit is handled separately (TE_PROCESS_ENTER) for ctrl in (self.groupByRadio, self.activeOnlyCheckBox): if ctrl.HasFocus(): self.tree.SetFocus() diff --git a/addon/globalPlugins/webAccess/gui/rule/properties.py b/addon/globalPlugins/webAccess/gui/rule/properties.py index 93dae00e..a13676bc 100644 --- a/addon/globalPlugins/webAccess/gui/rule/properties.py +++ b/addon/globalPlugins/webAccess/gui/rule/properties.py @@ -31,7 +31,6 @@ from collections import ChainMap from abc import abstractmethod from enum import Enum -import sys from typing import Any import wx @@ -46,11 +45,7 @@ from ...utils import guarded, logException, updateOrDrop from .. import ContextualSettingsPanel, EditorType, ListCtrlAutoWidth, SingleFieldEditorMixin - -if sys.version_info[1] < 9: - from typing import Iterator, Mapping -else: - from collections.abc import Iterator, Mapping +from collections.abc import Iterator, Mapping addonHandler.initTranslation() @@ -552,7 +547,6 @@ def onEditor_charHook(self, evt): self.prop_reset() return evt.Skip() - @guarded def onListCtrl_charHook(self, evt): diff --git a/addon/globalPlugins/webAccess/gui/webModule/__init__.py b/addon/globalPlugins/webAccess/gui/webModule/__init__.py index 5e1a81d7..9f60f978 100644 --- a/addon/globalPlugins/webAccess/gui/webModule/__init__.py +++ b/addon/globalPlugins/webAccess/gui/webModule/__init__.py @@ -23,9 +23,6 @@ __author__ = "Julien Cochuyt " -import sys -from typing import Any - import os import wx @@ -36,12 +33,6 @@ from ...webModuleHandler import WebModule -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping - - addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/webModule/editor.py b/addon/globalPlugins/webAccess/gui/webModule/editor.py index a2c0ff54..ba70e973 100644 --- a/addon/globalPlugins/webAccess/gui/webModule/editor.py +++ b/addon/globalPlugins/webAccess/gui/webModule/editor.py @@ -31,7 +31,6 @@ import itertools import os -import sys from typing import Any import wx @@ -49,11 +48,7 @@ from ...webModuleHandler import WebModule, getEditableWebModule, getUrl, getWindowTitle, save, store from .. import ContextualDialog, showContextualDialog - -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/webModule/manager.py b/addon/globalPlugins/webAccess/gui/webModule/manager.py index 8c682952..269dede3 100644 --- a/addon/globalPlugins/webAccess/gui/webModule/manager.py +++ b/addon/globalPlugins/webAccess/gui/webModule/manager.py @@ -162,7 +162,6 @@ def onCharHook(self, evt): self.onRulesManager(None) return evt.Skip() - @guarded def onModuleCreate(self, evt=None): diff --git a/addon/globalPlugins/webAccess/nodeHandler.py b/addon/globalPlugins/webAccess/nodeHandler.py index 78989966..10cfcd64 100644 --- a/addon/globalPlugins/webAccess/nodeHandler.py +++ b/addon/globalPlugins/webAccess/nodeHandler.py @@ -30,7 +30,6 @@ import gc import re -import sys import time import weakref from ast import literal_eval @@ -40,7 +39,7 @@ import controlTypes from logHandler import log import mouseHandler -import NVDAHelper +from NVDAHelper.localLib import VBuf_getTextInRange import textInfos import treeInterceptorHandler import ui @@ -51,10 +50,7 @@ from .webAppLib import * -if sys.version_info[1] < 9: - from typing import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping TRACE = lambda *args, **kwargs: None # noqa: E731 @@ -285,7 +281,7 @@ def update(self, force=False, ruleManager=None, debug=False): if debug: log.info("The VirtualBuffer is empty") return False - text = NVDAHelper.VBuf_getTextInRange( + text = VBuf_getTextInRange( info.obj.VBufHandle, start, end, True) if self.mainNode is not None: self.mainNode.recursiveDelete() @@ -397,7 +393,7 @@ def walk(node): prev = map[controlId] if prev[1] == span[0]: # Consecutive spans for the same control. Expand the recorded span. - span = (prev[0], span[1]) + span = (prev[0], span[1]) elif not(prev[0] <= span[0] and span[1] <= prev[1]): # Neither consecutive nor nested log.warning(f"ControlId double: {controlId} at {prev} and {span} (role: {node.role})") @@ -521,7 +517,7 @@ def previousTextNode(self): def url(self): if hasattr(self, "_url"): return self._url - if self.role != controlTypes.ROLE_DOCUMENT: + if self.role != controlTypes.Role.DOCUMENT: return None url = None obj = self.getNVDAObject() @@ -918,7 +914,7 @@ def mouseMove(self): # if self.offset < node.offset: # return True # return False -# +# # def __le__(self, node): # """ # Compare nodes based on their offset. @@ -926,7 +922,7 @@ def mouseMove(self): # if self.offset <= node.offset: # return True # return False -# +# # def __gt__(self, node): # """ # Compare nodes based on their offset. @@ -934,7 +930,7 @@ def mouseMove(self): # if self.offset > node.offset: # return True # return False -# +# # def __ge__(self, node): # """ # Compare nodes based on their offset. diff --git a/addon/globalPlugins/webAccess/overlay.py b/addon/globalPlugins/webAccess/overlay.py index 77fd0423..2cc668e4 100644 --- a/addon/globalPlugins/webAccess/overlay.py +++ b/addon/globalPlugins/webAccess/overlay.py @@ -630,30 +630,30 @@ def __getCriteriaForMutatedControlType(self, itemType): if itemType == "annotation": attrs = { "IAccessible::role": [ - controlTypes.ROLE_DELETED_CONTENT, - controlTypes.ROLE_INSERTED_CONTENT + controlTypes.Role.DELETED_CONTENT, + controlTypes.Role.INSERTED_CONTENT ] } elif itemType == "blockQuote": attrs = { - "role": [controlTypes.ROLE_BLOCKQUOTE] + "role": [controlTypes.Role.BLOCKQUOTE] } elif itemType == "button": - attrs = {"role": [controlTypes.ROLE_BUTTON]} + attrs = {"role": [controlTypes.Role.BUTTON]} elif itemType == "checkBox": - attrs = {"role": [controlTypes.ROLE_CHECKBOX]} + attrs = {"role": [controlTypes.Role.CHECKBOX]} elif itemType == "comboBox": - attrs = {"role": [controlTypes.ROLE_COMBOBOX]} + attrs = {"role": [controlTypes.Role.COMBOBOX]} elif itemType == "edit": attrs = [ { - "role": [controlTypes.ROLE_EDITABLETEXT], - "states": [set((controlTypes.STATE_EDITABLE,))] + "role": [controlTypes.Role.EDITABLETEXT], + "states": [set((controlTypes.State.EDITABLE,))] }, { - "states": [set((controlTypes.STATE_EDITABLE,))], + "states": [set((controlTypes.State.EDITABLE,))], "parent::states::not": [ - set((controlTypes.STATE_EDITABLE,)) + set((controlTypes.State.EDITABLE,)) ] }, ] @@ -664,50 +664,50 @@ def __getCriteriaForMutatedControlType(self, itemType): }, { "role": [ - controlTypes.ROLE_APPLICATION, - controlTypes.ROLE_DIALOG + controlTypes.Role.APPLICATION, + controlTypes.Role.DIALOG ] } ] elif itemType == "frame": - attrs = {"role": [controlTypes.ROLE_INTERNALFRAME]} + attrs = {"role": [controlTypes.Role.INTERNALFRAME]} elif itemType == "focusable": attrs = { - "states": [set((controlTypes.STATE_FOCUSABLE,))] + "states": [set((controlTypes.State.FOCUSABLE,))] } elif itemType == "formField": attrs = [ { "role": [ - controlTypes.ROLE_BUTTON, - controlTypes.ROLE_CHECKBOX, - controlTypes.ROLE_COMBOBOX, - controlTypes.ROLE_LIST, - controlTypes.ROLE_MENUBUTTON, - controlTypes.ROLE_RADIOBUTTON, - controlTypes.ROLE_TOGGLEBUTTON, - controlTypes.ROLE_TREEVIEW, + controlTypes.Role.BUTTON, + controlTypes.Role.CHECKBOX, + controlTypes.Role.COMBOBOX, + controlTypes.Role.LIST, + controlTypes.Role.MENUBUTTON, + controlTypes.Role.RADIOBUTTON, + controlTypes.Role.TOGGLEBUTTON, + controlTypes.Role.TREEVIEW, ], - "states::not": [set((controlTypes.STATE_READONLY,))] + "states::not": [set((controlTypes.State.READONLY,))] }, { "role": [ - controlTypes.ROLE_COMBOBOX, - controlTypes.ROLE_EDITABLETEXT + controlTypes.Role.COMBOBOX, + controlTypes.Role.EDITABLETEXT ], - "states": [set((controlTypes.STATE_EDITABLE,))] + "states": [set((controlTypes.State.EDITABLE,))] }, { - "states": [set((controlTypes.STATE_EDITABLE,))], + "states": [set((controlTypes.State.EDITABLE,))], "parent::states::not": [ - set((controlTypes.STATE_EDITABLE,)) + set((controlTypes.State.EDITABLE,)) ] }, ] elif itemType == "graphic": - attrs = {"role": [controlTypes.ROLE_GRAPHIC]} + attrs = {"role": [controlTypes.Role.GRAPHIC]} elif itemType.startswith('heading'): - attrs = {"role": [controlTypes.ROLE_HEADING]} + attrs = {"role": [controlTypes.Role.HEADING]} if itemType[7:].isdigit(): # "level" is int in position info, # but text in control field attributes... @@ -715,17 +715,17 @@ def __getCriteriaForMutatedControlType(self, itemType): elif itemType == "landmark": attrs = {"landmark": [True]} elif itemType == "link": - attrs = {"role": [controlTypes.ROLE_LINK]} + attrs = {"role": [controlTypes.Role.LINK]} elif itemType == "list": - attrs = {"role": [controlTypes.ROLE_LIST]} + attrs = {"role": [controlTypes.Role.LIST]} elif itemType == "listItem": - attrs = {"role": [controlTypes.ROLE_LISTITEM]} + attrs = {"role": [controlTypes.Role.LISTITEM]} elif itemType == "radioButton": - attrs = {"role": [controlTypes.ROLE_RADIOBUTTON]} + attrs = {"role": [controlTypes.Role.RADIOBUTTON]} elif itemType == "separator": - attrs = {"role": [controlTypes.ROLE_SEPARATOR]} + attrs = {"role": [controlTypes.Role.SEPARATOR]} elif itemType == "table": - attrs = {"role": [controlTypes.ROLE_TABLE]} + attrs = {"role": [controlTypes.Role.TABLE]} if not config.conf["documentFormatting"]["includeLayoutTables"]: attrs["table-layout"] = [False] elif itemType == "unvisitedLink": diff --git a/addon/globalPlugins/webAccess/ruleHandler/__init__.py b/addon/globalPlugins/webAccess/ruleHandler/__init__.py index 069cac1c..01330984 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/ruleHandler/__init__.py @@ -36,7 +36,6 @@ from pprint import pformat import threading import time -import sys from typing import Any import weakref @@ -73,11 +72,8 @@ from .properties import RuleProperties, CriteriaProperties -if sys.version_info[1] < 9: - from typing import Mapping, Sequence, Tuple -else: - from collections.abc import Mapping, Sequence - Tuple = tuple +from collections.abc import Mapping, Sequence +Tuple = tuple addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/ruleHandler/controlMutation.py b/addon/globalPlugins/webAccess/ruleHandler/controlMutation.py index 04330cb9..1ec7934b 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/controlMutation.py +++ b/addon/globalPlugins/webAccess/ruleHandler/controlMutation.py @@ -85,32 +85,32 @@ def apply(self, result): MUTATIONS = { - "button": Mutation({"role": controlTypes.ROLE_BUTTON}, False), + "button": Mutation({"role": controlTypes.Role.BUTTON}, False), # "level" is int in position info, but text in control field attributes... "heading.1": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "1"}, False + {"role": controlTypes.Role.HEADING, "level": "1"}, False ), "heading.2": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "2"}, False + {"role": controlTypes.Role.HEADING, "level": "2"}, False ), "heading.3": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "3"}, False + {"role": controlTypes.Role.HEADING, "level": "3"}, False ), "heading.4": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "4"}, False + {"role": controlTypes.Role.HEADING, "level": "4"}, False ), "heading.5": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "5"}, False + {"role": controlTypes.Role.HEADING, "level": "5"}, False ), "heading.6": Mutation( - {"role": controlTypes.ROLE_HEADING, "level": "6"}, False + {"role": controlTypes.Role.HEADING, "level": "6"}, False ), "labelled": Mutation({}, True), "landmark.region": Mutation({"landmark": "region"}, True), "landmark.nav.named": Mutation({"landmark": "navigation"}, True), "landmark.nav.unnamed": Mutation({"landmark": "navigation"}, False), - "link": Mutation({"role": controlTypes.ROLE_LINK}, False), - "section": Mutation({"role": controlTypes.ROLE_SECTION}, False), + "link": Mutation({"role": controlTypes.Role.LINK}, False), + "section": Mutation({"role": controlTypes.Role.SECTION}, False), "table.data": Mutation({"table-layout": False}, False), "table.layout": Mutation({"table-layout": True}, False) } diff --git a/addon/globalPlugins/webAccess/ruleHandler/properties.py b/addon/globalPlugins/webAccess/ruleHandler/properties.py index e9faa1d6..8d1da9e3 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/properties.py +++ b/addon/globalPlugins/webAccess/ruleHandler/properties.py @@ -28,7 +28,6 @@ from dataclasses import dataclass from enum import Enum from pprint import pformat -import sys from typing import Any, TypeAlias import weakref @@ -37,10 +36,7 @@ import addonHandler -if sys.version_info[1] < 9: - from typing import Iterator, Mapping, Sequence -else: - from collections.abc import Iterator, Mapping, Sequence +from collections.abc import Iterator, Mapping, Sequence addonHandler.initTranslation() @@ -289,4 +285,3 @@ class CriteriaProperties(RuleProperties): def __init__(self, criteria: "Criteria"): super().__init__(criteria.rule) self._map = criteria.rule.properties._map.new_child() - diff --git a/addon/globalPlugins/webAccess/store/__init__.py b/addon/globalPlugins/webAccess/store/__init__.py index 2d088c6b..70724124 100644 --- a/addon/globalPlugins/webAccess/store/__init__.py +++ b/addon/globalPlugins/webAccess/store/__init__.py @@ -198,7 +198,6 @@ def update(self, item, ref=None, **kwargs): class DuplicateRefError(Exception): pass - class MalformedRefError(Exception): pass diff --git a/addon/globalPlugins/webAccess/store/addons.py b/addon/globalPlugins/webAccess/store/addons.py index 2aaca713..6cf6178a 100644 --- a/addon/globalPlugins/webAccess/store/addons.py +++ b/addon/globalPlugins/webAccess/store/addons.py @@ -42,8 +42,7 @@ def __init__(self, *args, **kwargs): def __getStores(self): for addon in addonHandler.getAvailableAddons(): - # Introduced in NVDA 2016.3 - if hasattr(addon, "isDisabled") and addon.isDisabled: + if addon.isDisabled: continue yield self.addonStoreFactory(addon) diff --git a/addon/globalPlugins/webAccess/store/webModule.py b/addon/globalPlugins/webAccess/store/webModule.py index d3e178cb..1cdc96ad 100644 --- a/addon/globalPlugins/webAccess/store/webModule.py +++ b/addon/globalPlugins/webAccess/store/webModule.py @@ -27,7 +27,6 @@ from collections import OrderedDict import errno -import imp import os import os.path from pprint import pformat @@ -53,10 +52,7 @@ from .addons import AddonsStore -try: - import json -except ImportError: - from ..lib import json +import json class WebModuleJsonFileDataStore(Store): diff --git a/addon/globalPlugins/webAccess/utils.py b/addon/globalPlugins/webAccess/utils.py index 5adc6010..c2ee4888 100644 --- a/addon/globalPlugins/webAccess/utils.py +++ b/addon/globalPlugins/webAccess/utils.py @@ -34,12 +34,6 @@ import addonHandler -try: - from six import text_type -except ImportError: - # NVDA version < 2018.3 - text_type = str - addonHandler.initTranslation() @@ -47,7 +41,7 @@ def updateOrDrop(map, key, value, default=None): if ( value == default - or (isinstance(value, text_type) and not value.strip()) + or (isinstance(value, str) and not value.strip()) ): map.pop(key, None) else: diff --git a/addon/globalPlugins/webAccess/webAppLib/__init__.py b/addon/globalPlugins/webAccess/webAppLib/__init__.py index c8257fc1..005aff91 100644 --- a/addon/globalPlugins/webAccess/webAppLib/__init__.py +++ b/addon/globalPlugins/webAccess/webAppLib/__init__.py @@ -51,13 +51,6 @@ from . import html -try: - from six import string_types -except ImportError: - # NVDA version < 2018.3 - string_types = str - - def speechOff(): speech.setSpeechMode(speech.SpeechMode.off) @@ -117,7 +110,7 @@ def focusVirtualBuffer (obj=None): # place le focus NVDA sur le premier objet qui a un attribut treeInterceptor afin d'accéder au curseur virtuel if obj is None: obj = api.getFocusObject() - if obj.role == controlTypes.ROLE_UNKNOWN: + if obj.role == controlTypes.Role.UNKNOWN: obj = obj.parent if hasattr (obj, "treeInterceptor") and obj.treeInterceptor is not None: api.setFocusObject(obj) @@ -239,7 +232,7 @@ def searchNameByColor (obj, background): trace (repr(f["background-color"])) except: red = -1 - elif isinstance (field, string_types): + elif isinstance (field, str): if red == background and len(field) > 3: return str (field) # end of loop diff --git a/addon/globalPlugins/webAccess/webAppLib/html.py b/addon/globalPlugins/webAccess/webAppLib/html.py index 870473d8..3802228e 100644 --- a/addon/globalPlugins/webAccess/webAppLib/html.py +++ b/addon/globalPlugins/webAccess/webAppLib/html.py @@ -29,14 +29,9 @@ from NVDAObjects.IAccessible import IAccessible import controlTypes from logHandler import log -import virtualBuffers -try: - REASON_CARET = controlTypes.OutputReason.CARET -except AttributeError: - # NVDA < 2021.1 - REASON_CARET = controlTypes.REASON_CARET +REASON_CARET = controlTypes.OutputReason.CARET # global variable that stores the last valid document tree interceptor @@ -174,7 +169,7 @@ def getTreeInterceptor(focusObject=None): def getCaretInfo(focusObject=None): treeInterceptor = getTreeInterceptor(focusObject=focusObject) - if not treeInterceptor: + if not treeInterceptor: return None return treeInterceptor.makeTextInfo(textInfos.POSITION_CARET) @@ -383,111 +378,3 @@ def searchTag_2015(nodeType, info=None, id=None, className=None, src=None, text= item.moveTo() api.setReviewPosition(info) return info - -""" -NVDA 2014.4 and lower code for searchTag -""" - -def nextTag (focus, nodeType, start): - if nodeType == "button|link": - type1 = "button" - type2 = "link" - else: - type1 = nodeType - type2 = None - try: - node1, start1, end1 = next(focus._iterNodesByType(type1, "next", start)) - except: - start1 = 1000000 - try: - node2, start2, end2 = next(focus._iterNodesByType(type2, "next", start)) - except: - start2 = 1000000 - if start1 < start2: - return node1, start1, end1 - elif start2 < start1: - return node2, start2, end2 - else: - raise - -def previousTag (focus, nodeType, start): - if nodeType == "button|link": - type1 = "button" - type2 = "link" - else: - type1 = nodeType - type2 = None - try: - node1, start1, end1 = next(focus._iterNodesByType(type1, "previous", start)) - except: - start1 = -1 - try: - node2, start2, end2 = next(focus._iterNodesByType(type2, "previous", start)) - except: - start2 = -1 - if start1 > start2: - return node1, start1, end1 - elif start2 > start1: - return node2, start2, end2 - else: - raise - -def searchTag_2014(nodeType, first=False, reverse=False, func=None, elementDescription=None, moveFocus=True): - try: - focus = api.getFocusObject() - focus = focus.treeInterceptor - if moveFocus: - focus.passThrough = False - virtualBuffers.reportPassThrough.last = False # pour que le changement de mode ne soit pas lu automatiquement - if first: - info=focus.makeTextInfo(textInfos.POSITION_FIRST) - else: - info=focus.makeTextInfo(textInfos.POSITION_CARET) - startOffset=info._startOffset - endOffset=info._endOffset - ok = False - while not ok: - if reverse: - node, startOffset, endOffset = previousTag (focus, nodeType, startOffset) - else: - node, startOffset, endOffset = nextTag (focus, nodeType, startOffset) - info = focus.makeTextInfo(textInfos.offsets.Offsets(startOffset, endOffset)) - if elementDescription is not None: - obj = info.NVDAObjectAtStart - ok = getElementDescription (obj).find (elementDescription) > 0 - elif func is not None: - ok = func(info) - else: - ok = True - except: - return False - info = focus.makeTextInfo(textInfos.offsets.Offsets(startOffset, endOffset)) - if func is not None: - if not func(info): - return False - fieldInfo = info.copy() - info.collapse() - info.move(textInfos.UNIT_LINE, 1, endPoint="end") - if info.compareEndPoints(fieldInfo, "endToEnd") > 0: - # We've expanded past the end of the field, so limit to the end of the field. - info.setEndPoint(fieldInfo, "endToEnd") - info.collapse() - if moveFocus: - focus._set_selection(info) - api.setReviewPosition(info) - return True - - - -""" -searchTag - searches for a specific tag into the document. -This calls either the 2015.1 or 2014.4 version, depending on the NVDA version we actually use. -""" - -def searchTag(nodeType, info=None, id=None, className=None, src=None, text=None, first=False, reverse=False, func=None, maxAncestors=1, moveFocus=False): - if hasattr (virtualBuffers, "reportPassThrough"): - # ancienne version de NVDA - return searchTag_2014(nodeType, first, reverse, func, moveFocus) - else: - return searchTag_2015(nodeType, info=info, id=id, className=className, src=src, text=text, first=first, reverse=reverse, func=func, max=maxAncestors, moveFocus=moveFocus) - \ No newline at end of file diff --git a/addon/globalPlugins/webAccess/webAppScheduler.py b/addon/globalPlugins/webAccess/webAppScheduler.py index 4d56ddd8..06d3b1e4 100644 --- a/addon/globalPlugins/webAccess/webAppScheduler.py +++ b/addon/globalPlugins/webAccess/webAppScheduler.py @@ -35,18 +35,12 @@ import api import textInfos +import queue from .overlay import WebAccessBmdti, WebAccessObject from .webAppLib import * -try: - from six.moves import queue -except ImportError: - # NVDA version < 2018.3 - import queue as queue - - TRACE = lambda *args, **kwargs: None # @UnusedVariable #TRACE = log.info @@ -91,7 +85,7 @@ def send(self, **kwargs): self.queue.put(kwargs) def event_stop(self): - self.stop = True + self.stop = True def event_timeout(self): focus = api.getFocusObject() diff --git a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py index ce74ce5b..af51aa79 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py @@ -102,7 +102,7 @@ def getWebModuleForTreeInterceptor(treeInterceptor): # when the TreeInterceptor gets killed when the modal is discarded. if ( isinstance(obj, WebAccessObject) - and obj._get_role(original=True) in (controlTypes.ROLE_DIALOG, controlTypes.ROLE_APPLICATION) + and obj._get_role(original=True) in (controlTypes.Role.DIALOG, controlTypes.Role.APPLICATION) ): class Break(Exception): """Block-level break.""" @@ -166,13 +166,13 @@ def getWindowTitle(obj): role = obj._get_role(original=True) else: role = obj.role - if role == controlTypes.ROLE_DIALOG: + if role == controlTypes.Role.DIALOG: try: root = obj.parent.treeInterceptor.rootNVDAObject except AttributeError: return None return getWindowTitle(root) - if role != controlTypes.ROLE_DOCUMENT: + if role != controlTypes.Role.DOCUMENT: try: root = obj.treeInterceptor.rootNVDAObject except AttributeError: @@ -436,19 +436,28 @@ def getWebModuleFactory(name): def hasCustomModule(name): - return any( - importer.find_module(f"{PACKAGE_NAME}.{name}") - for importer in _importers - if importer - ) + fullname = f"{PACKAGE_NAME}.{name}" + for importer in _importers: + if importer is None: + continue + # Python 3.12+: find_module is removed, use find_spec instead + if hasattr(importer, 'find_spec'): + spec = importer.find_spec(fullname, None) + if spec is not None: + return True + elif hasattr(importer, 'find_module'): + # Fallback for older Python versions + if importer.find_module(fullname): + return True + return False def initialize(): global store global _importers - import imp - webModules = imp.new_module(PACKAGE_NAME) + import types + webModules = types.ModuleType(PACKAGE_NAME) webModules.__path__ = list() import sys sys.modules[PACKAGE_NAME] = webModules diff --git a/addon/globalPlugins/webAccess/webModuleHandler/dataRecovery/__init__.py b/addon/globalPlugins/webAccess/webModuleHandler/dataRecovery/__init__.py index c3300da8..694cf979 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/dataRecovery/__init__.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/dataRecovery/__init__.py @@ -39,14 +39,6 @@ from ...lib.packaging import version -try: - from six import string_types, text_type -except ImportError: - # NVDA version < 2018.3 - string_types = str - text_type = str - - class NewerFormatVersion(version.InvalidVersion): pass @@ -111,7 +103,7 @@ def recoverFromLegacyTo_0_1(data): data["Rules"] = data.pop("PlaceMarkers") # Earlier versions supported only a single URL trigger url = data.get("WebModule", {}).get("url", None) - if isinstance(url, string_types): + if isinstance(url, str): data["WebModule"]["url"] = [url] # Custom labels for certain fields are not supported anymore # TODO: Re-implement custom field labels? @@ -246,7 +238,7 @@ def recoverFrom_0_4_to_0_5(data): rules = data.get("Rules", []) for rule in rules: if "role" in rule: - rule["role"] = text_type(rule["role"]) + rule["role"] = str(rule["role"]) data["formatVersion"] = "0.5" diff --git a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py index 5a7aeb18..60922487 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py @@ -33,7 +33,6 @@ import datetime import json import os -import sys import addonHandler addonHandler.initTranslation() @@ -52,10 +51,7 @@ from ..webAppLib import playWebAccessSound -if sys.version_info[1] < 9: - from typing import Sequence -else: - from collections.abc import Sequence +from collections.abc import Sequence class InvalidApiVersion(version.InvalidVersion): @@ -318,12 +314,12 @@ def event_webModule_pageChanged(self, pageTitle, nextHandler): # Currently dead code, but will likely be revived for issue #17 def event_webModule_gainFocus(self, obj, nextHandler): - if obj.role not in [controlTypes.ROLE_DOCUMENT, controlTypes.ROLE_FRAME, controlTypes.ROLE_INTERNALFRAME]: + if obj.role not in [controlTypes.Role.DOCUMENT, controlTypes.Role.FRAME, controlTypes.Role.INTERNALFRAME]: nextHandler() # Currently dead code, but will likely be revived for issue #17 def event_focusEntered(self, obj, nextHandler): - if obj.role != controlTypes.ROLE_DOCUMENT: + if obj.role != controlTypes.Role.DOCUMENT: nextHandler() def event_gainFocus(self, obj, nextHandler): diff --git a/buildVars.py b/buildVars.py index 9475f8bb..233be387 100644 --- a/buildVars.py +++ b/buildVars.py @@ -26,7 +26,7 @@ def _(arg): # Translators: Long description to be shown for this add-on on add-on information from add-ons manager "addon_description" : _("""Web application modules support for modern or complex web sites."""), # version - "addon_version" : "2025.04.22-dev", + "addon_version" : "2026.02.23-dev", # Author(s) "addon_author" : ( "Accessolutions (https://accessolutions.fr), " @@ -42,9 +42,9 @@ def _(arg): # Documentation file name "addon_docFileName": "readme.html", # Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional) - "addon_minimumNVDAVersion": "2021.1", + "addon_minimumNVDAVersion": "2024.1", # Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version) - "addon_lastTestedNVDAVersion": "2024.2", + "addon_lastTestedNVDAVersion": "2026.1", # Add-on update channel (default is None, denoting stable releases, # and for development releases, use "dev".) # Do not change unless you know what you are doing!