diff --git a/addon/globalPlugins/webAccess/__init__.py b/addon/globalPlugins/webAccess/__init__.py index a33fa4ca..0965e9f7 100644 --- a/addon/globalPlugins/webAccess/__init__.py +++ b/addon/globalPlugins/webAccess/__init__.py @@ -224,12 +224,18 @@ def showWebAccessGui(self): "webAccess": self, "focusObject": obj, } - webModule = obj.webAccess.webModule - if webModule is not None: + if obj.webAccess.webModule: + webModule = obj.treeInterceptor.webAccess.webModuleAtCaret context["webModule"] = webModule context["pageTitle"] = webModule.pageTitle mgr = webModule.ruleManager context["result"] = mgr.getResultAtCaret(focus=obj) + stack = [] + while webModule: + stack.append(webModule) + webModule = webModule.ruleManager.parentModule + if len(stack) > 1: + context["webModuleStackAtCaret"] = stack menu.show(context) @script( diff --git a/addon/globalPlugins/webAccess/gui/actions.py b/addon/globalPlugins/webAccess/gui/actions.py index 47d7e82d..4c43a877 100644 --- a/addon/globalPlugins/webAccess/gui/actions.py +++ b/addon/globalPlugins/webAccess/gui/actions.py @@ -260,7 +260,7 @@ def updateGesturesListBox(self, selectId: str = None, focus: bool = False): @guarded def onPanelActivated(self): super().onPanelActivated() - supported = self.getRuleType() in (ruleTypes.ZONE, ruleTypes.MARKER) + supported = self.getRuleType() in ruleTypes.ACTION_TYPES self.panelDescription = "" if supported else self.descriptionIfNoneSupported self.Freeze() for item in self.hideable["IfSupported"]: @@ -273,7 +273,7 @@ def onPanelActivated(self): def onSave(self): super().onSave() data = self.getData() - if self.getRuleType() not in (ruleTypes.ZONE, ruleTypes.MARKER): + if self.getRuleType() not in ruleTypes.ACTION_TYPES: data.pop("gestures", None) data.get("properties", {}).pop("autoAction", None) elif not data.get("gestures"): diff --git a/addon/globalPlugins/webAccess/gui/menu.py b/addon/globalPlugins/webAccess/gui/menu.py index 8576c8dd..0ffea7e5 100644 --- a/addon/globalPlugins/webAccess/gui/menu.py +++ b/addon/globalPlugins/webAccess/gui/menu.py @@ -22,7 +22,7 @@ """Web Access GUI.""" -__version__ = "2024.08.25" +__version__ = "2024.08.26" __authors__ = ( "Julien Cochuyt ", "André-Abush Clause ", @@ -33,6 +33,7 @@ import wx import addonHandler +import config import gui from ... import webAccess @@ -80,7 +81,20 @@ def __init__(self, context): _("&New web module...")) self.Bind(wx.EVT_MENU, self.onWebModuleCreate, item) - if webModule: + stack = context.get("webModuleStackAtCaret", []).copy() + if stack: + subMenu = wx.Menu() + while stack: + mod = stack.pop(0) + handler = lambda evt, webModule=mod: self.onWebModuleEdit(evt, webModule=webModule) + item = subMenu.Append(wx.ID_ANY, mod.name) + subMenu.Bind(wx.EVT_MENU, handler, item) + self.AppendSubMenu( + subMenu, + # Translators: Web Access menu item label. + _("Edit &web module") + ) + elif webModule: item = self.Append( wx.ID_ANY, # Translators: Web Access menu item label. @@ -97,6 +111,16 @@ def __init__(self, context): self.AppendSeparator() + if config.conf["webAccess"]["devMode"]: + item = self.Append( + wx.ID_ANY, + # Translators: Web Access menu item label. + _("&Element description...") + ) + self.Bind(wx.EVT_MENU, self.onElementDescription, item) + + self.AppendSeparator() + item = self.AppendCheckItem( wx.ID_ANY, # Translators: Web Access menu item label. @@ -110,6 +134,11 @@ def show(self): gui.mainFrame.PopupMenu(self) gui.mainFrame.postPopup() + @guarded + def onElementDescription(self, evt): + from .elementDescription import showElementDescriptionDialog + showElementDescriptionDialog() + @guarded def onRuleCreate(self, evt): self.context["new"] = True @@ -122,13 +151,14 @@ def onRulesManager(self, evt): show(self.context, gui.mainFrame) @guarded - def onWebModuleCreate(self, evt): - self.context["new"] = True - from .webModuleEditor import show - show(self.context, gui.mainFrame) + def onSubModuleEdit(self, evt, webModule): + from logHandler import log + log.info(f"onSubModuleEdit: {dir(evt)}") @guarded - def onWebModuleEdit(self, evt): + def onWebModuleEdit(self, evt, webModule=None): + if webModule is not None: + self.context["webModule"] = webModule from .webModuleEditor import show show(self.context) diff --git a/addon/globalPlugins/webAccess/gui/rule/editor.py b/addon/globalPlugins/webAccess/gui/rule/editor.py index 75337892..d9a1f2fd 100644 --- a/addon/globalPlugins/webAccess/gui/rule/editor.py +++ b/addon/globalPlugins/webAccess/gui/rule/editor.py @@ -154,7 +154,7 @@ def onRuleType_change(self): prm = self.categoryParams # FIXME: Having a mapping stored in an attribute initialized with an unused sequence is quite misleading categoryClasses = tuple(nodeInfo.categoryClass for nodeInfo in self.Parent.Parent.categoryClasses) - for index in (categoryClasses.index(cls) for cls in (ActionsPanel, PropertiesPanel)): + for index in (categoryClasses.index(cls) for cls in (GeneralPanel, ActionsPanel, PropertiesPanel)): category = prm.tree.getXChild(prm.tree.GetRootItem(), index) self.refreshParent(category) @@ -227,22 +227,11 @@ def makeSettings(self, settingsSizer): def initData(self, context: Mapping[str, Any]) -> None: super().initData(context) data = self.getData() - if 'type' in data: - self.ruleType.SetSelection(list(ruleTypes.ruleTypeLabels.keys()).index(data['type'])) - else: - self.ruleType.SetSelection(0) - + self.ruleType.SetSelection(tuple(ruleTypes.ruleTypeLabels.keys()).index(data["type"])) self.ruleName.Value = data.get("name", "") self.commentText.Value = data.get("comment", "") self.refreshSummary() - @staticmethod - def initRuleTypeChoice(data, ruleTypeChoice): - for index, key in enumerate(ruleTypes.ruleTypeLabels.keys()): - if key == data["type"]: - ruleTypeChoice.Selection = index - break - def updateData(self): data = self.getData() # The rule type should already be stored as of onRuleType_choice @@ -863,16 +852,16 @@ def getGeneralChildren(self): categoryParams=prm ) for editorType, prm in ( - (EditorType.TEXT, cls.CategoryParams( - fieldDisplayName=SHARED_LABELS["name"], - fieldName="name", - )), (EditorType.CHOICE, cls.CategoryParams( editorChoices=ruleTypes.ruleTypeLabels, fieldDisplayName=SHARED_LABELS["type"], fieldName="type", onEditor_change=cls.onRuleType_change, )), + (EditorType.TEXT, cls.CategoryParams( + fieldDisplayName=SHARED_LABELS["name"], + fieldName="name", + )), ) ) diff --git a/addon/globalPlugins/webAccess/gui/webModulesManager.py b/addon/globalPlugins/webAccess/gui/webModulesManager.py index 94c7d844..609d277c 100644 --- a/addon/globalPlugins/webAccess/gui/webModulesManager.py +++ b/addon/globalPlugins/webAccess/gui/webModulesManager.py @@ -255,13 +255,16 @@ def refreshModulesList(self, selectIndex: int = None, selectItem: "WebModule" = ctrl = self.modulesList ctrl.DeleteAllItems() contextModule = self.context.get("webModule") + contextModules = { + (module.name, module.layers[0].storeRef): module + for module in list(reversed(contextModule.ruleManager.subModules.all())) + [contextModule] + } modules = self.modules = [] from .. import webModuleHandler for index, module in enumerate(webModuleHandler.getWebModules()): if selectIndex is None and module.equals(selectItem): selectIndex = index - if module.equals(contextModule): - module = contextModule + module = contextModules.get((module.name, module.layers[0].storeRef), module) trigger = (" %s " % _("and")).join( ([ "url=%s" % url diff --git a/addon/globalPlugins/webAccess/overlay.py b/addon/globalPlugins/webAccess/overlay.py index 6d080be6..9acd6db5 100644 --- a/addon/globalPlugins/webAccess/overlay.py +++ b/addon/globalPlugins/webAccess/overlay.py @@ -215,7 +215,15 @@ def webModule(self): except Exception: log.exception() return webModule - + + @property + def webModuleAtCaret(self): + rootModule = self.webModule + if not rootModule: + return None + info = self.treeInterceptor.makeTextInfo(textInfos.POSITION_CARET) + return self.ruleManager.subModules.atPosition(info._startOffset) or rootModule + @property def zone(self): ruleManager = self.ruleManager @@ -931,19 +939,12 @@ class Break(Exception): return super().getAlternativeScript(gesture, script) def getScript(self, gesture): - webModule = self.webAccess.webModule + webModule = self.webAccess.webModuleAtCaret if webModule: - func = webModule.getScript(gesture) - if func: - return ScriptWrapper( - func, ignoreTreeInterceptorPassThrough=True - ) - mgr = self.webAccess.ruleManager - if mgr: - func = mgr.getScript(gesture) - if func: + script = webModule.getScript(gesture) + if script: return ScriptWrapper( - func, ignoreTreeInterceptorPassThrough=True + script, ignoreTreeInterceptorPassThrough=True ) return super().getScript(gesture) diff --git a/addon/globalPlugins/webAccess/ruleHandler/__init__.py b/addon/globalPlugins/webAccess/ruleHandler/__init__.py index 59ca4d26..3d39572f 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/ruleHandler/__init__.py @@ -30,7 +30,7 @@ ) -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from itertools import chain from pprint import pformat import threading @@ -42,7 +42,7 @@ import addonHandler import api -import baseObject +from baseObject import AutoPropertyObject, ScriptableObject import browseMode import controlTypes import inputCore @@ -57,6 +57,7 @@ from garbageHandler import TrackedObject from .. import nodeHandler +from ..utils import logException from ..webAppLib import ( html, logTimeStart, @@ -84,7 +85,7 @@ builtinRuleActions["mouseMove"] = pgettext("webAccess.action", "Mouse move") -class DefaultScripts(baseObject.ScriptableObject): +class DefaultScripts(ScriptableObject): def __init__(self, warningMessage): super().__init__() @@ -100,7 +101,7 @@ def script_notAssigned(self, gesture): __gestures = {} -class RuleManager(baseObject.ScriptableObject): +class RuleManager(ScriptableObject): def __init__(self, webModule): super().__init__() @@ -120,7 +121,11 @@ def __init__(self, webModule): self.lastAutoMovetoTime = 0 self.defaultScripts = DefaultScripts("Aucun marqueur associé à cette touche") self.timerCheckAutoAction = None - self.zone = None + self.zone: "Zone" = None + self.parentModule: "WebModule" = None + self.parentNode: "NodeField" = None + self.subModules: SubModules = SubModules(self) + self._subModuleResults: Sequence["SingleNodeResult"] = [] def _get_webModule(self): return self._webModule() @@ -178,7 +183,7 @@ def getRules(self, layer=None): return tuple([ rule for ruleLayers in list(self._rules.values()) - for rule in list(ruleLayers.values()) + for rule in reversed(list(ruleLayers.values())) ]) def getRule(self, name, layer=None): @@ -190,7 +195,7 @@ def getRule(self, name, layer=None): if layer not in (None, False): return ruleLayers[layer] try: - return next(iter(list(ruleLayers.values()))) + return next(iter(reversed(ruleLayers.values()))) except StopIteration: raise LookupError({"name": name, "layer": layer}) @@ -215,7 +220,7 @@ def iterResultsByName(self, name, layer=None): continue elif ( layer not in (None, False) - or self._layersIndex[layer] >= self._layersIndex[rule.layer] + or tuple(self._layers.keys()).index(layer) >= tuple(self._layers.keys()).index(rule.layer) ): yield result @@ -260,26 +265,48 @@ def getActions(self) -> Mapping[str, str]: actions.setdefault(actionId, actionLabel) return actions + def getGlobalScript(self, gesture, skipCaret=True): + + def gen(): + if not skipCaret: + modAtCaret = self.nodeManager.treeInterceptor.webAccess.webModuleAtCaret + if modAtCaret is not self.webModule: + yield modAtCaret.ruleManager.getGlobalScript + for subMod in self.subModules.all(): + if subMod is not modAtCaret: + yield subMod.ruleManager.getGlobalScript + for result in self.getResults(): + if result.rule.type == ruleTypes.GLOBAL_MARKER: + yield result.getScript + + for func in gen(): + script = func(gesture) + if script: + return script + def getScript(self, gesture): - func = super().getScript(gesture) - if func is not None: - return func + script = super().getScript(gesture) + if script: + return script + script = self.getGlobalScript(gesture, skipCaret=False) + if script: + return script for layer in reversed(list(self._layers.keys())): for result in self.getResults(): - if result.rule.layer != layer: + if result.rule.type is ruleTypes.GLOBAL_MARKER or result.rule.layer != layer: continue - func = result.getScript(gesture) - if func is not None: - return func - for rules in reversed(list(self._layers.values())): - for rule in list(rules.values()): - for criterion in rule.criteria: - func = rule.getScript(gesture) - if func is not None: - return func - func = rule.getScript(gesture) - if func is not None: - return func + script = result.getScript(gesture) + if script: + return script +# for rules in reversed(list(self._layers.values())): +# for rule in list(rules.values()): +# for criterion in rule.criteria: +# func = rule.getScript(gesture) +# if func is not None: +# return func +# func = rule.getScript(gesture) +# if func is not None: +# return func return self.defaultScripts.getScript(gesture) def _get_isReady(self): @@ -288,21 +315,24 @@ def _get_isReady(self): return True def terminate(self): - self._webModule = None self._ready = False + self._webModule = None + self.subModules.terminate() try: self.timerCheckAutoAction.cancel() except Exception: pass self.timerCheckAutoAction = None self._nodeManager = None - del self._results[:] + self._results.clear() + self._subModuleResults.clear() self._mutatedControlsById.clear() - self._mutatedControlsByOffset[:] = [] + self._mutatedControlsByOffset.clear() for ruleLayers in list(self._rules.values()): for rule in list(ruleLayers.values()): rule.resetResults() + @logException def update(self, nodeManager=None, force=False): if self.webModule is None: # This instance has been terminated @@ -323,26 +353,29 @@ def update(self, nodeManager=None, force=False): self._ready = True return False t = logTimeStart() - self._results[:] = [] + self._results.clear() + self._subModuleResults.clear() self._mutatedControlsById.clear() self._mutatedControlsByOffset.clear() - for ruleLayers in list(self._rules.values()): - for rule in list(ruleLayers.values()): - rule.resetResults() - - results = self._results + for rule in self.getRules(): + rule.resetResults() + + for rule in (rule for rule in self.getRules() if rule.properties.subModule): + results = rule.getResults() + self._results.extend(results) + self._subModuleResults.extend(results) for rule in sorted( - [rule for ruleLayers in list(self._rules.values()) for rule in list(ruleLayers.values())], + (rule for rule in self.getRules() if not rule.properties.subModule), key=lambda rule: ( 0 if rule.type in ( ruleTypes.PAGE_TITLE_1, ruleTypes.PAGE_TITLE_2 - ) else 1 + ) else 2 ) ): - results.extend(rule.getResults()) - results.sort() - - for result in results: + self._results.extend(rule.getResults()) + self._results.sort() + + for result in self._results: if not (hasattr(result, "node") and result.properties.mutation): continue controlId = int(result.node.controlIdentifier) @@ -353,9 +386,9 @@ def update(self, nodeManager=None, force=False): self._mutatedControlsByOffset.append(entry) else: entry.apply(result) - self._ready = True self.nodeManagerIdentifier = self.nodeManager.identifier + self.subModules.update() if self.zone is not None: if not self.zone.update() or not self.zone.containsTextInfo( self.nodeManager.treeInterceptor.makeTextInfo(textInfos.POSITION_CARET) @@ -620,6 +653,7 @@ def quickNav( ): if not self.isReady: playWebAccessSound("keyError") + # Translators: Reported when attempting an action while WebAccess is not ready ui.message(_("Not ready")) return None @@ -629,6 +663,7 @@ def quickNav( if position is None: playWebAccessSound("keyError") + # Translators: Reported when attempting an action while WebAccess is not ready ui.message(_("Not ready")) return None @@ -694,14 +729,14 @@ def quickNavToPreviousLevel1(self): self.quickNav(previous=True, types=(ruleTypes.ZONE,), honourSkip=False) def quickNavToNextLevel2(self): - self.quickNav(types=(ruleTypes.ZONE, ruleTypes.MARKER)) + self.quickNav(types=ruleTypes.ACTION_TYPES) def quickNavToPreviousLevel2(self): - self.quickNav(previous=True, types=(ruleTypes.ZONE, ruleTypes.MARKER)) + self.quickNav(previous=True, types=ruleTypes.ACTION_TYPES) def quickNavToNextLevel3(self): self.quickNav( - types=(ruleTypes.ZONE, ruleTypes.MARKER), + types=ruleTypes.ACTION_TYPES, respectZone=True, honourSkip=False, cycle=False @@ -710,13 +745,58 @@ def quickNavToNextLevel3(self): def quickNavToPreviousLevel3(self): self.quickNav( previous=True, - types=(ruleTypes.ZONE, ruleTypes.MARKER), + types=ruleTypes.ACTION_TYPES, respectZone=True, honourSkip=False, cycle=False ) +class SubModules(AutoPropertyObject): + + def __init__(self, parent: RuleManager): + self._parent = weakref.ref(parent) + self.modulesByNameAndIndex: Mapping[tuple(str, int), "WebModule"] = {} + self.modulesByPosition: Mapping[tuple(int, int), "WebModule"] = {} + + def _get_parent(self): + return self._parent and self._parent() + + def all(self) -> Sequence["WebModule"]: + return tuple(self.modulesByNameAndIndex.values()) + + @logException + def atPosition(self, offset) -> "WebModule": + for (start, end), module in self.modulesByPosition.items(): + if start <= offset <= end: + return module.ruleManager.subModules.atPosition(offset) or module + + @logException + def update(self): + from ..webModuleHandler import getWebModule + previous = self.modulesByNameAndIndex.copy() + self.modulesByNameAndIndex.clear() + for result in self.parent.getResults(): + if result.properties.subModule: + key = (result.rule.name, result.index) + webModule = previous.get(key) + if not webModule: + webModule = getWebModule(result.properties.subModule) + if not webModule: + log.error(f"WebModule not found: {result.properties.subModule!r}") + continue + self.modulesByNameAndIndex[key] = webModule + self.modulesByPosition[(result.startOffset, result.endOffset)] = webModule + webModule.ruleManager.parentModule = self.parent.webModule + webModule.ruleManager.parentNode = result.node + webModule.ruleManager.update(self.parent.nodeManager) + + def terminate(self): + self.modulesByNameAndIndex.clear() + self.modulesByPosition.clear() + self._parent = None + + class CustomActionDispatcher(object): """ Execute a custom action, eventually overriding a standard action. @@ -805,7 +885,7 @@ def getCustomFunc(self, webModule=None): ) -class Result(baseObject.ScriptableObject): +class Result(ScriptableObject): def __init__(self, criteria, context, index): super().__init__() @@ -1005,11 +1085,6 @@ def script_sayall(self, gesture, fromQuickNav=False): ) def script_activate(self, gesture): - if self.node.nodeManager is None: - return - if not self.rule.ruleManager.isReady : - log.info ("not ready") - return treeInterceptor = self.node.nodeManager.treeInterceptor if self.properties.sayName: speech.speakMessage(self.label) @@ -1052,7 +1127,7 @@ def getTitle(self): return self.label + " - " + self.node.innerText -class Criteria(baseObject.ScriptableObject): +class Criteria(ScriptableObject): def __init__(self, rule, data): super().__init__() @@ -1090,7 +1165,7 @@ def load(self, data): gesturesMap = {} for gestureIdentifier in list(self.gestures.keys()): gesturesMap[gestureIdentifier] = "notFound" - self.bindGestures(gesturesMap) + # self.bindGestures(gesturesMap) self.properties.load(data.pop("properties", {})) if data: raise ValueError( @@ -1210,7 +1285,8 @@ def iterResults(self): return if not self.checkContextPageType(): return - + # Restrict to current SubModule + parentNode = mgr.parentNode or mgr.nodeManager.mainNode # Handle contextParent rootNodes = set() # Set of possible parent nodes excludedNodes = set() # Set of excluded parent nodes @@ -1243,7 +1319,7 @@ def iterResults(self): else: multipleContext = False if results: - nodes = [result.node for result in results] + nodes = [result.node for result in results if result.node in parentNode] if exclude: excludedNodes.update(nodes) else: @@ -1265,6 +1341,9 @@ def iterResults(self): return rootNodes = newRootNodes kwargs = getSimpleSearchKwargs(self) + excludedNodes.update({ + result.node for result in self.rule.ruleManager._subModuleResults + }) if excludedNodes: kwargs["exclude"] = excludedNodes limit = None @@ -1299,7 +1378,7 @@ def script_notFound(self, gesture): ) -class Rule(baseObject.ScriptableObject): +class Rule(ScriptableObject): def __init__(self, ruleManager, data): super().__init__() @@ -1482,7 +1561,7 @@ def getSimpleSearchKwargs(criteria, raiseOnUnsupported=False): return kwargs -class Zone(baseObject.AutoPropertyObject): +class Zone(AutoPropertyObject): def __init__(self, result): super().__init__() diff --git a/addon/globalPlugins/webAccess/ruleHandler/properties.py b/addon/globalPlugins/webAccess/ruleHandler/properties.py index 08694ee3..a50e223b 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/properties.py +++ b/addon/globalPlugins/webAccess/ruleHandler/properties.py @@ -71,7 +71,7 @@ def getDisplayName(self, ruleType) -> str: class PropertySpec(Enum): autoAction = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=str, default=None, # Translators: The display name for a rule property @@ -81,7 +81,7 @@ class PropertySpec(Enum): isRestrictedChoice=True ) multiple = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.PARENT, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.PARENT, ruleTypes.ZONE), valueType=bool, default=False, # Translators: The display name for a rule property @@ -90,7 +90,7 @@ class PropertySpec(Enum): isRestrictedChoice=False ) formMode = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=bool, default=False, # Translators: The display name for a rule property @@ -99,7 +99,7 @@ class PropertySpec(Enum): isRestrictedChoice=False ) skip = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=bool, default=False, # Translators: The display name for a rule property @@ -108,7 +108,7 @@ class PropertySpec(Enum): isRestrictedChoice=False ) sayName = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=bool, default=False, # Translators: The display name for a rule property @@ -117,7 +117,7 @@ class PropertySpec(Enum): isRestrictedChoice=False ) customName = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=str, default="", # Translators: The display name for a rule property @@ -128,6 +128,7 @@ class PropertySpec(Enum): ) customValue = PropertySpecValue( ruleTypes=( + ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.PAGE_TITLE_1, ruleTypes.PAGE_TITLE_2, @@ -136,7 +137,7 @@ class PropertySpec(Enum): valueType=str, default="", displayName={ - (ruleTypes.MARKER, ruleTypes.ZONE): + (ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE): # Translators: The display name for a rule property pgettext("webAccess.ruleProperty", "Custom message"), ("pageTitle1", "pageTitle2"): @@ -148,7 +149,7 @@ class PropertySpec(Enum): isRestrictedChoice=False ) mutation = PropertySpecValue( - ruleTypes=(ruleTypes.MARKER, ruleTypes.ZONE), + ruleTypes=(ruleTypes.GLOBAL_MARKER, ruleTypes.MARKER, ruleTypes.ZONE), valueType=str, default=None, # Translators: The display name for a rule property diff --git a/addon/globalPlugins/webAccess/ruleHandler/ruleTypes.py b/addon/globalPlugins/webAccess/ruleHandler/ruleTypes.py index b0863e04..f56568f7 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/ruleTypes.py +++ b/addon/globalPlugins/webAccess/ruleHandler/ruleTypes.py @@ -31,6 +31,7 @@ addonHandler.initTranslation() +GLOBAL_MARKER = "globalMarker" MARKER = "marker" ZONE = "zone" PAGE_TYPE = "pageType" @@ -38,8 +39,11 @@ PAGE_TITLE_1 = "pageTitle1" PAGE_TITLE_2 = "pageTitle2" +ACTION_TYPES = (GLOBAL_MARKER, MARKER, ZONE) ruleTypeLabels = { + # Translators: The label for a rule type. + GLOBAL_MARKER: _("Global Marker"), # Translators: The label for a rule type. MARKER: _("Marker"), # Translators: The label for a rule type. diff --git a/addon/globalPlugins/webAccess/store/webModule.py b/addon/globalPlugins/webAccess/store/webModule.py index c777e99e..09e5b7af 100644 --- a/addon/globalPlugins/webAccess/store/webModule.py +++ b/addon/globalPlugins/webAccess/store/webModule.py @@ -82,7 +82,7 @@ def catalog(self, errors=None): try: data = self.get(ref).data meta = {} - for key in ("windowTitle", "url"): + for key in ("name", "url", "windowTitle"): # "WebApp" corresponds to legacy format version (pre 0.1) value = data.get("WebModule", data.get("WebApp", {})).get(key) if value: diff --git a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py index 1be4899f..2dd9cc94 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py @@ -79,6 +79,13 @@ def getCatalog(refresh=False, errors=None): return _catalog +def getWebModule(name: str) -> WebModule: + for ref, meta in getCatalog(): + candidate = meta.get("name") + if candidate == name: + return store.get(ref) + + def getWebModuleForTreeInterceptor(treeInterceptor): obj = treeInterceptor.rootNVDAObject windowTitle = getWindowTitle(obj) diff --git a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py index 0913c68a..3285eeb0 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py @@ -51,7 +51,7 @@ from ..lib.markdown2 import markdown from ..lib.packaging import version from ..webAppLib import playWebAccessSound -from .. import ruleHandler + class InvalidApiVersion(version.InvalidVersion): pass @@ -104,7 +104,8 @@ def __init__(self): self.layers: Sequence[WebModuleDataLayer] = [] self.activePageTitle = None self.activePageIdentifier = None - self.ruleManager = ruleHandler.RuleManager(self) + from ..ruleHandler import RuleManager + self.ruleManager = RuleManager(self) def __repr__(self): return "WebModule {name}".format( @@ -172,8 +173,12 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): """ return False + def getScript(self,gesture): + return super().getScript(gesture) or self.ruleManager.getScript(gesture) + def createRule(self, data): - return ruleHandler.Rule(self.ruleManager, data) + from ..ruleHandler import Rule + return Rule(self.ruleManager, data) def dump(self, layerName): layer = self.getLayer(layerName, raiseIfMissing=True)