From 8e1bea5c05b5536bd2f4416bd7715ff3f169c9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9-Abush=20Clause?= Date: Fri, 29 May 2020 12:20:48 +0200 Subject: [PATCH] Braille tables improvements: groups, settings, etc. --- .../globalPlugins/brailleExtender/__init__.py | 95 +-- .../brailleExtender/brailleTablesExt.py | 578 ++++++++++++++++++ .../globalPlugins/brailleExtender/configBE.py | 55 +- .../brailleExtender/dictionaries.py | 58 +- .../globalPlugins/brailleExtender/settings.py | 254 +------- .../brailleExtender/undefinedChars.py | 19 +- addon/globalPlugins/brailleExtender/utils.py | 34 +- 7 files changed, 716 insertions(+), 377 deletions(-) create mode 100644 addon/globalPlugins/brailleExtender/brailleTablesExt.py diff --git a/addon/globalPlugins/brailleExtender/__init__.py b/addon/globalPlugins/brailleExtender/__init__.py index ade7faee..389ef98f 100644 --- a/addon/globalPlugins/brailleExtender/__init__.py +++ b/addon/globalPlugins/brailleExtender/__init__.py @@ -49,6 +49,7 @@ from . import utils from .updateCheck import * from . import advancedInputMode +from . import brailleTablesExt from . import dictionaries from . import huc from . import patchs @@ -173,8 +174,7 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin): _pGestures = OrderedDict() rotorGES = {} noKC = None - if not configBE.noUnicodeTable: - backupInputTable = brailleInput.handler.table + backupInputTable = brailleInput.handler.table backupMessageTimeout = None backupShowCursor = False backupTether = utils.getTether() @@ -201,13 +201,13 @@ def __init__(self): self.backup__addTextWithFields = braille.TextInfoRegion._addTextWithFields self.backup__update = braille.TextInfoRegion.update self.backup__getTypeformFromFormatField = braille.TextInfoRegion._getTypeformFromFormatField - self.backup__brailleTableDict = config.conf["braille"]["translationTable"] braille.TextInfoRegion._addTextWithFields = decorator(braille.TextInfoRegion._addTextWithFields, "addTextWithFields") braille.TextInfoRegion.update = decorator(braille.TextInfoRegion.update, "update") braille.TextInfoRegion._getTypeformFromFormatField = decorator(braille.TextInfoRegion._getTypeformFromFormatField, "_getTypeformFromFormatField") if config.conf["brailleExtender"]["reverseScrollBtns"]: self.reverseScrollBtns() self.createMenu() advancedInputMode.initialize() + brailleTablesExt.initializeGroups() log.info(f"{addonName} {addonVersion} loaded ({round(time.time()-startTime, 2)}s)") def event_gainFocus(self, obj, nextHandler): @@ -247,7 +247,6 @@ def event_gainFocus(self, obj, nextHandler): configBE.curBD = braille.handler.display.name self.onReload(None, 1) - if self.backup__brailleTableDict != config.conf["braille"]["translationTable"]: self.reloadBrailleTables() nextHandler() return @@ -304,7 +303,6 @@ def createMenu(self): self.submenu_item = gui.mainFrame.sysTrayIcon.menu.InsertMenu(2, wx.ID_ANY, "%s (%s)" % (_("&Braille Extender"), addonVersion), self.submenu) def reloadBrailleTables(self): - self.backup__brailleTableDict = config.conf["braille"]["translationTable"] dictionaries.setDictTables() dictionaries.notifyInvalidTables() if config.conf["brailleExtender"]["tabSpace"]: @@ -320,7 +318,7 @@ def onDefaultDictionary(evt): @staticmethod def onTableDictionary(evt): - outTable = configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])] + outTable = brailleTablesExt.listTablesDisplayName()[brailleTablesExt.listTablesFileName().index(config.conf["braille"]["translationTable"])] gui.mainFrame._popupSettingsDialog(dictionaries.DictionaryDlg, _("Table dictionary")+(" (%s)" % outTable), "table") @staticmethod @@ -628,7 +626,7 @@ def script_reportExtraInfos(self, gesture): def script_getTableOverview(self, gesture): inTable = brailleInput.handler.table.displayName - ouTable = configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])] + ouTable = brailleTablesExt.listTablesDisplayName()[brailleTablesExt.listTablesFileName().index(config.conf["braille"]["translationTable"])] t = (_(" Input table")+": %s\n"+_("Output table")+": %s\n\n") % (inTable+' (%s)' % (brailleInput.handler.table.fileName), ouTable+' (%s)' % (config.conf["braille"]["translationTable"])) t += utils.getTableOverview() ui.browseableMessage("
%s
" % t, _("Table overview (%s)" % brailleInput.handler.table.displayName), True) @@ -855,54 +853,70 @@ def script_decreaseDelayAutoScroll(self, gesture): script_decreaseDelayAutoScroll.__doc__ = _("Decrease autoscroll delay") def script_switchInputBrailleTable(self, gesture): - if configBE.noUnicodeTable: - return ui.message(_("Please use NVDA 2017.3 minimum for this feature")) - if len(configBE.inputTables) < 2: - return ui.message(_("You must choose at least two tables for this feature. Please fill in the settings")) - if not config.conf["braille"]["inputTable"] in configBE.inputTables: - configBE.inputTables.append(config.conf["braille"]["inputTable"]) - tid = configBE.inputTables.index(config.conf["braille"]["inputTable"]) - nID = tid + 1 if tid + 1 < len(configBE.inputTables) else 0 - brailleInput.handler.table = brailleTables.listTables( - )[configBE.tablesFN.index(configBE.inputTables[nID])] - ui.message(_("Input: %s") % brailleInput.handler.table.displayName) - return + usableIn = brailleTablesExt.USABLE_INPUT + choices = brailleTablesExt.getPreferredTables()[0] + brailleTablesExt.getGroups(0)[0] + if len(choices) < 2: + return ui.message(_("Please fill at least two tables and/or groups of tables for this feature first")) + newGroup = brailleTablesExt.getGroup( + position=brailleTablesExt.POSITION_NEXT, + usableIn=usableIn + ) + res = brailleTablesExt.setTableOrGroup( + usableIn=usableIn, + e=newGroup + ) + table = brailleTablesExt.getTable(newGroup.members[0] if newGroup else config.conf["braille"]["inputTable"]) + if table: brailleInput.handler._table = table + if not res: raise RuntimeError("error") + self.reloadBrailleTables() + utils.refreshBD() + dictionaries.setDictTables() + desc = (newGroup.name + (" (%s)" % _("group") if len(newGroup.members) > 1 else '') if newGroup else _("Default") + " (%s)" % brailleInput.handler.table.displayName) + ui.message(_("Input: %s") % desc) - script_switchInputBrailleTable.__doc__ = _( - "Switch between his favorite input braille tables") + script_switchInputBrailleTable.__doc__ = _("Switch between your favorite input braille tables including groups") def script_switchOutputBrailleTable(self, gesture): - if configBE.noUnicodeTable: - return ui.message( - _("Please use NVDA 2017.3 minimum for this feature")) - if len(configBE.outputTables) < 2: - return ui.message(_("You must choose at least two tables for this feature. Please fill in the settings")) - if not config.conf["braille"]["translationTable"] in configBE.outputTables: - configBE.outputTables.append(config.conf["braille"]["translationTable"]) - tid = configBE.outputTables.index( - config.conf["braille"]["translationTable"]) - nID = tid + 1 if tid + 1 < len(configBE.outputTables) else 0 - config.conf["braille"]["translationTable"] = configBE.outputTables[nID] + usableIn = brailleTablesExt.USABLE_OUTPUT + choices = brailleTablesExt.getPreferredTables()[1] + brailleTablesExt.getGroups(0)[1] + if len(choices) < 2: + return ui.message(_("Please fill at least two tables and/or groups of tables for this feature first")) + newGroup = brailleTablesExt.getGroup( + position=brailleTablesExt.POSITION_NEXT, + usableIn=usableIn + ) + res = brailleTablesExt.setTableOrGroup( + usableIn=usableIn, + e=newGroup + ) + if not res: raise RuntimeError("error") + self.reloadBrailleTables() utils.refreshBD() dictionaries.setDictTables() - ui.message(_("Output: %s") % configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])]) - return + desc = (newGroup.name + (" (%s)" % _("group") if len(newGroup.members) > 1 else '') if newGroup else (_("Default") + " (%s)" % brailleTablesExt.fileName2displayName([config.conf["braille"]["translationTable"]])[0])) + ui.message(_("Output: %s") % desc) - script_switchOutputBrailleTable.__doc__ = _("Switch between his favorite output braille tables") + script_switchOutputBrailleTable.__doc__ = _("Switch between your favorite output braille tables including groups") def script_currentBrailleTable(self, gesture): - inTable = brailleInput.handler.table.displayName - ouTable = configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])] + inTable = None + ouTable = None + if brailleTablesExt.groupEnabled(): + i = brailleTablesExt.getGroup(brailleTablesExt.USABLE_INPUT) + o = brailleTablesExt.getGroup(brailleTablesExt.USABLE_OUTPUT) + if i: inTable = i.name + if o: ouTable = o.name + if not inTable: inTable = brailleInput.handler.table.displayName + if not ouTable: ouTable = brailleTablesExt.listTablesDisplayName()[brailleTablesExt.listTablesFileName().index(config.conf["braille"]["translationTable"])] if ouTable == inTable: braille.handler.message(_("I⣿O:{I}").format(I=inTable, O=ouTable)) speech.speakMessage(_("Input and output: {I}.").format(I=inTable, O=ouTable)) else: braille.handler.message(_("I:{I} ⣿ O: {O}").format(I=inTable, O=ouTable)) speech.speakMessage(_("Input: {I}; Output: {O}").format(I=inTable, O=ouTable)) - return script_currentBrailleTable.__doc__ = _( - "Announce the current input and output braille tables") + "Announce the current input and output braille tables and/or groups") def script_brlDescChar(self, gesture): utils.currentCharDesc() @@ -1322,8 +1336,7 @@ def terminate(self): self.removeMenu() self.restorReviewCursorTethering() configBE.discardRoleLabels() - if configBE.noUnicodeTable: - brailleInput.handler.table = self.backupInputTable + brailleInput.handler.table = self.backupInputTable if self.hourDatePlayed: self.hourDateTimer.Stop() if configBE.noMessageTimeout: diff --git a/addon/globalPlugins/brailleExtender/brailleTablesExt.py b/addon/globalPlugins/brailleExtender/brailleTablesExt.py new file mode 100644 index 00000000..83723cf9 --- /dev/null +++ b/addon/globalPlugins/brailleExtender/brailleTablesExt.py @@ -0,0 +1,578 @@ +# coding: utf-8 +# brailleTablesExt.py +# Part of BrailleExtender addon for NVDA +# Copyright 2016-2020 André-Abush CLAUSE, released under GPL. + +import codecs +import json +import gui +import wx + +import addonHandler +import brailleTables +import config +from collections import namedtuple +from itertools import permutations +from typing import Optional, List, Tuple +from brailleTables import listTables +from logHandler import log +from . import configBE +from .common import * + +addonHandler.initTranslation() + +POSITION_CURRENT = "c" +POSITION_PREVIOUS = "p" +POSITION_NEXT = "n" +POSITIONS = [POSITION_CURRENT, POSITION_PREVIOUS, POSITION_NEXT] + +USABLE_INPUT = 'i' +USABLE_OUTPUT = 'o' +USABLE_LIST = [USABLE_INPUT, USABLE_OUTPUT] + +GroupTables = namedtuple("GroupTables", ("name", "members", "usableIn")) + +listContractedTables = lambda tables=None: [table for table in (tables or listTables()) if table.contracted] +listUncontractedTables = lambda tables=None: [table for table in (tables or listTables()) if not table.contracted] +listInputTables = lambda tables=None: [table for table in (tables or listTables()) if table.input] +listUncontractedInputTables = listInputTables(listUncontractedTables()) +listOutputTables = lambda tables=None: [table for table in (tables or listTables()) if table.output] +listTablesFileName = lambda tables=None: [table.fileName for table in (tables or listTables())] +listTablesDisplayName = lambda tables=None: [table.displayName for table in (tables or listTables())] + +def fileName2displayName(l): + allTablesFileName = listTablesFileName() + o = [] + for e in l: + if e in allTablesFileName: o.append(allTablesFileName.index(e)) + return [listTables()[e].displayName for e in o] + +def listTablesIndexes(l, tables): + if not tables: tables = listTables() + tables = listTablesFileName(tables) + return [tables.index(e) for e in l if e in tables] + +def getPreferredTables() -> Tuple[List[str]]: + allInputTablesFileName = listTablesFileName(listInputTables()) + allOutputTablesFileName = listTablesFileName(listOutputTables()) + preferredInputTablesFileName = config.conf["brailleExtender"]["tables"]["preferredInput"].split('|') + preferredOutputTablesFileName = config.conf["brailleExtender"]["tables"]["preferredOutput"].split('|') + inputTables = [fn for fn in preferredInputTablesFileName if fn in allInputTablesFileName] + outputTables = [fn for fn in preferredOutputTablesFileName if fn in allOutputTablesFileName] + return inputTables, outputTables + +def getPreferredTablesIndexes() -> List[int]: + preferredInputTables, preferredOutputTables = getPreferredTables() + inputTables = listTablesFileName(listInputTables()) + outputTables = listTablesFileName(listOutputTables()) + o = [] + for a, b in [(preferredInputTables, inputTables), (preferredOutputTables, outputTables)]: + o_ = [] + for e in a: + if e in b: o_.append(b.index(e)) + o.append(o_) + return o + +def getCustomBrailleTables(): + return [config.conf["brailleExtender"]["brailleTables"][k].split('|', 3) for k in config.conf["brailleExtender"]["brailleTables"]] + +def isContractedTable(fileName): + return fileName in listTablesFileName(listContractedTables()) +def getTable(fileName, tables=None): + if not tables: tables = listTables() + for table in tables: + if table.fileName == fileName: return table + return None + +def getTablesFilenameByID(l: List[int], tables=None) -> List[int]: + tablesFileName = [table.fileName for table in (tables or listTables())] + o = [] + size = len(tablesFileName) + for i in l: + if i < size: o.append(tablesFileName[i]) + return o + +def translateUsableIn(s): + labels = { + 'i': _("input"), + 'o': _("output"), + 'io': _("input and output") + } + return labels[s] if s in labels.keys() else _("None") + +def translateUsableInIndexes(usableIn): + o = [] + if 'i' in usableIn: o.append(0) + if 'o' in usableIn: o.append(1) + return o + +def setDict(newGroups): + global _groups + _groups = newGroups + +def getPathGroups(): + return f"{configDir}/groups-tables.json" + +def initializeGroups(): + global _groups + _groups = [] + fp = getPathGroups() + if not os.path.exists(fp): + return + json_ = json.load(codecs.open(fp, "r", "UTF-8")) + for entry in json_: + _groups.append( + GroupTables( + entry["name"], entry["members"], entry["usableIn"] + ) + ) + +def saveGroups(entries=None): + if not entries: entries = getGroups() + entries = [ + { + "name": entry.name, + "members": entry.members, + "usableIn": entry.usableIn + } + for entry in entries + ] + with codecs.open(getPathGroups(), "w", "UTF-8") as outfile: + json.dump(entries, outfile, ensure_ascii=False, indent=2) + +def getGroups(plain=True): + if plain: return _groups if _groups else [] + groups = getGroups() + i = [group for group in groups if group.usableIn in ['i', 'io']] + o = [group for group in groups if group.usableIn in ['o', 'io']] + return i, o + +def getAllGroups(usableIn): + usableInIndex = USABLE_LIST.index(usableIn) + return [None] +tablesToGroups(getPreferredTables()[usableInIndex], usableIn=usableIn) + getGroups(0)[usableInIndex] + +def getGroup( + usableIn, + position=POSITION_CURRENT, + choices=None +): + global _currentGroup + if position not in POSITIONS or usableIn not in USABLE_LIST: return None + usableInIndex = USABLE_LIST.index(usableIn) + currentGroup = _currentGroup[usableInIndex] + if not choices: choices = getAllGroups(usableIn) + if currentGroup not in choices: + currentGroup = choices[0] + curPos = choices.index(currentGroup) + newPos = curPos + if position == POSITION_PREVIOUS: newPos = curPos - 1 + elif position == POSITION_NEXT: newPos = curPos + 1 + return choices[newPos % len(choices)] + +def setTableOrGroup(usableIn, e, choices=None): + global _currentGroup + if not usableIn in USABLE_LIST: return False + usableInIndex = USABLE_LIST.index(usableIn) + choices = getAllGroups(usableIn) + if not e in choices: return False + _currentGroup[usableInIndex] = e + return True + +def tablesToGroups(tables, usableIn): + groups = [] + for table in tables: + groups.append(GroupTables( + ", ".join(fileName2displayName([table])), + [table], + usableIn + )) + return groups + +def groupEnabled(): + return bool(_groups) + +_groups = None +_currentGroup = [None, None] + + +class BrailleTablesDlg(gui.settingsDialogs.SettingsPanel): + + # Translators: title of a dialog. + title = _("Braille tables") + + def makeSettings(self, settingsSizer): + listPreferredTablesIndexes = getPreferredTablesIndexes() + currentTableLabel = _("Use the current input table") + sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) + bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) + + tables = [f"{table.displayName}, {table.fileName}" for table in listTables() if table.input] + label = _("Preferred &input tables") + self.inputTables = sHelper.addLabeledControl(label, gui.nvdaControls.CustomCheckListBox, choices=tables) + self.inputTables.CheckedItems = listPreferredTablesIndexes[0] + self.inputTables.Select(0) + + tables = [f"{table.displayName}, {table.fileName}" for table in listTables() if table.output] + label = _("Preferred &output tables") + self.outputTables = sHelper.addLabeledControl(label, gui.nvdaControls.CustomCheckListBox, choices=tables) + self.outputTables.CheckedItems = listPreferredTablesIndexes[1] + self.outputTables.Select(0) + + label = _("Input braille table to use for keyboard shortcuts") + try: + selectedItem = 0 if config.conf["brailleExtender"]["tables"]["shortcuts"] == '?' else listTablesFileName( + listUncontractedInputTables + ).index(config.conf["brailleExtender"]["tables"]["shortcuts"]) + 1 + except ValueError: + selectedItem = 0 + self.inputTableShortcuts = sHelper.addLabeledControl(label, wx.Choice, choices=[currentTableLabel] + listTablesDisplayName(listUncontractedInputTables)) + self.inputTableShortcuts.SetSelection(selectedItem) + + self.tablesGroupBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("&Groups of tables"), wx.DefaultPosition) + self.tablesGroupBtn.Bind(wx.EVT_BUTTON, self.onTablesGroupsBtn) + + self.customBrailleTablesBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("Alternative and &custom braille tables"), wx.DefaultPosition) + self.customBrailleTablesBtn.Bind(wx.EVT_BUTTON, self.onCustomBrailleTablesBtn) + + # Translators: label of a dialog. + self.tabSpace = sHelper.addItem(wx.CheckBox(self, label=_("Display &tab signs as spaces"))) + self.tabSpace.SetValue(config.conf["brailleExtender"]["tabSpace"]) + self.tabSpace.Bind(wx.EVT_CHECKBOX, self.onTabSpace) + + # Translators: label of a dialog. + self.tabSize = sHelper.addLabeledControl(_("Number of &space for a tab sign")+" "+_("for the currrent braille display"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=42, initial=int(config.conf["brailleExtender"]["tabSize_%s" % configBE.curBD])) + sHelper.addItem(bHelper1) + self.onTabSpace() + + def onTabSpace(self, evt=None): + if self.tabSpace.IsChecked(): self.tabSize.Enable() + else: self.tabSize.Disable() + + def onTablesGroupsBtn(self, evt): + tablesGroupsDlg = TablesGroupsDlg(self, multiInstanceAllowed=True) + tablesGroupsDlg.ShowModal() + + def onCustomBrailleTablesBtn(self, evt): + customBrailleTablesDlg = CustomBrailleTablesDlg(self, multiInstanceAllowed=True) + customBrailleTablesDlg.ShowModal() + + def postInit(self): self.tables.SetFocus() + + def isValid(self): + if self.tabSize.Value > 42: + gui.messageBox( + _("Tab size is invalid"), + _("Error"), + wx.OK|wx.ICON_ERROR, + self + ) + self.tabSize.SetFocus() + return False + return super().isValid() + + def onSave(self): + inputTables = '|'.join(getTablesFilenameByID( + self.inputTables.CheckedItems, + listInputTables() + )) + outputTables = '|'.join( + getTablesFilenameByID( + self.outputTables.CheckedItems, + listOutputTables() + ) + ) + tablesShortcuts = getTablesFilenameByID( + [self.inputTableShortcuts.GetSelection()-1], + listUncontractedInputTables + )[0] if self.inputTableShortcuts.GetSelection() > 0 else '?' + config.conf["brailleExtender"]["tables"]["preferredInput"] = inputTables + config.conf["brailleExtender"]["tables"]["preferredOutput"] = outputTables + config.conf["brailleExtender"]["tables"]["shortcuts"] = tablesShortcuts + config.conf["brailleExtender"]["tabSpace"] = self.tabSpace.IsChecked() + config.conf["brailleExtender"][f"tabSize_{configBE.curBD}"] = self.tabSize.Value + + def postSave(self): + configBE.initializePreferredTables() + + +class TablesGroupsDlg(gui.settingsDialogs.SettingsDialog): + + # Translators: title of a dialog. + title = f"{addonName} - %s" % _("Groups of tables") + + def makeSettings(self, settingsSizer): + self.tmpGroups = getGroups() + self.tmpGroups.sort(key=lambda e: e.name) + sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) + groupsLabelText = _("List of table groups") + self.groupsList = sHelper.addLabeledControl(groupsLabelText, wx.ListCtrl, style=wx.LC_REPORT|wx.LC_SINGLE_SEL,size=(550,350)) + self.groupsList.InsertColumn(0, _("Name"), width=150) + self.groupsList.InsertColumn(1, _("Members"), width=150) + self.groupsList.InsertColumn(2, _("Usable in"), width=150) + self.onSetEntries() + + bHelper = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) + bHelper.addButton( + parent=self, + # Translators: The label for a button in groups of tables dialog to add new entries. + label=_("&Add") + ).Bind(wx.EVT_BUTTON, self.onAddClick) + + bHelper.addButton( + parent=self, + # Translators: The label for a button in groups of tables dialog to edit existing entries. + label=_("&Edit") + ).Bind(wx.EVT_BUTTON, self.onEditClick) + + bHelper.addButton( + parent=self, + # Translators: The label for a button in groups of tables dialog to remove existing entries. + label=_("Re&move") + ).Bind(wx.EVT_BUTTON, self.onRemoveClick) + + sHelper.addItem(bHelper) + bHelper = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) + bHelper.addButton( + parent=self, + # Translators: The label for a button in groups of tables dialog to open groups of tables file in an editor. + label=_("&Open the groups of tables file in an editor") + ).Bind(wx.EVT_BUTTON, self.onOpenFileClick) + bHelper.addButton( + parent=self, + # Translators: The label for a button in groups of tables dialog to reload groups of tables. + label=_("&Reload the groups of tables") + ).Bind(wx.EVT_BUTTON, self.onReloadClick) + sHelper.addItem(bHelper) + + def onSetEntries(self, evt=None): + self.groupsList.DeleteAllItems() + for group in self.tmpGroups: + self.groupsList.Append(( + group.name, + ", ".join(fileName2displayName(group.members)), + translateUsableIn(group.usableIn) + )) + + def onAddClick(self, event): + entryDialog = GroupEntryDlg(self, title=_("Add group entry")) + if entryDialog.ShowModal() == wx.ID_OK: + entry = entryDialog.groupEntry + self.tmpGroups.append(entry) + self.groupsList.Append( + (entry.name, ", ".join(fileName2displayName(entry.members)), translateUsableIn(entry.usableIn)) + ) + index = self.groupsList.GetFirstSelected() + while index >= 0: + self.groupsList.Select(index, on=0) + index = self.groupsList.GetNextSelected(index) + addedIndex = self.groupsList.GetItemCount() - 1 + self.groupsList.Select(addedIndex) + self.groupsList.Focus(addedIndex) + self.groupsList.SetFocus() + entryDialog.Destroy() + + def onEditClick(self, event): + if self.groupsList.GetSelectedItemCount() != 1: + return + editIndex = self.groupsList.GetFirstSelected() + entryDialog = GroupEntryDlg(self) + entryDialog.name.SetValue(self.tmpGroups[editIndex].name) + entryDialog.members.CheckedItems = listTablesIndexes(self.tmpGroups[editIndex].members, listUncontractedInputTables) + entryDialog.refreshOrders() + selectedItem = 0 + try: + selectedItem = list(entryDialog.orderPermutations.keys()).index(tuple(listTablesIndexes(self.tmpGroups[editIndex].members, listUncontractedTables()))) + entryDialog.order.SetSelection(selectedItem) + except ValueError: pass + entryDialog.usableIn.CheckedItems = translateUsableInIndexes(self.tmpGroups[editIndex].usableIn) + if entryDialog.ShowModal() == wx.ID_OK: + entry = entryDialog.groupEntry + self.tmpGroups[editIndex] = entry + self.groupsList.SetItem(editIndex, 0, entry.name) + self.groupsList.SetItem(editIndex, 1, ", ".join(fileName2displayName(entry.members))) + self.groupsList.SetItem(editIndex, 2, translateUsableIn(entry.usableIn)) + self.groupsList.SetFocus() + entryDialog.Destroy() + + def onRemoveClick(self, event): + index = self.groupsList.GetFirstSelected() + while index >= 0: + self.groupsList.DeleteItem(index) + del self.tmpGroups[index] + index = self.groupsList.GetNextSelected(index) + self.onSetEntries() + self.groupsList.SetFocus() + + + def onOpenFileClick(self,evt): + path = getPathGroups() + if not os.path.exists(path): return + try: os.startfile(path) + except OSError: os.popen(f"notepad \"{path}\"") + + def onReloadClick(self,evt): + self.tmpGroups = getGroups() + self.onSetEntries() + + def onOk(self, evt): + saveGroups(self.tmpGroups) + setDict(self.tmpGroups) + super().onOk(evt) + + +class GroupEntryDlg(wx.Dialog): + + orderPermutations = {} + + def __init__(self, parent=None, title=_("Edit Dictionary Entry")): + super().__init__(parent, title=title) + mainSizer = wx.BoxSizer(wx.VERTICAL) + sHelper = gui.guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) + self.name = sHelper.addLabeledControl(_("Group name"), wx.TextCtrl) + label = _(f"Group members") + self.members = sHelper.addLabeledControl(label, gui.nvdaControls.CustomCheckListBox, choices=listTablesDisplayName(listUncontractedTables())) + self.members.SetSelection(0) + self.members.Bind(wx.EVT_CHECKLISTBOX, lambda s: self.refreshOrders()) + label = _("Usable in") + self.order = sHelper.addLabeledControl(_("Order of the tables"), wx.Choice, choices=[]) + self.refreshOrders() + choices = [_("input"), _("output")] + self.usableIn = sHelper.addLabeledControl(label, gui.nvdaControls.CustomCheckListBox, choices=choices) + self.usableIn.SetSelection(0) + sHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK | wx.CANCEL)) + mainSizer.Add(sHelper.sizer, border=20, flag=wx.ALL) + mainSizer.Fit(self) + self.SetSizer(mainSizer) + self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK) + self.name.SetFocus() + + def refreshOrders(self, evt=None): + tables = listUncontractedTables() + self.orderPermutations = {e: fileName2displayName(getTablesFilenameByID(e, tables)) for e in permutations(self.members.CheckedItems) if e} + self.order.SetItems([", ".join(e) for e in self.orderPermutations.values()]) + self.order.SetSelection(0) + + def onOk(self, evt): + name = self.name.Value + if not self.orderPermutations: return + members = getTablesFilenameByID(list(self.orderPermutations.keys())[self.order.GetSelection()], listUncontractedTables()) + matches = ['i', 'o'] + usableIn = ''.join([matches[e] for e in self.usableIn.CheckedItems]) + if not name: + gui.messageBox( + _("Please specify a group name"), + _("Error"), + wx.OK|wx.ICON_ERROR, + self + ) + return self.name.SetFocus() + if len(members) < 2: + gui.messageBox( + _("Please select at least 2 tables"), + _("Error"), + wx.OK|wx.ICON_ERROR, + self + ) + return self.members.SetFocus() + self.groupEntry = GroupTables(name, members, usableIn) + evt.Skip() + +class CustomBrailleTablesDlg(gui.settingsDialogs.SettingsDialog): + + # Translators: title of a dialog. + title = f"{addonName} - %s" % _("Custom braille tables") + providedTablesPath = "%s/res/json" % baseDir + userTablesPath = "%s/json" % configDir + + def makeSettings(self, settingsSizer): + self.providedTables = self.getBrailleTablesFromJSON(self.providedTablesPath) + self.userTables = self.getBrailleTablesFromJSON(self.userTablesPath) + sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) + bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) + self.inTable = sHelper.addItem(wx.CheckBox(self, label=_("Use a custom table as input table"))) + self.outTable = sHelper.addItem(wx.CheckBox(self, label=_("Use a custom table as output table"))) + self.addBrailleTablesBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("&Add a braille table"), wx.DefaultPosition) + self.addBrailleTablesBtn.Bind(wx.EVT_BUTTON, self.onAddBrailleTablesBtn) + sHelper.addItem(bHelper1) + + @staticmethod + def getBrailleTablesFromJSON(path): + if not os.path.exists(path): + path = "%s/%s" % (baseDir, path) + if not os.path.exists(path): return {} + f = open(path, "r") + return json.load(f) + + def onAddBrailleTablesBtn(self, evt): + addBrailleTablesDlg = AddBrailleTablesDlg(self, multiInstanceAllowed=True) + addBrailleTablesDlg.ShowModal() + + def postInit(self): self.inTable.SetFocus() + + def onOk(self, event): + super(CustomBrailleTablesDlg, self).onOk(evt) + + +class AddBrailleTablesDlg(gui.settingsDialogs.SettingsDialog): + # Translators: title of a dialog. + title = "Braille Extender - %s" % _("Add a braille table") + tbl = [] + + def makeSettings(self, settingsSizer): + sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) + bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) + self.name = sHelper.addLabeledControl(_("Display name"), wx.TextCtrl) + self.description = sHelper.addLabeledControl(_("Description"), wx.TextCtrl, style = wx.TE_MULTILINE|wx.TE_PROCESS_ENTER, size = (360, 90), pos=(-1,-1)) + self.path = sHelper.addLabeledControl(_("Path"), wx.TextCtrl) + self.browseBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("&Browse"), wx.DefaultPosition) + self.browseBtn.Bind(wx.EVT_BUTTON, self.onBrowseBtn) + sHelper.addItem(bHelper1) + self.isContracted = sHelper.addItem(wx.CheckBox(self, label=_("Contracted (grade 2) braille table"))) + # Translators: label of a dialog. + self.inputOrOutput = sHelper.addLabeledControl(_("Available for"), wx.Choice, choices=[_("Input and output"), _("Input only"), _("Output only")]) + self.inputOrOutput.SetSelection(0) + + def postInit(self): self.name.SetFocus() + + def onBrowseBtn(self, event): + dlg = wx.FileDialog(None, _("Choose a table file"), "%PROGRAMFILES%", "", "%s (*.ctb, *.cti, *.utb, *.uti)|*.ctb;*.cti;*.utb;*.uti" % _("Liblouis table files"), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if dlg.ShowModal() != wx.ID_OK: + dlg.Destroy() + return self.path.SetFocus() + self.path.SetValue(dlg.GetDirectory() + '\\' + dlg.GetFilename()) + dlg.Destroy() + self.path.SetFocus() + + def onOk(self, event): + path = self.path.GetValue().strip().encode("UTF-8") + displayName = self.name.GetValue().strip() + if not displayName: + gui.messageBox(_("Please specify a display name."), _("Invalid display name"), wx.OK|wx.ICON_ERROR) + self.name.SetFocus() + return + if not os.path.exists(path.decode("UTF-8").encode("mbcs")): + gui.messageBox(_("The specified path is not valid (%s).") % path.decode("UTF-8"), _("Invalid path"), wx.OK|wx.ICON_ERROR) + self.path.SetFocus() + return + switch_possibleValues = ["both", "input", "output"] + v = "%s|%s|%s|%s" % ( + switch_possibleValues[self.inputOrOutput.GetSelection()], + self.isContracted.IsChecked(), path.decode("UTF-8"), displayName + ) + k = hashlib.md5(path).hexdigest()[:15] + config.conf["brailleExtender"]["brailleTables"][k] = v + super(AddBrailleTablesDlg, self).onOk(evt) + + @staticmethod + def getAvailableBrailleTables(): + out = [] + brailleTablesDir = configBE.TABLES_DIR + ls = glob.glob(brailleTablesDir+'\\*.ctb')+glob.glob(brailleTablesDir+'\\*.cti')+glob.glob(brailleTablesDir+'\\*.utb') + for i, e in enumerate(ls): + e = str(e.split('\\')[-1]) + if e in configBE.tablesFN or e.lower() in configBE.tablesFN: del ls[i] + else: out.append(e.lower()) + out = sorted(out) + return out + + diff --git a/addon/globalPlugins/brailleExtender/configBE.py b/addon/globalPlugins/brailleExtender/configBE.py index 60780d03..1bffd741 100644 --- a/addon/globalPlugins/brailleExtender/configBE.py +++ b/addon/globalPlugins/brailleExtender/configBE.py @@ -15,6 +15,7 @@ import configobj import inputCore import languageHandler +from . import brailleTablesExt from .common import * from .oneHandMode import DOT_BY_DOT, ONE_SIDE, BOTH_SIDES @@ -73,21 +74,12 @@ noMessageTimeout = True if 'noMessageTimeout' in config.conf["braille"] else False outputTables = inputTables = None -preTable = [] -postTable = [] +preTables = [] +postTables = [] if not os.path.exists(profilesDir): log.error('Profiles\' path not found') else: log.debug('Profiles\' path (%s) found' % profilesDir) -try: - import brailleTables - tables = brailleTables.listTables() - tablesFN = [t[0] for t in brailleTables.listTables()] - tablesUFN = [t[0] for t in brailleTables.listTables() if not t.contracted and t.output] - tablesTR = [t[1] for t in brailleTables.listTables()] - noUnicodeTable = False -except BaseException: - noUnicodeTable = True -def getValidBrailleDisplayPrefered(): +def getValidBrailleDisplayPreferred(): l = braille.getDisplayList() l.append(("last", _("last known"))) return l @@ -139,9 +131,6 @@ def getConfspec(): "stopSpeechUnknown": "boolean(default=True)", "speakRoutingTo": "boolean(default=True)", "routingReviewModeWithCursorKeys": "boolean(default=False)", - "inputTableShortcuts": 'string(default="?")', - "inputTables": 'string(default="%s")' % config.conf["braille"]["inputTable"] + ", unicode-braille.utb", - "outputTables": "string(default=%s)" % config.conf["braille"]["translationTable"], "tabSpace": "boolean(default=False)", f"tabSize_{curBD}": "integer(min=1, default=2, max=42)", "undefinedCharsRepr": { @@ -157,7 +146,6 @@ def getConfspec(): "lang": "string(default=Windows)", "table": "string(default=current)" }, - "postTable": 'string(default="None")', "viewSaved": "string(default=%s)" % NOVIEWSAVED, "reviewModeTerminal": "boolean(default=True)", "features": { @@ -211,7 +199,6 @@ def getConfspec(): }, "quickLaunches": {}, "roleLabels": {}, - "brailleTables": {}, "advancedInputMode": { "stopAfterOneChar": "boolean(default=True)", "escapeSignUnicodeValue": "string(default=⠼)", @@ -220,21 +207,14 @@ def getConfspec(): "enabled": "boolean(default=False)", "inputMethod": f"option({DOT_BY_DOT}, {BOTH_SIDES}, {ONE_SIDE}, default={ONE_SIDE})", }, + "tables": { + "groups": {}, + "shortcuts": 'string(default="?")', + "preferredInput": f'string(default="{config.conf["braille"]["inputTable"]}|unicode-braille.utb")', + "preferredOutput": f'string(default="{config.conf["braille"]["translationTable"]}")', + }, } -def loadPreferedTables(): - global inputTables, outputTables - listInputTables = [table[0] for table in brailleTables.listTables() if table.input] - listOutputTables = [table[0] for table in brailleTables.listTables() if table.output] - inputTables = config.conf["brailleExtender"]["inputTables"] - outputTables = config.conf["brailleExtender"]["outputTables"] - if not isinstance(inputTables, list): - inputTables = inputTables.replace(', ', ',').split(',') - if not isinstance(outputTables, list): - outputTables = outputTables.replace(', ', ',').split(',') - inputTables = [t for t in inputTables if t in listInputTables] - outputTables = [t for t in outputTables if t in listOutputTables] - def getLabelFromID(idCategory, idLabel): if idCategory == 0: return braille.roleLabels[int(idLabel)] elif idCategory == 1: return braille.landmarkLabels[idLabel] @@ -303,12 +283,16 @@ def loadConf(): limitCellsRight = int(config.conf["brailleExtender"]["rightMarginCells_%s" % curBD]) if (backupDisplaySize-limitCellsRight <= backupDisplaySize and limitCellsRight > 0): braille.handler.displaySize = backupDisplaySize-limitCellsRight - if not noUnicodeTable: loadPreferedTables() - if config.conf["brailleExtender"]["inputTableShortcuts"] not in tablesUFN: config.conf["brailleExtender"]["inputTableShortcuts"] = '?' + if config.conf["brailleExtender"]["tables"]["shortcuts"] not in brailleTablesExt.listTablesFileName(brailleTablesExt.listUncontractedTables()): config.conf["brailleExtender"]["tables"]["shortcuts"] = '?' if config.conf["brailleExtender"]["features"]["roleLabels"]: loadRoleLabels(config.conf["brailleExtender"]["roleLabels"].copy()) + initializePreferredTables() return True +def initializePreferredTables(): + global inputTables, outputTables + inputTables, outputTables = brailleTablesExt.getPreferredTables() + def loadGestures(): if gesturesFileExists: if os.path.exists(os.path.join(profilesDir, "_BrowseMode", config.conf["braille"]["inputTable"] + ".ini")): GLng = config.conf["braille"]["inputTable"] @@ -359,11 +343,6 @@ def initGestures(): iniGestures["globalCommands.GlobalCommands"][g])).replace('br(' + curBD + '):', '')] = g return gesturesFileExists, iniGestures -def isContractedTable(table): - if not table in tablesFN: return False - tablePos = tablesFN.index(table) - if brailleTables.listTables()[tablePos].contracted: return True - return False def getKeyboardLayout(): if (config.conf["brailleExtender"]["keyboardLayout_%s" % curBD] is not None @@ -371,8 +350,6 @@ def getKeyboardLayout(): return iniProfile['keyboardLayouts'].keys().index(config.conf["brailleExtender"]["keyboardLayout_%s" % curBD]) else: return 0 -def getCustomBrailleTables(): - return [config.conf["brailleExtender"]["brailleTables"][k].split('|', 3) for k in config.conf["brailleExtender"]["brailleTables"]] def getTabSize(): size = config.conf["brailleExtender"]["tabSize_%s" % curBD] diff --git a/addon/globalPlugins/brailleExtender/dictionaries.py b/addon/globalPlugins/brailleExtender/dictionaries.py index e8b8df1b..1156b48a 100644 --- a/addon/globalPlugins/brailleExtender/dictionaries.py +++ b/addon/globalPlugins/brailleExtender/dictionaries.py @@ -18,7 +18,9 @@ from collections import namedtuple from . import configBE from .common import * +from . import brailleTablesExt from . import huc +from logHandler import log TableDictEntry = namedtuple("TableDictEntry", ("opcode", "textPattern", "braillePattern", "direction", "comment")) OPCODE_SIGN = "sign" @@ -44,27 +46,40 @@ } DIRECTION_LABELS_ORDERING = (DIRECTION_BOTH, DIRECTION_FORWARD, DIRECTION_BACKWARD) -dictTables = [] -invalidDictTables = set() +inputTables = [] +outputTables = [] +invalidTables = set() def checkTable(path): - global invalidDictTables - try: - louis.checkTable([path]) - return True - except RuntimeError: invalidDictTables.add(path) - return False - -def getValidPathsDict(): + global invalidTables + tablesString = b",".join([x.encode("mbcs") if isinstance(x, str) else bytes(x) for x in [path]]) + if not louis.liblouis.lou_checkTable(tablesString): + log.error("Can't compile: tables %s" % path) + invalidTables.add(path) + return False + return True + +def getValidPathsDict(usableIn): types = ["tmp", "table", "default"] - paths = [getPathDict(type_) for type_ in types] + paths = [getPathDict(type_, usableIn) for type_ in types] valid = lambda path: os.path.exists(path) and os.path.isfile(path) and checkTable(path) return [path for path in paths if valid(path)] -def getPathDict(type_): - if type_ == "table": path = os.path.join(configDir, "brailleDicts", config.conf["braille"]["translationTable"]) +def getPathDict(type_, usableIn): + groupEnabled = brailleTablesExt.groupEnabled() + g = brailleTablesExt.getGroup(usableIn=usableIn) + table = os.path.join(configDir, "brailleDicts", config.conf["braille"]["inputTable"]) if usableIn == brailleTablesExt.USABLE_INPUT else config.conf["braille"]["translationTable"] + if type_ == "table": + if groupEnabled and g and g.members: + if len(g.members) == 1: path = os.path.join(configDir, "brailleDicts", g.members[0]) + else: path = '' + else: path = os.path.join(configDir, "brailleDicts", table) elif type_ == "tmp": path = os.path.join(configDir, "brailleDicts", "tmp") - else: path = os.path.join(configDir, "brailleDicts", "default") + else: + if groupEnabled and g and g.members: + if len(g.members) == 1: path = os.path.join(configDir, "brailleDicts", "default") + else: path = '' + else: path = os.path.join(configDir, "brailleDicts", "default") return "%s.cti" % path def getDictionary(type_): @@ -95,18 +110,19 @@ def saveDict(type_, dict_): return True def setDictTables(): - global dictTables - dictTables = getValidPathsDict() - invalidDictTables.clear() + global inputTables, outTable + inputTables = getValidPathsDict(brailleTablesExt.USABLE_INPUT) + outputTables = getValidPathsDict(brailleTablesExt.USABLE_OUTPUT) + invalidTables.clear() def notifyInvalidTables(): - if invalidDictTables: + if invalidTables: dicts = { getPathDict("default"): "default", getPathDict("table"): "table", getPathDict("tmp"): "tmp" } - msg = _("One or more errors are present in dictionaries tables. Concerned dictionaries: %s. As a result, these dictionaries are not loaded.") % ", ".join([dicts[path] for path in invalidDictTables if path in dicts]) + msg = _("One or more errors are present in dictionaries tables. Concerned dictionaries: %s. As a result, these dictionaries are not loaded.") % ", ".join([dicts[path] for path in invalidTables if path in dicts]) wx.CallAfter(gui.messageBox, msg, _("Braille Extender"), wx.OK|wx.ICON_ERROR) def removeTmpDict(): @@ -292,7 +308,7 @@ def __init__(self, parent=None, title=_("Edit Dictionary Entry"), textPattern='' if specifyDict: # Translators: This is a label for an edit field in add dictionary entry dialog. dictText = _("Dictionary") - outTable = configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])] + outTable = brailleTablesExt.fileName2displayName(config.conf["braille"]["translationTable"]) dictChoices = [_("Global"), _("Table")+(" (%s)" % outTable), _("Temporary")] self.dictRadioBox = sHelper.addItem(wx.RadioBox(self, label=dictText, choices=dictChoices)) self.dictRadioBox.SetSelection(1) @@ -338,7 +354,7 @@ def __init__(self, parent=None, title=_("Edit Dictionary Entry"), textPattern='' def onSeeEntriesClick(self, evt): - outTable = configBE.tablesTR[configBE.tablesFN.index(config.conf["braille"]["translationTable"])] + outTable = brailleTablesExt.fileName2displayName(config.conf["braille"]["translationTable"]) label = [_("Global dictionary"), _("Table dictionary")+(" (%s)" % outTable), _("Temporary dictionary")][self.dictRadioBox.GetSelection()] type_ = self.getType_() self.Destroy() diff --git a/addon/globalPlugins/brailleExtender/settings.py b/addon/globalPlugins/brailleExtender/settings.py index 99b38a4a..95e2feae 100644 --- a/addon/globalPlugins/brailleExtender/settings.py +++ b/addon/globalPlugins/brailleExtender/settings.py @@ -23,6 +23,7 @@ import ui addonHandler.initTranslation() +from . import brailleTablesExt from . import configBE from . import utils from .advancedInputMode import SettingsDlg as AdvancedInputModeDlg @@ -39,8 +40,8 @@ class GeneralDlg(gui.settingsDialogs.SettingsPanel): # Translators: title of a dialog. title = _("General") - bds_k = [k for k, v in configBE.getValidBrailleDisplayPrefered()] - bds_v = [v for k, v in configBE.getValidBrailleDisplayPrefered()] + bds_k = [k for k, v in configBE.getValidBrailleDisplayPreferred()] + bds_v = [v for k, v in configBE.getValidBrailleDisplayPreferred()] def makeSettings(self, settingsSizer): sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) @@ -325,253 +326,6 @@ def onSave(self): if config.conf["brailleExtender"]["features"]["roleLabels"]: configBE.loadRoleLabels(config.conf["brailleExtender"]["roleLabels"].copy()) -class BrailleTablesDlg(gui.settingsDialogs.SettingsPanel): - - # Translators: title of a dialog. - title = _("Braille tables") - - def makeSettings(self, settingsSizer): - self.oTables = set(configBE.outputTables) - self.iTables = set(configBE.inputTables) - lt = [_("Use the current input table")] - for table in configBE.tables: - if table.output and not table.contracted: lt.append(table[1]) - if config.conf["brailleExtender"]["inputTableShortcuts"] in configBE.tablesUFN: - iSht = configBE.tablesUFN.index(config.conf["brailleExtender"]["inputTableShortcuts"]) + 1 - else: iSht = 0 - sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) - bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) - - self.tables = sHelper.addLabeledControl(_("Prefered braille tables")+" (%s)" % _("press F1 for help"), wx.Choice, choices=self.getTablesWithSwitches()) - self.tables.SetSelection(0) - self.tables.Bind(wx.EVT_CHAR, self.onTables) - - self.inputTableShortcuts = sHelper.addLabeledControl(_("Input braille table for keyboard shortcut keys"), wx.Choice, choices=lt) - self.inputTableShortcuts.SetSelection(iSht) - lt = [_('None')] - for table in configBE.tables: - if table.output: lt.append(table[1]) - self.postTable = sHelper.addLabeledControl(_("Secondary output table to use"), wx.Choice, choices=lt) - self.postTable.SetSelection(configBE.tablesFN.index(config.conf["brailleExtender"]["postTable"]) if config.conf["brailleExtender"]["postTable"] in configBE.tablesFN else 0) - - # Translators: label of a dialog. - self.tabSpace = sHelper.addItem(wx.CheckBox(self, label=_("Display &tab signs as spaces"))) - self.tabSpace.SetValue(config.conf["brailleExtender"]["tabSpace"]) - - # Translators: label of a dialog. - self.tabSize = sHelper.addLabeledControl(_("Number of &space for a tab sign")+" "+_("for the currrent braille display"), gui.nvdaControls.SelectOnFocusSpinCtrl, min=1, max=42, initial=int(config.conf["brailleExtender"]["tabSize_%s" % configBE.curBD])) - self.customBrailleTablesBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("Alternative and &custom braille tables"), wx.DefaultPosition) - self.customBrailleTablesBtn.Bind(wx.EVT_BUTTON, self.onCustomBrailleTablesBtn) - sHelper.addItem(bHelper1) - - def postInit(self): self.tables.SetFocus() - - def onSave(self): - config.conf["brailleExtender"]["outputTables"] = ','.join(self.oTables) - config.conf["brailleExtender"]["inputTables"] = ','.join(self.iTables) - config.conf["brailleExtender"]["inputTableShortcuts"] = configBE.tablesUFN[self.inputTableShortcuts.GetSelection()-1] if self.inputTableShortcuts.GetSelection()>0 else '?' - postTableID = self.postTable.GetSelection() - postTable = "None" if postTableID == 0 else configBE.tablesFN[postTableID] - config.conf["brailleExtender"]["postTable"] = postTable - if self.tabSpace.IsChecked() and config.conf["brailleExtender"]["tabSpace"] != self.tabSpace.IsChecked(): - restartRequired = True - else: restartRequired = False - config.conf["brailleExtender"]["tabSpace"] = self.tabSpace.IsChecked() - config.conf["brailleExtender"]["tabSize_%s" % configBE.curBD] = self.tabSize.Value - if restartRequired: - res = gui.messageBox( - _("NVDA must be restarted for some new options to take effect. Do you want restart now?"), - _("Braille Extender"), - style=wx.YES_NO|wx.ICON_INFORMATION - ) - if res == wx.YES: core.restart() - - def getTablesWithSwitches(self): - out = [] - for i, tbl in enumerate(configBE.tablesTR): - out.append("%s%s: %s" % (tbl, punctuationSeparator, self.getInSwitchesText(configBE.tablesFN[i]))) - return out - - def getCurrentSelection(self): - idx = self.tables.GetSelection() - tbl = configBE.tablesFN[self.tables.GetSelection()] - return idx, tbl - - def setCurrentSelection(self, tbl, newLoc): - if newLoc == "io": - self.iTables.add(tbl) - self.oTables.add(tbl) - elif newLoc == "i": - self.iTables.add(tbl) - self.oTables.discard(tbl) - elif newLoc == "o": - self.oTables.add(tbl) - self.iTables.discard(tbl) - elif newLoc == "n": - self.iTables.discard(tbl) - self.oTables.discard(tbl) - - def inSwitches(self, tbl): - inp = tbl in self.iTables - out = tbl in self.oTables - return [inp, out] - - def getInSwitchesText(self, tbl): - inS = self.inSwitches(tbl) - if all(inS): inSt = _("input and output") - elif not any(inS): inSt = _("none") - elif inS[0]: inSt = _("input only") - elif inS[1]: inSt = _("output only") - return inSt - - def changeSwitch(self, tbl, direction=1, loop=True): - dirs = ['n', 'i', 'o', "io"] - iCurDir = 0 - inS = self.inSwitches(tbl) - if all(inS): iCurDir = dirs.index("io") - elif not any(inS): iCurDir = dirs.index('n') - elif inS[0]: iCurDir = dirs.index('i') - elif inS[1]: iCurDir = dirs.index('o') - - if len(dirs)-1 == iCurDir and direction == 1 and loop: newDir = dirs[0] - elif iCurDir == 0 and direction == 0 and loop: newDir = dirs[-1] - elif iCurDir < len(dirs)-1 and direction == 1: newDir = dirs[iCurDir+1] - elif iCurDir > 0 and iCurDir < len(dirs) and direction == 0: newDir = dirs[iCurDir-1] - else: return - self.setCurrentSelection(tbl, newDir) - - def onCustomBrailleTablesBtn(self, evt): - customBrailleTablesDlg = CustomBrailleTablesDlg(self, multiInstanceAllowed=True) - customBrailleTablesDlg.ShowModal() - - def onTables(self, evt): - keycode = evt.GetKeyCode() - if keycode in [ord(','), ord(';')]: - idx, tbl = self.getCurrentSelection() - if keycode == ord(','): - queueHandler.queueFunction(queueHandler.eventQueue, ui.message, "%s" % tbl) - else: - ui.browseableMessage('\n'.join([ - _("Table name: %s") % configBE.tablesTR[idx], - _("File name: %s") % tbl, - _("In switches: %s") % self.getInSwitchesText(tbl) - ]), _("About this table"), False) - if keycode == wx.WXK_F1: - ui.browseableMessage( - _("In this combo box, all tables are present. Press space bar, left or right arrow keys to include (or not) the selected table in switches")+".\n"+ - _("You can also press 'comma' key to get the file name of the selected table and 'semicolon' key to view miscellaneous infos on the selected table")+".", - _("Contextual help"), False) - if keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_SPACE]: - idx, tbl = self.getCurrentSelection() - if keycode == wx.WXK_LEFT: self.changeSwitch(tbl, 0, False) - elif keycode == wx.WXK_RIGHT: self.changeSwitch(tbl, 1, False) - elif keycode == wx.WXK_SPACE: self.changeSwitch(tbl, 1, True) - newSwitch = self.getInSwitchesText(tbl) - self.tables.SetString(self.tables.GetSelection(), "%s%s: %s" % (configBE.tablesTR[idx], punctuationSeparator, newSwitch)) - queueHandler.queueFunction(queueHandler.eventQueue, ui.message, "%s" % newSwitch) - utils.refreshBD() - else: evt.Skip() - - -class CustomBrailleTablesDlg(gui.settingsDialogs.SettingsDialog): - - # Translators: title of a dialog. - title = "Braille Extender - %s" % _("Custom braille tables") - providedTablesPath = "%s/res/brailleTables.json" % configBE.baseDir - userTablesPath = "%s/brailleTables.json" % configBE.configDir - - def makeSettings(self, settingsSizer): - self.providedTables = self.getBrailleTablesFromJSON(self.providedTablesPath) - self.userTables = self.getBrailleTablesFromJSON(self.userTablesPath) - wx.CallAfter(notImplemented) - sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) - bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) - self.inTable = sHelper.addItem(wx.CheckBox(self, label=_("Use a custom table as input table"))) - self.outTable = sHelper.addItem(wx.CheckBox(self, label=_("Use a custom table as output table"))) - self.addBrailleTablesBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("&Add a braille table"), wx.DefaultPosition) - self.addBrailleTablesBtn.Bind(wx.EVT_BUTTON, self.onAddBrailleTablesBtn) - sHelper.addItem(bHelper1) - - @staticmethod - def getBrailleTablesFromJSON(path): - if not os.path.exists(path): - path = "%s/%s" % (configBE.baseDir, path) - if not os.path.exists(path): return {} - f = open(path, "r") - return json.load(f) - - def onAddBrailleTablesBtn(self, evt): - addBrailleTablesDlg = AddBrailleTablesDlg(self, multiInstanceAllowed=True) - addBrailleTablesDlg.ShowModal() - - def postInit(self): self.inTable.SetFocus() - - def onOk(self, event): - super(CustomBrailleTablesDlg, self).onOk(evt) - - -class AddBrailleTablesDlg(gui.settingsDialogs.SettingsDialog): - # Translators: title of a dialog. - title = "Braille Extender - %s" % _("Add a braille table") - tbl = [] - - def makeSettings(self, settingsSizer): - sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) - bHelper1 = gui.guiHelper.ButtonHelper(orientation=wx.HORIZONTAL) - self.name = sHelper.addLabeledControl(_("Display name"), wx.TextCtrl) - self.description = sHelper.addLabeledControl(_("Description"), wx.TextCtrl, style = wx.TE_MULTILINE|wx.TE_PROCESS_ENTER, size = (360, 90), pos=(-1,-1)) - self.path = sHelper.addLabeledControl(_("Path"), wx.TextCtrl) - self.browseBtn = bHelper1.addButton(self, wx.NewId(), "%s..." % _("&Browse"), wx.DefaultPosition) - self.browseBtn.Bind(wx.EVT_BUTTON, self.onBrowseBtn) - sHelper.addItem(bHelper1) - self.isContracted = sHelper.addItem(wx.CheckBox(self, label=_("Contracted (grade 2) braille table"))) - # Translators: label of a dialog. - self.inputOrOutput = sHelper.addLabeledControl(_("Available for"), wx.Choice, choices=[_("Input and output"), _("Input only"), _("Output only")]) - self.inputOrOutput.SetSelection(0) - - def postInit(self): self.name.SetFocus() - - def onBrowseBtn(self, event): - dlg = wx.FileDialog(None, _("Choose a table file"), "%PROGRAMFILES%", "", "%s (*.ctb, *.cti, *.utb, *.uti)|*.ctb;*.cti;*.utb;*.uti" % _("Liblouis table files"), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return self.path.SetFocus() - self.path.SetValue(dlg.GetDirectory() + '\\' + dlg.GetFilename()) - dlg.Destroy() - self.path.SetFocus() - - def onOk(self, event): - path = self.path.GetValue().strip().encode("UTF-8") - displayName = self.name.GetValue().strip() - if not displayName: - gui.messageBox(_("Please specify a display name."), _("Invalid display name"), wx.OK|wx.ICON_ERROR) - self.name.SetFocus() - return - if not os.path.exists(path.decode("UTF-8").encode("mbcs")): - gui.messageBox(_("The specified path is not valid (%s).") % path.decode("UTF-8"), _("Invalid path"), wx.OK|wx.ICON_ERROR) - self.path.SetFocus() - return - switch_possibleValues = ["both", "input", "output"] - v = "%s|%s|%s|%s" % ( - switch_possibleValues[self.inputOrOutput.GetSelection()], - self.isContracted.IsChecked(), path.decode("UTF-8"), displayName - ) - k = hashlib.md5(path).hexdigest()[:15] - config.conf["brailleExtender"]["brailleTables"][k] = v - super(AddBrailleTablesDlg, self).onOk(evt) - - @staticmethod - def getAvailableBrailleTables(): - out = [] - brailleTablesDir = configBE.brailleTables.TABLES_DIR - ls = glob.glob(brailleTablesDir+'\\*.ctb')+glob.glob(brailleTablesDir+'\\*.cti')+glob.glob(brailleTablesDir+'\\*.utb') - for i, e in enumerate(ls): - e = str(e.split('\\')[-1]) - if e in configBE.tablesFN or e.lower() in configBE.tablesFN: del ls[i] - else: out.append(e.lower()) - out = sorted(out) - return out - class QuickLaunchesDlg(gui.settingsDialogs.SettingsDialog): @@ -894,7 +648,7 @@ class AddonSettingsDialog(gui.settingsDialogs.MultiCategorySettingsDialog): categoryClasses=[ GeneralDlg, AttribraDlg, - BrailleTablesDlg, + brailleTablesExt.BrailleTablesDlg, UndefinedCharsDlg, AdvancedInputModeDlg, OneHandModeDlg, diff --git a/addon/globalPlugins/brailleExtender/undefinedChars.py b/addon/globalPlugins/brailleExtender/undefinedChars.py index 6f6a9936..8b2f818d 100644 --- a/addon/globalPlugins/brailleExtender/undefinedChars.py +++ b/addon/globalPlugins/brailleExtender/undefinedChars.py @@ -11,13 +11,13 @@ import addonHandler import brailleInput -import brailleTables +from . import brailleTablesExt import characterProcessing import config import gui import louis -from . import configBE, huc +from . import huc from .common import * from .utils import getCurrentBrailleTables, getTextInBraille from . import brailleRegionHelper @@ -316,17 +316,12 @@ def makeSettings(self, settingsSizer): _("&Language"), wx.Choice, choices=values ) self.undefinedCharLang.SetSelection(undefinedCharLangID) - values = [_("Use the current output table")] + [ - table.displayName for table in configBE.tables if table.output - ] - keys = ["current"] + [ - table.fileName for table in configBE.tables if table.output - ] + values = [_("Use the current output table")] + brailleTablesExt.listTablesDisplayName(brailleTablesExt.listOutputTables()) + keys = ["current"] + brailleTablesExt.listTablesFileName(brailleTablesExt.listOutputTables()) undefinedCharTable = config.conf["brailleExtender"]["undefinedCharsRepr"][ "table" ] - if undefinedCharTable not in configBE.tablesFN + ["current"]: - undefinedCharTable = "current" + if undefinedCharTable not in keys: undefinedCharTable = "current" undefinedCharTableID = keys.index(undefinedCharTable) self.undefinedCharTable = sHelper.addLabeledControl( _("Braille &table"), wx.Choice, choices=values @@ -418,9 +413,7 @@ def onSave(self): 0 ] undefinedCharTable = self.undefinedCharTable.GetSelection() - keys = ["current"] + [ - table.fileName for table in configBE.tables if table.output - ] + keys = ["current"] + brailleTablesExt.listTablesFileName(brailleTablesExt.listOutputTables()) config.conf["brailleExtender"]["undefinedCharsRepr"]["table"] = keys[ undefinedCharTable ] diff --git a/addon/globalPlugins/brailleExtender/utils.py b/addon/globalPlugins/brailleExtender/utils.py index 5568206c..e4572070 100644 --- a/addon/globalPlugins/brailleExtender/utils.py +++ b/addon/globalPlugins/brailleExtender/utils.py @@ -25,6 +25,7 @@ import treeInterceptorHandler import unicodedata from .common import * +from . import brailleTablesExt from . import huc from . import dictionaries @@ -179,8 +180,7 @@ def bkToChar(dots, inTable=-1): if inTable == -1: inTable = config.conf["braille"]["inputTable"] char = chr(dots | 0x8000) text = louis.backTranslate( - [osp.join(r"louis\tables", inTable), - "braille-patterns.cti"], + [osp.join(r"louis\tables", inTable), "braille-patterns.cti"], char, mode=louis.dotsIO) chars = text[0] if len(chars) == 1 and chars.isupper(): @@ -202,11 +202,14 @@ def currentCharDesc(ch='', display=True): if not ch: return ui.message(_("Not a character")) c = ord(ch) if c: - try: descChar = unicodedata.name(ch) - except ValueError: descChar = _("unknown") + charDescCurLang = getSpeechSymbols(ch) + try: charDesc = unicodedata.name(ch) + except ValueError: charDesc = _("N/A") HUCrepr = " (%s, %s)" % (huc.translate(ch, False), huc.translate(ch, True)) brch = getTextInBraille(ch) - s = "%c%s: %s; %s; %s; %s; %s [%s]\n%s (%s)" % (ch, HUCrepr, hex(c), c, oct(c), bin(c), descChar, unicodedata.category(ch), brch, huc.unicodeBrailleToDescription(brch)) + brchDesc = huc.unicodeBrailleToDescription(brch) + charCategory = unicodedata.category(ch) + s = f"{ch}{HUCrepr}: {hex(c)}, {c}, {oct(c)}, {bin(c)}, {charDescCurLang} / {charDesc} [{charCategory}]. {brch} {brchDesc}" if not display: return s if scriptHandler.getLastScriptRepeatCount() == 0: ui.message(s) elif scriptHandler.getLastScriptRepeatCount() == 1: @@ -257,9 +260,7 @@ def getTextInBraille(t=None, table=[]): if not t: t = getTextSelection() if not t.strip(): return '' if not table or "current" in table: - currentTable = os.path.join(brailleTables.TABLES_DIR, config.conf["braille"]["translationTable"]) - if "current" in table: table[table.index("current")] = currentTable - else: table.append(currentTable) + table = getCurrentBrailleTables() nt = [] res = '' t = t.split("\n") @@ -473,11 +474,18 @@ def getCurrentBrailleTables(input_=False, brf=False): else: tables = [] app = appModuleHandler.getAppModuleForNVDAObject(api.getNavigatorObject()) - if brailleInput.handler._table.fileName == config.conf["braille"]["translationTable"] and app and app.appName != "nvda": tables += dictionaries.dictTables - if input_: mainTable = os.path.join(brailleTables.TABLES_DIR, brailleInput.handler._table.fileName) - else: mainTable = os.path.join(brailleTables.TABLES_DIR, config.conf["braille"]["translationTable"]) - tables += [ - mainTable, + if brailleInput.handler._table.fileName == config.conf["braille"]["translationTable"] and app and app.appName != "nvda": tables += dictionaries.inputTables if input_ else dictionaries.outputTables + if input_: + mainTable = os.path.join(brailleTables.TABLES_DIR, brailleInput.handler._table.fileName) + group = brailleTablesExt.getGroup(usableIn='i') + else: + mainTable = os.path.join(brailleTables.TABLES_DIR, config.conf["braille"]["translationTable"]) + group = brailleTablesExt.getGroup(usableIn='o') + if group: + group = group.members + group = [f if '\\' in f else osp.join(r"louis\tables", f) for f in group] + tbl = group or [mainTable] + tables += tbl + [ os.path.join(brailleTables.TABLES_DIR, "braille-patterns.cti") ] return tables