diff --git a/addon/globalPlugins/webAccess/gui/menu.py b/addon/globalPlugins/webAccess/gui/menu.py index f4bdb438..9e6fa344 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.26" +__version__ = "2024.08.30" __authors__ = ( "Julien Cochuyt ", "André-Abush Clause ", @@ -39,7 +39,6 @@ from ... import webAccess from .. import ruleHandler from ..utils import guarded -from . import webModulesManager addonHandler.initTranslation() @@ -151,21 +150,21 @@ def onRulesManager(self, evt): show(self.context, gui.mainFrame) @guarded - def onWebModuleEdit(self, evt, webModule=None): - if webModule is not None: - self.context["webModule"] = webModule - from .webModuleEditor import show + def onWebModuleCreate(self, evt, webModule=None): + self.context["new"] = True + from .webModule.editor import show show(self.context) @guarded - def onWebModuleCreate(self, evt, webModule=None): - self.context["new"] = True - from .webModuleEditor import show + def onWebModuleEdit(self, evt, webModule=None): + if webModule is not None: + self.context["webModule"] = webModule + from .webModule.editor import show show(self.context) @guarded def onWebModulesManager(self, evt): - from .webModulesManager import show + from .webModule.manager import show show(self.context) @guarded diff --git a/addon/globalPlugins/webAccess/gui/rule/__init__.py b/addon/globalPlugins/webAccess/gui/rule/__init__.py index e69de29b..11a78704 100644 --- a/addon/globalPlugins/webAccess/gui/rule/__init__.py +++ b/addon/globalPlugins/webAccess/gui/rule/__init__.py @@ -0,0 +1,84 @@ +# globalPlugins/webAccess/gui/rule/__init__.py +# -*- coding: utf-8 -*- + +# This file is part of Web Access for NVDA. +# Copyright (C) 2015-2024 Accessolutions (http://accessolutions.fr) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# See the file COPYING.txt at the root of this distribution for more details. + + +__version__ = "2024.08.30" +__author__ = "Julien Cochuyt " + + +from collections.abc import Mapping +from typing import Any + +import wx + +import addonHandler +import gui +from logHandler import log + + +addonHandler.initTranslation() + + +def createMissingSubModule( + context: Mapping[str, Any], + data: Mapping[str, Any], + parent: wx.Window + ) -> bool: + """Create the missing SubModule from Rule or Criteria data + + If a SubModule is specified in the provided data, it is looked-up in the catalog. + If it is missing from the catalog, the user is prompted for creating it. + + This function returns: + - `None` if no creation was necessary or if the user declined the prompt. + - `False` if the user canceled the prompt or if the creation failed or has been canceled. + - `True` if the creation succeeded. + """ + name = data.get("properties", {}).get("subModule") + if not name: + return None + from ...webModuleHandler import getCatalog + if any(meta["name"] == name for ref, meta in getCatalog()): + return True + res = gui.messageBox( + message=( + # Translators: A prompt for creation of a missing SubModule + _(f"""SubModule {name} could not be found. + +Do you want to create it now?""") + ), + style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION, + parent=parent, + ) + if res is wx.NO: + return None + elif res is wx.CANCEL: + return False + context = context.copy() + context["new"] = True + context["data"] = {"webModule": {"name": name, "subModule": True}} + from ..webModule.editor import show + res = show(context, parent) + if res: + newName = context["webModule"].name + if newName != name: + data["properties"]["subModule"] = newName + return res diff --git a/addon/globalPlugins/webAccess/gui/actions.py b/addon/globalPlugins/webAccess/gui/rule/actions.py similarity index 97% rename from addon/globalPlugins/webAccess/gui/actions.py rename to addon/globalPlugins/webAccess/gui/rule/actions.py index 4c43a877..a759d43d 100644 --- a/addon/globalPlugins/webAccess/gui/actions.py +++ b/addon/globalPlugins/webAccess/gui/rule/actions.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/actions.py +# globalPlugins/webAccess/gui/rule/actions.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -20,7 +20,7 @@ # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.08.25" +__version__ = "2024.08.30" __authors__ = ( "Shirley Noel ", "Julien Cochuyt ", @@ -39,10 +39,11 @@ import gui from gui import guiHelper -from ..ruleHandler import ruleTypes -from ..utils import guarded -from . import ContextualSettingsPanel, Change, gestureBinding -from .rule.abc import RuleAwarePanelBase +from ...ruleHandler import ruleTypes +from ...utils import guarded +from .. import ContextualSettingsPanel, Change +from . import gestureBinding +from .abc import RuleAwarePanelBase addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/criteriaEditor.py b/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py similarity index 97% rename from addon/globalPlugins/webAccess/gui/criteriaEditor.py rename to addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py index 9708709d..c7a7a7e1 100644 --- a/addon/globalPlugins/webAccess/gui/criteriaEditor.py +++ b/addon/globalPlugins/webAccess/gui/rule/criteriaEditor.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/criteriaEditor.py +# globalPlugins/webAccess/gui/rule/criteria.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -21,7 +21,7 @@ -__version__ = "2024.08.29" +__version__ = "2024.08.30" __authors__ = ( "Shirley Noël ", "Julien Cochuyt ", @@ -47,9 +47,9 @@ import ui import addonHandler -from ..ruleHandler import builtinRuleActions, ruleTypes -from ..utils import guarded, notifyError, updateOrDrop -from . import ( +from ...ruleHandler import builtinRuleActions, ruleTypes +from ...utils import guarded, notifyError, updateOrDrop +from .. import ( ContextualMultiCategorySettingsDialog, ContextualSettingsPanel, DropDownWithHideableChoices, @@ -60,8 +60,9 @@ stripAccel, stripAccelAndColon, ) +from . import createMissingSubModule +from .abc import RuleAwarePanelBase from .actions import ActionsPanelBase -from .rule.abc import RuleAwarePanelBase from .properties import Properties, PropertiesPanelBase, Property @@ -237,7 +238,7 @@ def testCriteria(context): ruleData.setdefault("properties", {})["multiple"] = True critData.setdefault("properties", {}).pop("multiple", None) mgr = context["webModule"].ruleManager - from ..ruleHandler import Rule + from ...ruleHandler import Rule rule = Rule(mgr, ruleData) import time start = time.time() @@ -255,7 +256,9 @@ def testCriteria(context): class CriteriaEditorPanel(RuleAwarePanelBase): def getData(self): - return self.context["data"].setdefault("criteria", {}) + # Should always be initialized, as the Rule Editor populates it with at least + # the index of this Alternative Criteria Set ("criteriaIndex"). + return self.context["data"]["criteria"] class GeneralPanel(CriteriaEditorPanel): @@ -1015,6 +1018,11 @@ class CriteriaEditorDialog(ContextualMultiCategorySettingsDialog): categoryClasses = [GeneralPanel, CriteriaPanel, ActionsPanel, PropertiesPanel] INITIAL_SIZE = (900, 580) + def getData(self): + # Should always be initialized, as the Rule Editor populates it with at least + # the index of this Alternative Criteria Set ("criteriaIndex"). + return self.context["data"]["criteria"] + def makeSettings(self, settingsSizer): super().makeSettings(settingsSizer) idTestCriteria = wx.NewId() @@ -1026,8 +1034,13 @@ def makeSettings(self, settingsSizer): def onTestCriteria(self, evt): self.currentCategory.updateData() testCriteria(self.context) + + def _saveAllPanels(self): + super()._saveAllPanels() + if createMissingSubModule(self.context, self.getData(), self) is False: + raise ValidationError() # Cancels closing of the dialog def show(context, parent=None): - from . import showContextualDialog + from .. import showContextualDialog return showContextualDialog(CriteriaEditorDialog, context, parent) diff --git a/addon/globalPlugins/webAccess/gui/rule/editor.py b/addon/globalPlugins/webAccess/gui/rule/editor.py index ebfac438..6c9eb554 100644 --- a/addon/globalPlugins/webAccess/gui/rule/editor.py +++ b/addon/globalPlugins/webAccess/gui/rule/editor.py @@ -20,7 +20,7 @@ # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.08.25" +__version__ = "2024.08.30" __authors__ = ( "Julien Cochuyt ", "Shirley Noël ", @@ -63,22 +63,21 @@ TreeMultiCategorySettingsDialog, TreeNodeInfo, ValidationError, - criteriaEditor, - gestureBinding, showContextualDialog, stripAccel, stripAccelAndColon, stripAccelAndColon, ) -from ..actions import ActionsPanelBase -from ..properties import ( +from . import createMissingSubModule, criteriaEditor, gestureBinding +from .abc import RuleAwarePanelBase +from .actions import ActionsPanelBase +from .properties import ( EditorType, Property, Properties, PropertiesPanelBase, SinglePropertyEditorPanelBase, ) -from .abc import RuleAwarePanelBase addonHandler.initTranslation() @@ -920,9 +919,9 @@ def initData(self, context: Mapping[str, Any]) -> None: break node = node.parent super().initData(context) - - def _doSave(self): - super()._doSave() + + def _saveAllPanels(self): + super()._saveAllPanels() context = self.context data = self.getData() mgr = context["webModule"].ruleManager @@ -933,14 +932,17 @@ def _doSave(self): layerName = rule.layer webModule = webModuleHandler.getEditableWebModule(mgr.webModule, layerName=layerName) if not webModule: - return + raise ValidationError() # Cancels closing of the dialog + if createMissingSubModule(context, data, self) is False: + raise ValidationError() # Cancels closing of the dialog if context.get("new"): layerName = webModule.getWritableLayer().name else: mgr.removeRule(rule) context["rule"] = mgr.loadRule(layerName, data["name"], data) webModule.getLayer(layerName, raiseIfMissing=True).dirty = True - webModuleHandler.save(webModule, layerName=layerName) + if not webModuleHandler.save(webModule, layerName=layerName): + raise ValidationError() # Cancels closing of the dialog def show(context, parent=None): diff --git a/addon/globalPlugins/webAccess/gui/gestureBinding.py b/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py similarity index 97% rename from addon/globalPlugins/webAccess/gui/gestureBinding.py rename to addon/globalPlugins/webAccess/gui/rule/gestureBinding.py index c48f1ad4..a42444d8 100644 --- a/addon/globalPlugins/webAccess/gui/gestureBinding.py +++ b/addon/globalPlugins/webAccess/gui/rule/gestureBinding.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/gestureBinding.py +# globalPlugins/webAccess/gui/rule/gestureBinding.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -20,7 +20,7 @@ # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.08.25" +__version__ = "2024.08.30" __authors__ = ( "Shirley Noel ", "Frédéric Brugnot ", @@ -42,8 +42,8 @@ import speech import ui -from ..utils import guarded, logException -from . import ScalingMixin, showContextualDialog +from ...utils import guarded, logException +from .. import ScalingMixin, showContextualDialog addonHandler.initTranslation() diff --git a/addon/globalPlugins/webAccess/gui/properties.py b/addon/globalPlugins/webAccess/gui/rule/properties.py similarity index 97% rename from addon/globalPlugins/webAccess/gui/properties.py rename to addon/globalPlugins/webAccess/gui/rule/properties.py index d3d596a4..1fdeefd8 100644 --- a/addon/globalPlugins/webAccess/gui/properties.py +++ b/addon/globalPlugins/webAccess/gui/rule/properties.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/properties.py +# globalPlugins/webAccess/gui/rule/properties.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -19,9 +19,10 @@ # # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.08.29" +__version__ = "2024.08.30" __author__ = "Sendhil Randon " + from collections import ChainMap from collections.abc import Iterator, Mapping from abc import abstractmethod @@ -35,10 +36,10 @@ import speech import ui -from ..ruleHandler.controlMutation import MUTATIONS_BY_RULE_TYPE, mutationLabels -from ..ruleHandler.properties import PropertiesBase, PropertySpec, PropertySpecValue, PropertyValue -from ..utils import guarded, logException -from . import ContextualSettingsPanel, EditorType, ListCtrlAutoWidth, SingleFieldEditorMixin +from ...ruleHandler.controlMutation import MUTATIONS_BY_RULE_TYPE, mutationLabels +from ...ruleHandler.properties import PropertiesBase, PropertySpec, PropertySpecValue, PropertyValue +from ...utils import guarded, logException +from .. import ContextualSettingsPanel, EditorType, ListCtrlAutoWidth, SingleFieldEditorMixin addonHandler.initTranslation() @@ -144,7 +145,7 @@ def suggestions(self): if name in cache: return cache[name] if name == "subModule": - from ..webModuleHandler import getCatalog + from ...webModuleHandler import getCatalog suggestions = tuple(sorted({meta["name"] for ref, meta in getCatalog()})) else: raise ValueError(f"prop.name: {name!r}") diff --git a/addon/globalPlugins/webAccess/gui/webModule/__init__.py b/addon/globalPlugins/webAccess/gui/webModule/__init__.py new file mode 100644 index 00000000..5fce5d11 --- /dev/null +++ b/addon/globalPlugins/webAccess/gui/webModule/__init__.py @@ -0,0 +1,83 @@ +# globalPlugins/webAccess/gui/rule/__init__.py +# -*- coding: utf-8 -*- + +# This file is part of Web Access for NVDA. +# Copyright (C) 2015-2024 Accessolutions (http://accessolutions.fr) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# See the file COPYING.txt at the root of this distribution for more details. + + +__version__ = "2024.08.30" +__author__ = "Julien Cochuyt " + + +from collections.abc import Mapping +from typing import Any + +import os +import wx + +import addonHandler +import config +import gui + +from ...webModuleHandler import WebModule + + +addonHandler.initTranslation() + + +def promptDelete(webModule: WebModule): + msg = ( + # Translators: Prompt before deleting a web module. + _("Do you really want to delete this web module?") + + os.linesep + + str(webModule.name) + ) + if config.conf["webAccess"]["devMode"]: + msg += " ({})".format("/".join((layer.name for layer in webModule.layers))) + return gui.messageBox( + parent=gui.mainFrame, + message=msg, + style=wx.YES_NO | wx.CANCEL | wx.NO_DEFAULT | wx.ICON_WARNING + ) == wx.YES + + +def promptMask(webModule: WebModule): + ref = webModule.getLayer("addon", raiseIfMissing=True).storeRef + if ref[0] != "addons": + raise ValueError("ref={!r}".format(ref)) + addonName = ref[1] + for addon in addonHandler.getRunningAddons(): + if addon.name == addonName: + addonSummary = addon.manifest["summary"] + break + else: + raise LookupError("addonName={!r}".format(addonName)) + log.info("Proposing to mask {!r} from addon {!r}".format(webModule, addonName)) + msg = _( + """This web module comes with the add-on {addonSummary}. +It cannot be modified at its current location. + +Do you want to make a copy in your scratchpad? +""" + ).format(addonSummary=addonSummary) + return gui.messageBox( + parent=gui.mainFrame, + message=msg, + caption=_("Warning"), + style=wx.ICON_WARNING | wx.YES | wx.NO + ) == wx.YES diff --git a/addon/globalPlugins/webAccess/gui/webModuleEditor.py b/addon/globalPlugins/webAccess/gui/webModuleEditor.py deleted file mode 100644 index c54ff6f1..00000000 --- a/addon/globalPlugins/webAccess/gui/webModuleEditor.py +++ /dev/null @@ -1,293 +0,0 @@ -# globalPlugins/webAccess/gui/webModuleEditor.py -# -*- coding: utf-8 -*- - -# This file is part of Web Access for NVDA. -# Copyright (C) 2015-2024 Accessolutions (http://accessolutions.fr) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# See the file COPYING.txt at the root of this distribution for more details. - - -__version__ = "2021.07.25" -__author__ = ( - "Yannick Plassiard " - "Frédéric Brugnot " - "Julien Cochuyt " -) - - -import itertools -import os -import wx - -from NVDAObjects import NVDAObject, IAccessible -import addonHandler -import api -import controlTypes -import config -import gui -from gui import guiHelper -from logHandler import log -import ui - -from ..webModuleHandler import WebModule, getEditableWebModule, getUrl, getWindowTitle, save -from . import ContextualDialog, showContextualDialog - - -addonHandler.initTranslation() - - -def promptOverwrite(): - return wx.MessageBox( - parent=gui.mainFrame, - message=( - # Translators: Error message while naming a web module. - _("A web module already exists with this name.") - + os.linesep - # Translators: Prompt before overwriting a web module. - + _("Do you want to overwrite it?") - ), - style=wx.YES_NO|wx.ICON_WARNING, - ) == wx.YES - - -def show(context): - gui.mainFrame.prePopup() - result = Dialog(gui.mainFrame).ShowModal(context) - gui.mainFrame.postPopup() - return result == wx.ID_OK - - -class Dialog(ContextualDialog): - - def __init__(self, parent): - super().__init__( - parent, - style=wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER, - ) - scale = self.scale - mainSizer = wx.BoxSizer(wx.VERTICAL) - gbSizer = wx.GridBagSizer() - mainSizer.Add( - gbSizer, - proportion=1, - flag=wx.EXPAND | wx.ALL, - border=guiHelper.BORDER_FOR_DIALOGS - ) - - row = 0 - # Translators: The label for a field in the WebModule editor - item = wx.StaticText(self, label=_("Web module name:")) - gbSizer.Add(item, pos=(row, 0)) - gbSizer.Add(scale(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL, 0), pos=(row, 1)) - item = self.webModuleName = wx.TextCtrl(self) - gbSizer.Add(item, pos=(row, 2), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_VERTICAL), pos=(row, 0)) - - row += 1 - # Translators: The label for a field in the WebModule editor - item = wx.StaticText(self, label=_("URL:")) - gbSizer.Add(item, pos=(row, 0)) - gbSizer.Add(scale(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL, 0), pos=(row, 1)) - item = self.webModuleUrl = wx.ComboBox(self, choices=[]) - gbSizer.Add(item, pos=(row, 2), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_VERTICAL), pos=(row, 0)) - - row += 1 - # Translators: The label for a field in the WebModule editor - item = wx.StaticText(self, label=_("Window title:")) - gbSizer.Add(item, pos=(row, 0)) - gbSizer.Add(scale(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL, 0), pos=(row, 1)) - item = self.webModuleWindowTitle = wx.ComboBox(self, choices=[]) - gbSizer.Add(item, pos=(row, 2), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_VERTICAL), pos=(row, 0)) - - row += 1 - # Translators: The label for a field in the WebModule editor - item = wx.StaticText(self, label=_("Help (in Markdown):")) - gbSizer.Add(item, pos=(row, 0)) - gbSizer.Add(scale(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL, 0), pos=(row, 1)) - item = self.help = wx.TextCtrl(self, style=wx.TE_MULTILINE) - gbSizer.Add(item, pos=(row, 2), flag=wx.EXPAND) - - gbSizer.AddGrowableCol(2) - gbSizer.AddGrowableRow(row) - - mainSizer.Add( - self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL), - flag=wx.EXPAND | wx.TOP | wx.DOWN, - border=scale(guiHelper.BORDER_FOR_DIALOGS), - ) - self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK) - self.SetSize(scale(790, 400)) - self.SetSizer(mainSizer) - self.CentreOnScreen() - self.webModuleName.SetFocus() - - def initData(self, context): - super().initData(context) - data = context.setdefault("data", {})["webModule"] = {} - if not context.get("new"): - webModule = context.get("webModule") - data.update(webModule.dump(webModule.layers[-1].name).data["WebModule"]) - # Translators: Web module edition dialog title - title = _("Edit Web Module") - if config.conf["webAccess"]["devMode"]: - title += " ({})".format("/".join((layer.name for layer in webModule.layers))) - else: - # Translators: Web module creation dialog title - title = _("New Web Module") - if config.conf["webAccess"]["devMode"]: - from .. import webModuleHandler - try: - guineaPig = getEditableWebModule(WebModule(), prompt=False) - store = next(iter(webModuleHandler.store.getSupportingStores( - "create", - item=guineaPig - ))) if guineaPig is not None else None - title += " ({})".format( - store and ("user" if store.name == "userConfig" else store.name) - ) - except Exception: - log.exception() - self.Title = title - - self.webModuleName.Value = data.get("name", "") - - urls = [] - selectedUrl = None - if data.get("url"): - url = selectedUrl = ", ".join(data["url"]) - for candidate in itertools.chain([url], data["url"]): - if candidate not in urls: - urls.append(candidate) - if "focusObject" in context: - focus = context["focusObject"] - if focus and focus.treeInterceptor and focus.treeInterceptor.rootNVDAObject: - urlFromObject = getUrl(focus.treeInterceptor.rootNVDAObject) - if not urlFromObject: - if context.get("new"): - ui.message(_("URL not found")) - elif urlFromObject not in urls: - urls.append(urlFromObject) - else: - log.warning("focusObject not in context") - urlsChoices = [] - for url in urls: - choice = url - urlChoices = [choice] - # Strip protocol - if "://" in choice: - choice = choice.split("://", 1)[-1] - urlChoices.insert(0, choice) - # Strip parameters - if "?" in choice: - choice = choice.rsplit('?', 1)[0] - urlChoices.insert(0, choice) - # Strip directories - while "/" in choice[1:]: - choice = choice.rsplit('/', 1)[0] - urlChoices.insert(0, choice) - # Reverse order for local resources (most specific first) - if url.startswith("file:") or url.startswith("/"): - urlChoices.reverse() - urlsChoices.extend(urlChoices) - urlsChoicesSet = set() - urlsChoices = [ - choice - for choice in urlsChoices - if not(choice in urlsChoicesSet or urlsChoicesSet.add(choice)) - ] - self.webModuleUrl.SetItems(urlsChoices) - self.webModuleUrl.Selection = ( - urlsChoices.index(selectedUrl) - if selectedUrl - else 0 - ) - - windowTitleChoices = [] - windowTitleIsFilled = False - if data.get("windowTitle"): - windowTitleIsFilled = True - windowTitleChoices.append(data["windowTitle"]) - if "focusObject" in context: - obj = context["focusObject"] - windowTitle = getWindowTitle(obj) - if windowTitle and windowTitle not in windowTitleChoices: - windowTitleChoices.append(windowTitle) - item = self.webModuleWindowTitle - item.SetItems(windowTitleChoices) - if windowTitleIsFilled: - item.Selection = 0 - else: - item.Value = "" - - self.help.Value = data.get("help", "") - - def onOk(self, evt): - name = self.webModuleName.Value.strip() - if len(name) < 1: - gui.messageBox( - _("You must enter a name for this web module"), - _("Error"), - wx.OK | wx.ICON_ERROR, - self - ) - self.webModuleName.SetFocus() - return - - url = [url.strip() for url in self.webModuleUrl.Value.split(",") if url.strip()] - windowTitle = self.webModuleWindowTitle.Value.strip() - help = self.help.Value.strip() - if not (url or windowTitle): - gui.messageBox( - _("You must specify at least a URL or a window title."), - _("Error"), - wx.OK | wx.ICON_ERROR, - self, - ) - self.webModuleUrl.SetFocus() - return - - context = self.context - if context.get("new"): - webModule = WebModule() - else: - webModule = context["webModule"] - if webModule.isReadOnly(): - webModule = getEditableWebModule(webModule) - if not webModule: - return - - webModule.name = name - webModule.url = url - webModule.windowTitle = windowTitle - webModule.help = help - - if not save(webModule, prompt=self.Title): - return - - self.DestroyLater() - self.SetReturnCode(wx.ID_OK) - - -def show(context, parent=None): - return showContextualDialog(Dialog, context, parent or gui.mainFrame) == wx.ID_OK diff --git a/addon/globalPlugins/webAccess/gui/webModulesManager.py b/addon/globalPlugins/webAccess/gui/webModulesManager.py deleted file mode 100644 index 609d277c..00000000 --- a/addon/globalPlugins/webAccess/gui/webModulesManager.py +++ /dev/null @@ -1,294 +0,0 @@ -# globalPlugins/webAccess/gui/webModulesManager.py -# -*- coding: utf-8 -*- - -# This file is part of Web Access for NVDA. -# Copyright (C) 2015-2024 Accessolutions (http://accessolutions.fr) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# See the file COPYING.txt at the root of this distribution for more details. - - -__version__ = "2024.08.25" -__authors__ = ( - "Julien Cochuyt ", - "André-Abush Clause ", - "Gatien Bouyssou ", -) - - -import os -import wx - -import addonHandler -addonHandler.initTranslation() -import config -import core -import globalVars -import gui -from gui import guiHelper -import languageHandler -from logHandler import log - -from ..utils import guarded -from . import ContextualDialog, ListCtrlAutoWidth, showContextualDialog - - -def promptDelete(webModule): - msg = ( - # Translators: Prompt before deleting a web module. - _("Do you really want to delete this web module?") - + os.linesep - + str(webModule.name) - ) - if config.conf["webAccess"]["devMode"]: - msg += " ({})".format("/".join((layer.name for layer in webModule.layers))) - return gui.messageBox( - parent=gui.mainFrame, - message=msg, - style=wx.YES_NO | wx.ICON_WARNING - ) == wx.YES - - -def promptMask(webModule): - ref = webModule.getLayer("addon", raiseIfMissing=True).storeRef - if ref[0] != "addons": - raise ValueError("ref={!r}".format(ref)) - addonName = ref[1] - for addon in addonHandler.getRunningAddons(): - if addon.name == addonName: - addonSummary = addon.manifest["summary"] - break - else: - raise LookupError("addonName={!r}".format(addonName)) - log.info("Proposing to mask {!r} from addon {!r}".format(webModule, addonName)) - msg = _( - """This web module comes with the add-on {addonSummary}. -It cannot be modified at its current location. - -Do you want to make a copy in your scratchpad? -""" - ).format(addonSummary=addonSummary) - return gui.messageBox( - parent=gui.mainFrame, - message=msg, - caption=_("Warning"), - style=wx.ICON_WARNING | wx.YES | wx.NO - ) == wx.YES - - -class Dialog(ContextualDialog): - - def __init__(self, parent): - super().__init__( - parent, - # Translators: The title of the Web Modules Manager dialog - title=_("Web Modules Manager"), - style=wx.DEFAULT_DIALOG_STYLE|wx.MAXIMIZE_BOX|wx.RESIZE_BORDER, - ) - scale = self.scale - mainSizer = wx.BoxSizer(wx.VERTICAL) - gbSizer = wx.GridBagSizer() - mainSizer.Add( - gbSizer, - border=scale(guiHelper.BORDER_FOR_DIALOGS), - flag=wx.ALL | wx.EXPAND, - proportion=1 - ) - row = 0 - col = 0 - item = modulesListLabel = wx.StaticText( - self, - # Translators: The label for the modules list in the - # Web Modules dialog. - label=_("Available Web Modules:"), - ) - gbSizer.Add(item, (row, col), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_VERTICAL), (row, col)) - - row += 1 - item = self.modulesList = ListCtrlAutoWidth(self, style=wx.LC_REPORT) - # Translators: The label for a column of the web modules list - item.InsertColumn(0, _("Name"), width=150) - # Translators: The label for a column of the web modules list - item.InsertColumn(1, _("Trigger")) - item.Bind( - wx.EVT_LIST_ITEM_FOCUSED, - self.onModulesListItemSelected) - gbSizer.Add(item, (row, col), span=(8, 1), flag=wx.EXPAND) - gbSizer.AddGrowableRow(row+7) - gbSizer.AddGrowableCol(col) - - col += 1 - gbSizer.Add(scale(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL, 0), (row, col)) - - col += 1 - item = self.moduleCreateButton = wx.Button( - self, - # Translators: The label for a button in the Web Modules Manager - label=_("&New web module..."), - ) - item.Bind(wx.EVT_BUTTON, self.onModuleCreate) - gbSizer.Add(item, (row, col), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS), (row, col)) - - row += 1 - # Translators: The label for a button in the Web Modules Manager dialog - item = self.moduleEditButton = wx.Button(self, label=_("&Edit...")) - item.Disable() - item.Bind(wx.EVT_BUTTON, self.onModuleEdit) - gbSizer.Add(item, (row, col), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS), (row, col)) - - row += 1 - # Translators: The label for a button in the Web Modules Manager dialog - item = self.rulesManagerButton = wx.Button(self, label=_("Manage &rules...")) - item.Disable() - item.Bind(wx.EVT_BUTTON, self.onRulesManager) - gbSizer.Add(item, (row, col), flag=wx.EXPAND) - - row += 1 - gbSizer.Add(scale(0, guiHelper.SPACE_BETWEEN_VERTICAL_DIALOG_ITEMS), (row, col)) - - row += 1 - item = self.moduleDeleteButton = wx.Button( - self, - # Translators: The label for a button in the - # Web Modules Manager dialog - label=_("&Delete")) - item.Disable() - item.Bind(wx.EVT_BUTTON, self.onModuleDelete) - gbSizer.Add(item, (row, col), flag=wx.EXPAND) - - mainSizer.Add( - self.CreateSeparatedButtonSizer(wx.CLOSE), - flag=wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, - border=scale(guiHelper.BORDER_FOR_DIALOGS), - ) - self.SetSize(scale(790, 400)) - self.SetSizer(mainSizer) - self.CentreOnScreen() - self.modulesList.SetFocus() - - def __del__(self): - Dialog._instance = None - - def initData(self, context): - super().initData(context) - module = context["webModule"] if "webModule" in context else None - self.refreshModulesList(selectItem=module) - - @guarded - def onModuleCreate(self, evt=None): - context = self.context.copy() - context["new"] = True - from .webModuleEditor import show - if show(context, self): - self.refreshModulesList(selectItem=context["webModule"]) - - @guarded - def onModuleDelete(self, evt=None): - index = self.modulesList.GetFirstSelected() - if index < 0: - return - webModule = self.modules[index] - from .. import webModuleHandler - if webModuleHandler.delete(webModule=webModule): - self.refreshModulesList() - - @guarded - def onModuleEdit(self, evt=None): - index = self.modulesList.GetFirstSelected() - if index < 0: - wx.Bell() - return - context = self.context - context.pop("new", None) - context["webModule"] = self.modules[index] - from .webModuleEditor import show - if show(context, self): - self.refreshModulesList(selectIndex=index) - - @guarded - def onModulesListItemSelected(self, evt): - self.refreshButtons() - - @guarded - def onRulesManager(self, evt=None): - index = self.modulesList.GetFirstSelected() - if index < 0: - wx.Bell() - return - webModule = self.modules[index] - context = self.context - if not webModule.equals(context.get("webModule")): - context["webModule"] = webModule - context.pop("result", None) - from .rule.manager import show - show(context, self) - - def refreshButtons(self): - index = self.modulesList.GetFirstSelected() - hasSelection = index >= 0 - self.moduleEditButton.Enable(hasSelection) - self.rulesManagerButton.Enable(hasSelection) - self.moduleDeleteButton.Enable(hasSelection) - - def refreshModulesList(self, selectIndex: int = None, selectItem: "WebModule" = None): - 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 - module = contextModules.get((module.name, module.layers[0].storeRef), module) - trigger = (" %s " % _("and")).join( - ([ - "url=%s" % url - for url in (module.url if module.url else []) - ]) + ( - ["title=%s" % module.windowTitle] - if module.windowTitle else [] - ) - ) - ctrl.Append(( - module.name, - trigger, - )) - modules.append(module) - - if selectIndex is None: - selectIndex = min(0, len(modules) - 1) - else: - selectIndex %= len(modules) - if selectIndex >= 0: - ctrl.Select(selectIndex, on=1) - ctrl.Focus(selectIndex) - self.refreshButtons() - - -def show(context): - showContextualDialog(Dialog, context, gui.mainFrame) diff --git a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py index 2dd9cc94..3dfd62bd 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py @@ -22,7 +22,7 @@ """Web Access GUI.""" -__version__ = "2024.08.25" +__version__ = "2024.08.30" __author__ = "Julien Cochuyt " @@ -57,7 +57,7 @@ def delete(webModule, prompt=True): if prompt: - from ..gui.webModulesManager import promptDelete + from ..gui.webModule import promptDelete if not promptDelete(webModule): return False store.delete(webModule) @@ -228,8 +228,8 @@ def save(webModule, layerName=None, prompt=True, force=False, fromRuleEditor=Fal except DuplicateRefError as e: if not prompt or force: return False - from ..gui import webModuleEditor - if webModuleEditor.promptOverwrite(): + from ..gui.webModule.editor import promptOverwrite + if promptOverwrite(): return save(webModule, layerName=layerName, prompt=prompt, force=True) return False except MalformedRefError: @@ -348,7 +348,7 @@ def _getEditableScratchpadWebModule(webModule, layerName=None, prompt=True): if layerName != "addon": return None if prompt: - from ..gui.webModulesManager import promptMask + from ..gui.webModule import promptMask if not promptMask(webModule): return False data = webModule.dump(layerName).data