From 2d95649c6f0b157fdd66c49b86922a01ec867e72 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Fri, 30 Aug 2024 17:41:37 +0200 Subject: [PATCH] SubModules: GUI: Editor: Prompt for creation of a missing SubModule - WebModule Editor: Do not require a trigger when creating from the Rule Editor - WebModule Manager: Add key shortcuts `F2`, `F3` and `delete` to edit, manage rules and delete, respectively (#38) - WebModule Manager: Set "no" as the default when prompting for deletion - Refactor `gui.actions` into `gui.rule.actions` - Refactor `gui.criteriaEditor` into `gui.rule.criteriaEditor` - Refactor `gui.gestureBinding` into `gui.rule.gestureBinding` - Refactor `gui.properties` into `gui.rule.properties` - Refactor `gui.webModulesManager` into `gui.webModule.manager` - Refactor `gui.webModuleEditor` into `gui.webModule.editor` - Refactor `gui.webModuleManager.promptDelete` into `gui.webModule.promptDelete` - GUI: Refactor `gui.webModuleManager.promptMask` into `gui.webModule.promptMask` --- addon/globalPlugins/webAccess/gui/menu.py | 17 ++-- .../webAccess/gui/rule/__init__.py | 83 +++++++++++++++++ .../webAccess/gui/{ => rule}/actions.py | 11 ++- .../gui/{ => rule}/criteriaEditor.py | 31 +++++-- .../webAccess/gui/rule/editor.py | 22 +++-- .../gui/{ => rule}/gestureBinding.py | 6 +- .../webAccess/gui/{ => rule}/properties.py | 12 +-- .../webAccess/gui/webModule/__init__.py | 82 ++++++++++++++++ .../editor.py} | 57 ++++++++---- .../manager.py} | 93 +++++++------------ .../webAccess/webModuleHandler/__init__.py | 8 +- 11 files changed, 297 insertions(+), 125 deletions(-) rename addon/globalPlugins/webAccess/gui/{ => rule}/actions.py (97%) rename addon/globalPlugins/webAccess/gui/{ => rule}/criteriaEditor.py (97%) rename addon/globalPlugins/webAccess/gui/{ => rule}/gestureBinding.py (97%) rename addon/globalPlugins/webAccess/gui/{ => rule}/properties.py (97%) create mode 100644 addon/globalPlugins/webAccess/gui/webModule/__init__.py rename addon/globalPlugins/webAccess/gui/{webModuleEditor.py => webModule/editor.py} (83%) rename addon/globalPlugins/webAccess/gui/{webModulesManager.py => webModule/manager.py} (79%) diff --git a/addon/globalPlugins/webAccess/gui/menu.py b/addon/globalPlugins/webAccess/gui/menu.py index 7bd23ae8..b02f1cbe 100644 --- a/addon/globalPlugins/webAccess/gui/menu.py +++ b/addon/globalPlugins/webAccess/gui/menu.py @@ -38,7 +38,6 @@ from ... import webAccess from .. import ruleHandler from ..utils import guarded -from . import webModulesManager addonHandler.initTranslation() @@ -150,21 +149,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..86778698 100644 --- a/addon/globalPlugins/webAccess/gui/rule/__init__.py +++ b/addon/globalPlugins/webAccess/gui/rule/__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. + + +__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 dc83da66..35c68bfa 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. @@ -38,10 +38,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 e94aa9d8..acdb385f 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 ", @@ -48,9 +48,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, @@ -61,8 +61,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 @@ -239,7 +240,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() @@ -257,7 +258,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): @@ -1017,6 +1020,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() @@ -1028,8 +1036,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 2fda1008..4357aa12 100644 --- a/addon/globalPlugins/webAccess/gui/rule/editor.py +++ b/addon/globalPlugins/webAccess/gui/rule/editor.py @@ -62,22 +62,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() @@ -933,9 +932,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 @@ -946,14 +945,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 68f069a1..9e22ac1d 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. @@ -41,8 +41,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 9d585fcc..107db56f 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. @@ -36,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() @@ -145,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..5ce7b650 --- /dev/null +++ b/addon/globalPlugins/webAccess/gui/webModule/__init__.py @@ -0,0 +1,82 @@ +# 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. + + +__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/webModule/editor.py similarity index 83% rename from addon/globalPlugins/webAccess/gui/webModuleEditor.py rename to addon/globalPlugins/webAccess/gui/webModule/editor.py index e834be42..6326a9ab 100644 --- a/addon/globalPlugins/webAccess/gui/webModuleEditor.py +++ b/addon/globalPlugins/webAccess/gui/webModule/editor.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/webModuleEditor.py +# globalPlugins/webAccess/gui/webModule/editor.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -23,12 +23,16 @@ __author__ = ( "Yannick Plassiard " "Frédéric Brugnot " - "Julien Cochuyt " + "Julien Cochuyt ", + "André-Abush Clause ", + "Gatien Bouyssou ", ) +from collections.abc import Mapping import itertools import os +from typing import Any import wx from NVDAObjects import NVDAObject, IAccessible @@ -41,8 +45,9 @@ from logHandler import log import ui -from ..webModuleHandler import WebModule, getEditableWebModule, getUrl, getWindowTitle, save -from . import ContextualDialog, showContextualDialog +from ...utils import guarded +from ...webModuleHandler import WebModule, getEditableWebModule, getUrl, getWindowTitle, save, store +from .. import ContextualDialog, showContextualDialog addonHandler.initTranslation() @@ -140,30 +145,38 @@ def __init__(self, parent): self.SetSizer(mainSizer) self.CentreOnScreen() self.webModuleName.SetFocus() - + + def getData(self) -> Mapping[str, Any]: + return self.context["data"]["webModule"] + def initData(self, context): super().initData(context) - data = context.setdefault("data", {})["webModule"] = {} + data = context.setdefault("data", {}).setdefault("webModule", {}) if not context.get("new"): webModule = context.get("webModule") data.update(webModule.dump(webModule.layers[-1].name).data["WebModule"]) + # Could not have been saved without triggers from this editor unless invoked + # from the Rule or Criteria editor. + subModule = data["subModule"] = not (data.get("url") or data.get("windowTitle")) # 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: + subModule = data.get("subModule", False) # 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( + supportingStore = next(iter(store.getSupportingStores( "create", item=guineaPig ))) if guineaPig is not None else None title += " ({})".format( - store and ("user" if store.name == "userConfig" else store.name) + supportingStore and ( + "user" if supportingStore.name == "userConfig" else supportingStore.name + ) ) except Exception: log.exception() @@ -217,9 +230,7 @@ def initData(self, context): ] self.webModuleUrl.SetItems(urlsChoices) self.webModuleUrl.Selection = ( - urlsChoices.index(selectedUrl) - if selectedUrl - else 0 + urlsChoices.index(selectedUrl) if selectedUrl else 0 if not subModule else -1 ) windowTitleChoices = [] @@ -240,9 +251,18 @@ def initData(self, context): item.Value = "" self.help.Value = data.get("help", "") - + + def updateData(self): + data = self.getData() + data["name"] = self.webModuleName.Value.strip() + data["url"] = [url.strip() for url in self.webModuleUrl.Value.split(",") if url.strip()] + data["windowTitle"] = self.webModuleWindowTitle.Value.strip() + + @guarded def onOk(self, evt): - name = self.webModuleName.Value.strip() + self.updateData() + data = self.getData() + name = data["name"] if len(name) < 1: gui.messageBox( _("You must enter a name for this web module"), @@ -253,10 +273,11 @@ def onOk(self, evt): self.webModuleName.SetFocus() return - url = [url.strip() for url in self.webModuleUrl.Value.split(",") if url.strip()] - windowTitle = self.webModuleWindowTitle.Value.strip() + subModule: bool = data.get("subModule", False) + url = data["url"] + windowTitle = data["windowTitle"] help = self.help.Value.strip() - if not (url or windowTitle): + if not (url or windowTitle or subModule): gui.messageBox( _("You must specify at least a URL or a window title."), _("Error"), @@ -283,7 +304,7 @@ def onOk(self, evt): if not save(webModule, prompt=self.Title): return - + context["webModule"] = webModule self.DestroyLater() self.SetReturnCode(wx.ID_OK) diff --git a/addon/globalPlugins/webAccess/gui/webModulesManager.py b/addon/globalPlugins/webAccess/gui/webModule/manager.py similarity index 79% rename from addon/globalPlugins/webAccess/gui/webModulesManager.py rename to addon/globalPlugins/webAccess/gui/webModule/manager.py index 011fc130..70817063 100644 --- a/addon/globalPlugins/webAccess/gui/webModulesManager.py +++ b/addon/globalPlugins/webAccess/gui/webModule/manager.py @@ -1,4 +1,4 @@ -# globalPlugins/webAccess/gui/webModulesManager.py +# globalPlugins/webAccess/gui/webModule/manager.py # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. @@ -27,64 +27,16 @@ ) -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 +from ...utils import guarded +from .. import ContextualDialog, ListCtrlAutoWidth, showContextualDialog class Dialog(ContextualDialog): @@ -181,24 +133,42 @@ def __init__(self, parent): flag=wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, border=scale(guiHelper.BORDER_FOR_DIALOGS), ) + self.Bind(wx.EVT_CHAR_HOOK, self.onCharHook) 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 onCharHook(self, evt): + keycode = evt.GetKeyCode() + if keycode == wx.WXK_ESCAPE: + # Try to limit the difficulty of closing the dialog using the keyboard + # in the event of an error later in this function + evt.Skip() + return + elif keycode == wx.WXK_DELETE: + self.onModuleDelete(None) + return + elif keycode == wx.WXK_F2: + self.onModuleEdit(None) + return + elif keycode == wx.WXK_F3: + self.onRulesManager(None) + return + evt.Skip() + + @guarded def onModuleCreate(self, evt=None): context = self.context.copy() context["new"] = True - from .webModuleEditor import show + from .editor import show if show(context, self): self.refreshModulesList(selectItem=context["webModule"]) @@ -206,10 +176,11 @@ def onModuleCreate(self, evt=None): def onModuleDelete(self, evt=None): index = self.modulesList.GetFirstSelected() if index < 0: + wx.Bell() return webModule = self.modules[index] - from .. import webModuleHandler - if webModuleHandler.delete(webModule=webModule): + from ...webModuleHandler import delete + if delete(webModule=webModule): self.refreshModulesList() @guarded @@ -221,7 +192,7 @@ def onModuleEdit(self, evt=None): context = self.context context.pop("new", None) context["webModule"] = self.modules[index] - from .webModuleEditor import show + from .editor import show if show(context, self): self.refreshModulesList(selectIndex=index) @@ -240,7 +211,7 @@ def onRulesManager(self, evt=None): if not webModule.equals(context.get("webModule")): context["webModule"] = webModule context.pop("result", None) - from .rule.manager import show + from ..rule.manager import show show(context, self) def refreshButtons(self): @@ -259,8 +230,8 @@ def refreshModulesList(self, selectIndex: int = None, selectItem: "WebModule" = for module in list(reversed(contextModule.ruleManager.subModules.all())) + [contextModule] } if contextModule else {} modules = self.modules = [] - from .. import webModuleHandler - for index, module in enumerate(webModuleHandler.getWebModules()): + from ...webModuleHandler import getWebModules + for index, module in enumerate(getWebModules()): if selectIndex is None and module.equals(selectItem): selectIndex = index module = contextModules.get((module.name, module.layers[0].storeRef), module) diff --git a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py index ce709d6d..a7a90d60 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/__init__.py @@ -56,7 +56,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) @@ -227,8 +227,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: @@ -347,7 +347,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