86 new commits in nvda: https://bitbucket.org/nvdaaddonteam/nvda/commits/c98095098c80/ Changeset: c98095098c80 Branch: None User: jteh Date: 2013-08-22 14:53:24 Summary: Initial code for retrieving info about all bound gestures based on the current focus. This is far from complete. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 9feaf8f..bd0aa83 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -27,6 +27,9 @@ import globalVars import languageHandler import controlTypes +SCRCAT_KBEMU = _("Emulated system keyboard keys") +SCRCAT_MISC = _("Miscellaneous") + class NoInputGestureAction(LookupError): """Informs that there is no action to execute for a gesture. """ @@ -249,6 +252,16 @@ class GlobalGestureMap(object): continue yield cls, scriptName + def getScriptsForAllGestures(self): + """Get all of the scripts and their gestures. + @return: The Python class, gesture and script name for each mapping; + the script name may be C{None} indicating that the gesture should be unbound for this class. + @rtype: generator of (class, str, str) + """ + for gesture in self._map: + for cls, scriptName in self.getScriptsForGesture(gesture): + yield cls, gesture, scriptName + class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user. Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. @@ -379,6 +392,131 @@ class InputManager(baseObject.AutoPropertyObject): except NotImplementedError: pass + def getAllGestureMappings(self, obj=None): + if not obj: + obj = api.getFocusObject() + return _AllGestureMappingsRetriever(obj).results + +class _AllGestureMappingsRetriever(object): + + def __init__(self, obj): + self.results = {} + self.scriptInfo = {} + self.handledGestures = set() + + self.addGlobalMap(manager.userGestureMap, byUser=True) + self.addGlobalMap(manager.localeGestureMap) + import braille + gmap = braille.handler.display.gestureMap + if gmap: + self.addGlobalMap(gmap) + + # Global plugins. + import globalPluginHandler + for plugin in globalPluginHandler.runningPlugins: + self.addObjBindings(plugin) + + # App module. + app = obj.appModule + if app: + self.addObjBindings(app) + + # Tree interceptor. + ti = obj.treeInterceptor + if ti: + self.addObjBindings(ti) + + # NVDAObject. + self.addObjBindings(obj) + + # Global commands. + import globalCommands + self.addObjBindings(globalCommands.commands) + + def addResult(self, scriptInfo): + self.scriptInfo[scriptInfo.cls, scriptInfo.scriptName] = scriptInfo + try: + cat = self.results[scriptInfo.category] + except KeyError: + cat = self.results[scriptInfo.category] = {} + cat[scriptInfo.displayName] = scriptInfo + + def addGlobalMap(self, gmap, byUser=False): + for cls, gesture, scriptName in gmap.getScriptsForAllGestures(): + key = (cls, gesture) + if key in self.handledGestures: + continue + self.handledGestures.add(key) + if scriptName is None: + # The global map specified that no script should execute for this gesture and object. + return + try: + scriptInfo = self.scriptInfo[cls, scriptName] + except KeyError: + if scriptName.startswith("kb:"): + scriptInfo = self.makeKbEmuScriptInfo(cls, scriptName) + else: + try: + script = getattr(cls, "script_%s" % scriptName) + except AttributeError: + continue + scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + if not scriptInfo: + continue + self.addResult(scriptInfo) + scriptInfo.gestures.append((gesture, byUser)) + + def makeKbEmuScriptInfo(self, cls, scriptName): + info = AllGesturesScriptInfo(cls, scriptName) + info.category = SCRCAT_KBEMU + info.displayName = scriptName[3:] + return info + + def makeNormalScriptInfo(self, cls, scriptName, script): + info = AllGesturesScriptInfo(cls, scriptName) + info.category = self.getScriptCategory(cls, script) + try: + info.displayName = script.__doc__ + except AttributeError: + return None + return info + + def getScriptCategory(self, cls, script): + try: + return script.category + except AttributeError: + pass + try: + return cls.scriptCategory + except AttributeError: + pass + return SCRCAT_MISC + + def addObjBindings(self, obj): + for gesture, script in obj._gestureMap.iteritems(): + scriptName = script.__name__[7:] + cls = self.getScriptCls(script) + try: + scriptInfo = self.scriptInfo[cls, scriptName] + except KeyError: + scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + self.addResult(scriptInfo) + scriptInfo.gestures.append((gesture, False)) + + def getScriptCls(self, script): + name = script.__name__ + for cls in script.im_class.__mro__: + if name in cls.__dict__: + return cls + +class AllGesturesScriptInfo(object): + __slots__ = ("cls", "scriptName", "category", "displayName", "gestures") + + def __init__(self, cls, scriptName): + self.cls = cls + self.scriptName = scriptName + self.gestures = [] + def normalizeGestureIdentifier(identifier): """Normalize a gesture identifier so that it matches other identifiers for the same gesture. Any items separated by a + sign after the source are considered to be of indeterminate order https://bitbucket.org/nvdaaddonteam/nvda/commits/949f71d815df/ Changeset: 949f71d815df Branch: None User: jteh Date: 2013-08-22 15:07:33 Summary: Fix typo. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index bd0aa83..33b85e3 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -449,7 +449,7 @@ class _AllGestureMappingsRetriever(object): self.handledGestures.add(key) if scriptName is None: # The global map specified that no script should execute for this gesture and object. - return + continue try: scriptInfo = self.scriptInfo[cls, scriptName] except KeyError: https://bitbucket.org/nvdaaddonteam/nvda/commits/dfe6400934a1/ Changeset: dfe6400934a1 Branch: None User: jteh Date: 2013-08-23 03:18:30 Summary: Add translator comments and code doc for script categories. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 33b85e3..0422486 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -27,7 +27,11 @@ import globalVars import languageHandler import controlTypes +#: Script category for emulated keyboard keys. +# Translators: The name of a category of NVDA commands. SCRCAT_KBEMU = _("Emulated system keyboard keys") +#: Script category for miscellaneous commands. +# Translators: The name of a category of NVDA commands. SCRCAT_MISC = _("Miscellaneous") class NoInputGestureAction(LookupError): https://bitbucket.org/nvdaaddonteam/nvda/commits/f9dbb751407b/ Changeset: f9dbb751407b Branch: None User: jteh Date: 2013-08-23 03:19:28 Summary: getAllGestureMappings: Fix issues related to scripts with no display name. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 0422486..5fc6c13 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -479,9 +479,8 @@ class _AllGestureMappingsRetriever(object): def makeNormalScriptInfo(self, cls, scriptName, script): info = AllGesturesScriptInfo(cls, scriptName) info.category = self.getScriptCategory(cls, script) - try: - info.displayName = script.__doc__ - except AttributeError: + info.displayName = script.__doc__ + if not info.displayName: return None return info @@ -504,6 +503,8 @@ class _AllGestureMappingsRetriever(object): scriptInfo = self.scriptInfo[cls, scriptName] except KeyError: scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + if not scriptInfo: + continue self.addResult(scriptInfo) scriptInfo.gestures.append((gesture, False)) https://bitbucket.org/nvdaaddonteam/nvda/commits/8470bd46768a/ Changeset: 8470bd46768a Branch: None User: jteh Date: 2013-08-23 03:21:40 Summary: Initial work on a GUI for input gestures. Right now, it just displays the commands in a tree control. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 28d89ea..83530d0 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -30,6 +30,7 @@ try: import updateCheck except RuntimeError: updateCheck = None +import inputCore class SettingsDialog(wx.Dialog): """A settings dialog. @@ -1510,3 +1511,22 @@ class SpeechSymbolsDialog(SettingsDialog): log.error("Error saving user symbols info: %s" % e) characterProcessing._localeSpeechSymbolProcessors.invalidateLocaleData(self.symbolProcessor.locale) super(SpeechSymbolsDialog, self).onOk(evt) + +class InputGesturesDialog(SettingsDialog): + # Translators: The title of the Input Gestures dialog where the user can remap input gestures for commands. + title = _("Input Gestures") + + def makeSettings(self, settingsSizer): + tree = self.tree = wx.TreeCtrl(self, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_SINGLE) + self.treeRoot = tree.AddRoot("root") + settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) + gestures = inputCore.manager.getAllGestureMappings() + for category in sorted(gestures): + parent = tree.AppendItem(self.treeRoot, category) + commands = gestures[category] + for command in sorted(commands): + item = tree.AppendItem(parent, command) + self.tree.SetItemPyData(item, commands[command]) + + def postInit(self): + self.tree.SetFocus() https://bitbucket.org/nvdaaddonteam/nvda/commits/b30f96aa3a8d/ Changeset: b30f96aa3a8d Branch: None User: jteh Date: 2013-08-26 09:32:40 Summary: Add Input gestures item under NVDA Preferences menu. Affected #: 1 file diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 6428d32..55edcde 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -225,6 +225,9 @@ class MainFrame(wx.Frame): def onSpeechSymbolsCommand(self, evt): self._popupSettingsDialog(SpeechSymbolsDialog) + def onInputGesturesCommand(self, evt): + self._popupSettingsDialog(InputGesturesDialog) + def onAboutCommand(self,evt): # Translators: The title of the dialog to show about info for NVDA. messageBox(versionInfo.aboutMessage, _("About NVDA"), wx.OK) @@ -342,6 +345,9 @@ class SysTrayIcon(wx.TaskBarIcon): # Translators: The label for the menu item to open Punctuation/symbol pronunciation dialog. item = menu_preferences.Append(wx.ID_ANY, _("&Punctuation/symbol pronunciation...")) self.Bind(wx.EVT_MENU, frame.onSpeechSymbolsCommand, item) + # Translators: The label for the menu item to open the Input Gestures dialog. + item = menu_preferences.Append(wx.ID_ANY, _("I&nput gestures...")) + self.Bind(wx.EVT_MENU, frame.onInputGesturesCommand, item) # Translators: The label for Preferences submenu in NVDA menu. self.menu.AppendMenu(wx.ID_ANY,_("&Preferences"),menu_preferences) https://bitbucket.org/nvdaaddonteam/nvda/commits/164f5613181b/ Changeset: 164f5613181b Branch: None User: jteh Date: 2013-08-27 04:50:40 Summary: getAllGestureMappings: Kill unneeded byUser boolean in gestures. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 5fc6c13..7277a25 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -408,7 +408,7 @@ class _AllGestureMappingsRetriever(object): self.scriptInfo = {} self.handledGestures = set() - self.addGlobalMap(manager.userGestureMap, byUser=True) + self.addGlobalMap(manager.userGestureMap) self.addGlobalMap(manager.localeGestureMap) import braille gmap = braille.handler.display.gestureMap @@ -445,7 +445,7 @@ class _AllGestureMappingsRetriever(object): cat = self.results[scriptInfo.category] = {} cat[scriptInfo.displayName] = scriptInfo - def addGlobalMap(self, gmap, byUser=False): + def addGlobalMap(self, gmap): for cls, gesture, scriptName in gmap.getScriptsForAllGestures(): key = (cls, gesture) if key in self.handledGestures: @@ -468,7 +468,7 @@ class _AllGestureMappingsRetriever(object): if not scriptInfo: continue self.addResult(scriptInfo) - scriptInfo.gestures.append((gesture, byUser)) + scriptInfo.gestures.append(gesture) def makeKbEmuScriptInfo(self, cls, scriptName): info = AllGesturesScriptInfo(cls, scriptName) @@ -506,7 +506,7 @@ class _AllGestureMappingsRetriever(object): if not scriptInfo: continue self.addResult(scriptInfo) - scriptInfo.gestures.append((gesture, False)) + scriptInfo.gestures.append(gesture) def getScriptCls(self, script): name = script.__name__ https://bitbucket.org/nvdaaddonteam/nvda/commits/a7dce3fbe709/ Changeset: a7dce3fbe709 Branch: None User: jteh Date: 2013-08-27 05:12:12 Summary: InputGesturesDialog: Include the gestures in the tree as children of the commands. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 83530d0..bc7591f 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1522,11 +1522,14 @@ class InputGesturesDialog(SettingsDialog): settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) gestures = inputCore.manager.getAllGestureMappings() for category in sorted(gestures): - parent = tree.AppendItem(self.treeRoot, category) + treeCat = tree.AppendItem(self.treeRoot, category) commands = gestures[category] for command in sorted(commands): - item = tree.AppendItem(parent, command) - self.tree.SetItemPyData(item, commands[command]) + treeCom = tree.AppendItem(treeCat, command) + commandInfo = commands[command] + tree.SetItemPyData(treeCom, commandInfo) + for gesture in commandInfo.gestures: + tree.AppendItem(treeCom, gesture) def postInit(self): self.tree.SetFocus() https://bitbucket.org/nvdaaddonteam/nvda/commits/837a4ccb82f9/ Changeset: 837a4ccb82f9 Branch: None User: jteh Date: 2013-08-27 09:44:24 Summary: getAllGestureMappings: Include scripts that aren't bound to any gesture. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 7277a25..e895643 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -418,24 +418,24 @@ class _AllGestureMappingsRetriever(object): # Global plugins. import globalPluginHandler for plugin in globalPluginHandler.runningPlugins: - self.addObjBindings(plugin) + self.addObj(plugin) # App module. app = obj.appModule if app: - self.addObjBindings(app) + self.addObj(app) # Tree interceptor. ti = obj.treeInterceptor if ti: - self.addObjBindings(ti) + self.addObj(ti) # NVDAObject. - self.addObjBindings(obj) + self.addObj(obj) # Global commands. import globalCommands - self.addObjBindings(globalCommands.commands) + self.addObj(globalCommands.commands) def addResult(self, scriptInfo): self.scriptInfo[scriptInfo.cls, scriptInfo.scriptName] = scriptInfo @@ -495,25 +495,28 @@ class _AllGestureMappingsRetriever(object): pass return SCRCAT_MISC - def addObjBindings(self, obj): + def addObj(self, obj): + scripts = {} + for cls in obj.__class__.__mro__: + for scriptName, script in cls.__dict__.iteritems(): + if not scriptName.startswith("script_"): + continue + scriptName = scriptName[7:] + try: + scriptInfo = self.scriptInfo[cls, scriptName] + except KeyError: + scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + if not scriptInfo: + continue + self.addResult(scriptInfo) + scripts[script] = scriptInfo for gesture, script in obj._gestureMap.iteritems(): - scriptName = script.__name__[7:] - cls = self.getScriptCls(script) try: - scriptInfo = self.scriptInfo[cls, scriptName] + scriptInfo = scripts[script.__func__] except KeyError: - scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) - if not scriptInfo: - continue - self.addResult(scriptInfo) + continue scriptInfo.gestures.append(gesture) - def getScriptCls(self, script): - name = script.__name__ - for cls in script.im_class.__mro__: - if name in cls.__dict__: - return cls - class AllGesturesScriptInfo(object): __slots__ = ("cls", "scriptName", "category", "displayName", "gestures") https://bitbucket.org/nvdaaddonteam/nvda/commits/23c95d437dc4/ Changeset: 23c95d437dc4 Branch: None User: jteh Date: 2013-08-28 05:21:32 Summary: InputGesturesDialog: Provide Add and Remove buttons that are only enabled when appropriate. They don't do anything just yet. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index bc7591f..ce9df5c 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1519,7 +1519,9 @@ class InputGesturesDialog(SettingsDialog): def makeSettings(self, settingsSizer): tree = self.tree = wx.TreeCtrl(self, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_SINGLE) self.treeRoot = tree.AddRoot("root") + tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect) settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) + gestures = inputCore.manager.getAllGestureMappings() for category in sorted(gestures): treeCat = tree.AppendItem(self.treeRoot, category) @@ -1529,7 +1531,35 @@ class InputGesturesDialog(SettingsDialog): commandInfo = commands[command] tree.SetItemPyData(treeCom, commandInfo) for gesture in commandInfo.gestures: - tree.AppendItem(treeCom, gesture) + treeGes = tree.AppendItem(treeCom, gesture) + tree.SetItemPyData(treeGes, gesture) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + # Translators: The label of a button to add a gesture in the Input Gestures dialog. + item = self.addButton = wx.Button(self, label=_("&Add")) + item.Bind(wx.EVT_BUTTON, self.onAdd) + item.Disable() + sizer.Add(item) + # Translators: The label of a button to remove a gesture in the Input Gestures dialog. + item = self.removeButton = wx.Button(self, label=_("&Remove")) + item.Bind(wx.EVT_BUTTON, self.onRemove) + item.Disable() + sizer.Add(item) + settingsSizer.Add(sizer) def postInit(self): self.tree.SetFocus() + + def onTreeSelect(self, evt): + item = evt.Item + data = self.tree.GetItemPyData(item) + isCommand = isinstance(data, inputCore.AllGesturesScriptInfo) + isGesture = isinstance(data, basestring) + self.addButton.Enabled = isCommand or isGesture + self.removeButton.Enabled = isGesture + + def onAdd(self, evt): + pass + + def onRemove(self, evt): + pass https://bitbucket.org/nvdaaddonteam/nvda/commits/2988806143d7/ Changeset: 2988806143d7 Branch: None User: jteh Date: 2013-08-28 05:58:42 Summary: getAllGestureMappings: Account for the user unbinding gestures defined on objects. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index e895643..21c7ef2 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -515,6 +515,10 @@ class _AllGestureMappingsRetriever(object): scriptInfo = scripts[script.__func__] except KeyError: continue + key = (scriptInfo.cls, gesture) + if key in self.handledGestures: + continue + self.handledGestures.add(key) scriptInfo.gestures.append(gesture) class AllGesturesScriptInfo(object): https://bitbucket.org/nvdaaddonteam/nvda/commits/716d4040f948/ Changeset: 716d4040f948 Branch: None User: jteh Date: 2013-08-28 06:00:19 Summary: InputGesturesDialog: Add code for removing gestures. GlobalGestureMap grew a remove method to facilitate this. Affected #: 2 files diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index ce9df5c..35b190d 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1544,6 +1544,7 @@ class InputGesturesDialog(SettingsDialog): item = self.removeButton = wx.Button(self, label=_("&Remove")) item.Bind(wx.EVT_BUTTON, self.onRemove) item.Disable() + self.pendingRemoves = set() sizer.Add(item) settingsSizer.Add(sizer) @@ -1562,4 +1563,23 @@ class InputGesturesDialog(SettingsDialog): pass def onRemove(self, evt): - pass + treeGes = self.tree.Selection + gesture = self.tree.GetItemPyData(treeGes) + treeCom = self.tree.GetItemParent(treeGes) + scriptInfo = self.tree.GetItemPyData(treeCom) + cls = scriptInfo.cls + module = cls.__module__ + className = cls.__name__ + self.pendingRemoves.add((gesture, module, className, scriptInfo.scriptName)) + self.tree.Delete(treeGes) + self.tree.SetFocus() + + def onOk(self, evt): + for gesture, module, className, scriptName in self.pendingRemoves: + try: + inputCore.manager.userGestureMap.remove(gesture, module, className, scriptName) + except ValueError: + # The user wants to unbind a gesture they didn't define. + inputCore.manager.userGestureMap.add(gesture, module, className, None) + + super(InputGesturesDialog, self).onOk(evt) diff --git a/source/inputCore.py b/source/inputCore.py index 21c7ef2..232f3fe 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -266,6 +266,25 @@ class GlobalGestureMap(object): for cls, scriptName in self.getScriptsForGesture(gesture): yield cls, gesture, scriptName + def remove(self, gesture, module, className, script): + """Remove a gesture mapping. + @param gesture: The gesture identifier. + @type gesture: str + @param module: The name of the Python module containing the target script. + @type module: str + @param className: The name of the class in L{module} containing the target script. + @type className: str + @param script: The name of the target script. + @type script: str + @raise ValueError: If the requested mapping does not exist. + """ + gesture = normalizeGestureIdentifier(gesture) + try: + scripts = self._map[gesture] + except KeyError: + raise ValueError("Mapping not found") + scripts.remove((module, className, script)) + class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user. Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. https://bitbucket.org/nvdaaddonteam/nvda/commits/3dc9d322925f/ Changeset: 3dc9d322925f Branch: None User: jteh Date: 2013-08-29 01:36:05 Summary: inputCore: Abstract the capturing of gestures (as used by input help) so that it can be used for entering gestures in the Input Gestures dialog. This is still only intended for internal use, as incorrect usage could have nasty consequdnces. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 232f3fe..6a65cca 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -291,9 +291,10 @@ class InputManager(baseObject.AutoPropertyObject): """ def __init__(self): - #: Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed. - #: @type: bool - self.isInputHelpActive = False + #: The function to call when capturing gestures. + #: If it returns C{False}, normal execution will be prevented. + #: @type: callable + self._captureFunc = None #: The gestures mapped for the NVDA locale. #: @type: L{GlobalGestureMap} self.localeGestureMap = GlobalGestureMap() @@ -329,11 +330,13 @@ class InputManager(baseObject.AutoPropertyObject): if log.isEnabledFor(log.IO) and not gesture.isModifier: log.io("Input: %s" % gesture.logIdentifier) - if self.isInputHelpActive: - bypass = getattr(script, "bypassInputHelp", False) - queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass) - if not bypass: - return + if self._captureFunc: + try: + if self._captureFunc(gesture) is False: + return + except: + log.error("Error in capture function, disabling", exc_info=True) + self._captureFunc = None if gesture.isModifier: raise NoInputGestureAction @@ -349,6 +352,23 @@ class InputManager(baseObject.AutoPropertyObject): raise NoInputGestureAction + def _get_isInputHelpActive(self): + """Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed. + @rtype: bool + """ + return self._captureFunc == self._inputHelpCaptor + + def _set_isInputHelpActive(self, enable): + if enable: + self._captureFunc = self._inputHelpCaptor + elif self.isInputHelpActive: + self._captureFunc = None + + def _inputHelpCaptor(self, gesture): + bypass = getattr(gesture.script, "bypassInputHelp", False) + queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass) + return bypass + def _handleInputHelp(self, gesture, onlyLog=False): textList = [gesture.displayName] script = gesture.script https://bitbucket.org/nvdaaddonteam/nvda/commits/44d55637e7b1/ Changeset: 44d55637e7b1 Branch: None User: jteh Date: 2013-08-29 02:51:30 Summary: InputGesturesDialog: Add support for adding gestures. Affected #: 2 files diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 35b190d..8a76136 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1544,6 +1544,7 @@ class InputGesturesDialog(SettingsDialog): item = self.removeButton = wx.Button(self, label=_("&Remove")) item.Bind(wx.EVT_BUTTON, self.onRemove) item.Disable() + self.pendingAdds = set() self.pendingRemoves = set() sizer.Add(item) settingsSizer.Add(sizer) @@ -1552,7 +1553,7 @@ class InputGesturesDialog(SettingsDialog): self.tree.SetFocus() def onTreeSelect(self, evt): - item = evt.Item + item = self.tree.Selection data = self.tree.GetItemPyData(item) isCommand = isinstance(data, inputCore.AllGesturesScriptInfo) isGesture = isinstance(data, basestring) @@ -1560,17 +1561,40 @@ class InputGesturesDialog(SettingsDialog): self.removeButton.Enabled = isGesture def onAdd(self, evt): - pass + if inputCore.manager._captureFunc: + return + + treeCom = self.tree.Selection + scriptInfo = self.tree.GetItemPyData(treeCom) + if not isinstance(scriptInfo, inputCore.AllGesturesScriptInfo): + treeCom = self.tree.GetItemParent(treeCom) + scriptInfo = self.tree.GetItemPyData(treeCom) + # Translators: The prompt to enter a gesture in the Input Gestures dialog. + treeGes = self.tree.AppendItem(treeCom, _("Enter input gesture:")) + self.tree.SelectItem(treeGes) + self.tree.SetFocus() + + def addGestureCaptor(gesture): + if gesture.isModifier: + return False + inputCore.manager._captureFunc = None + wx.CallAfter(self._finishAdd, treeGes, scriptInfo, gesture) + return False + inputCore.manager._captureFunc = addGestureCaptor + + def _finishAdd(self, treeGes, scriptInfo, gesture): + gestureId = gesture.logIdentifier + self.pendingAdds.add((gestureId, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) + self.tree.SetItemText(treeGes, gesture.logIdentifier) + self.tree.SetItemPyData(treeGes, gestureId) + self.onTreeSelect(None) def onRemove(self, evt): treeGes = self.tree.Selection gesture = self.tree.GetItemPyData(treeGes) treeCom = self.tree.GetItemParent(treeGes) scriptInfo = self.tree.GetItemPyData(treeCom) - cls = scriptInfo.cls - module = cls.__module__ - className = cls.__name__ - self.pendingRemoves.add((gesture, module, className, scriptInfo.scriptName)) + self.pendingRemoves.add((gesture, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) self.tree.Delete(treeGes) self.tree.SetFocus() @@ -1582,4 +1606,13 @@ class InputGesturesDialog(SettingsDialog): # The user wants to unbind a gesture they didn't define. inputCore.manager.userGestureMap.add(gesture, module, className, None) + for gesture, module, className, scriptName in self.pendingAdds: + try: + # The user might have unbound this gesture, + # so remove this override first. + inputCore.manager.userGestureMap.remove(gesture, module, className, None) + except ValueError: + pass + inputCore.manager.userGestureMap.add(gesture, module, className, scriptName) + super(InputGesturesDialog, self).onOk(evt) diff --git a/source/inputCore.py b/source/inputCore.py index 6a65cca..c8dae30 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -568,6 +568,14 @@ class AllGesturesScriptInfo(object): self.scriptName = scriptName self.gestures = [] + @property + def moduleName(self): + return self.cls.__module__ + + @property + def className(self): + return self.cls.__name__ + def normalizeGestureIdentifier(identifier): """Normalize a gesture identifier so that it matches other identifiers for the same gesture. Any items separated by a + sign after the source are considered to be of indeterminate order https://bitbucket.org/nvdaaddonteam/nvda/commits/c24f41f8a099/ Changeset: c24f41f8a099 Branch: None User: jteh Date: 2013-08-29 04:15:56 Summary: Save user defined gestures when there are changes and OK is pressed in the Input Gestures dialog. GlobalGestureMap grew a save method to facilitate this. Affected #: 2 files diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 8a76136..6b5421b 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1615,4 +1615,14 @@ class InputGesturesDialog(SettingsDialog): pass inputCore.manager.userGestureMap.add(gesture, module, className, scriptName) + if self.pendingAdds or self.pendingRemoves: + # Only save if there is something to save. + try: + inputCore.manager.userGestureMap.save() + except: + log.debugWarning("", exc_info=True) + # Translators: An error displayed when saving user defined input gestures fails. + gui.messageBox(_("Error saving user defined gestures - probably read only file system."), + _("Error"), wx.OK | wx.ICON-ERROR) + super(InputGesturesDialog, self).onOk(evt) diff --git a/source/inputCore.py b/source/inputCore.py index c8dae30..f96f253 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -137,6 +137,9 @@ class GlobalGestureMap(object): #: Indicates that the last load or update contained an error. #: @type: bool self.lastUpdateContainedError = False + #: The file name for this gesture map, if any. + #: @type: basestring + self.fileName = None if entries: self.update(entries) @@ -184,6 +187,7 @@ class GlobalGestureMap(object): @param filename: The name of the file to load. @type: str """ + self.fileName = filename try: conf = configobj.ConfigObj(filename, file_error=True, encoding="UTF-8") except (configobj.ConfigObjError,UnicodeDecodeError), e: @@ -285,6 +289,40 @@ class GlobalGestureMap(object): raise ValueError("Mapping not found") scripts.remove((module, className, script)) + def save(self): + """Save this gesture map to disk. + @precondition: L{load} must have been called. + """ + if globalVars.appArgs.secure: + return + if not self.fileName: + raise ValueError("No file name") + out = configobj.ConfigObj() + out.filename = self.fileName + + for gesture, scripts in self._map.iteritems(): + for module, className, script in scripts: + key = "%s.%s" % (module, className) + try: + outSect = out[key] + except KeyError: + out[key] = {} + outSect = out[key] + if script is None: + script = "None" + try: + outVal = outSect[script] + except KeyError: + # Write the first value as a string so configobj doesn't output a comma if there's only one value. + outVal = outSect[script] = gesture + else: + if isinstance(outVal, list): + outVal.append(gesture) + else: + outSect[script] = [outVal, gesture] + + out.write() + class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user. Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. https://bitbucket.org/nvdaaddonteam/nvda/commits/db0fddf2a52d/ Changeset: db0fddf2a52d Branch: None User: jteh Date: 2013-08-29 06:27:06 Summary: getAllGestureMappings: Include ancestor NVDAObjects, using focus ancestors by default. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index f96f253..3b70125 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -473,14 +473,15 @@ class InputManager(baseObject.AutoPropertyObject): except NotImplementedError: pass - def getAllGestureMappings(self, obj=None): + def getAllGestureMappings(self, obj=None, ancestors=None): if not obj: obj = api.getFocusObject() - return _AllGestureMappingsRetriever(obj).results + ancestors = api.getFocusAncestors() + return _AllGestureMappingsRetriever(obj, ancestors).results class _AllGestureMappingsRetriever(object): - def __init__(self, obj): + def __init__(self, obj, ancestors): self.results = {} self.scriptInfo = {} self.handledGestures = set() @@ -509,6 +510,8 @@ class _AllGestureMappingsRetriever(object): # NVDAObject. self.addObj(obj) + for anc in reversed(ancestors): + self.addObj(anc, isAncestor=True) # Global commands. import globalCommands @@ -572,12 +575,14 @@ class _AllGestureMappingsRetriever(object): pass return SCRCAT_MISC - def addObj(self, obj): + def addObj(self, obj, isAncestor=False): scripts = {} for cls in obj.__class__.__mro__: for scriptName, script in cls.__dict__.iteritems(): if not scriptName.startswith("script_"): continue + if isAncestor and not getattr(script, "canPropagate", False): + continue scriptName = scriptName[7:] try: scriptInfo = self.scriptInfo[cls, scriptName] https://bitbucket.org/nvdaaddonteam/nvda/commits/b266daf99ffa/ Changeset: b266daf99ffa Branch: None User: mdcurran Date: 2013-09-02 01:08:06 Summary: MSHTML VBufBackend: rather than use attachEvent / detachEvent, use IConnectionPoint and friends. This *may* be more stable and *may* stop crashes after NVDA exit in Internet Explorer. Affected #: 3 files diff --git a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp index 8f2adb6..e8590b4 100755 --- a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp +++ b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp @@ -561,8 +561,12 @@ inline void fillTextFormattingForNode(IHTMLDOMNode* pHTMLDOMNode, VBufStorage_fi inline void fillTextFormattingForTextNode(VBufStorage_controlFieldNode_t* parentNode, VBufStorage_textFieldNode_t* textNode) //text nodes don't support IHTMLElement2 interface, so using style information from parent node { - IHTMLElement2* pHTMLElement2=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(parentNode))->pHTMLElement2; - fillTextFormatting_helper(pHTMLElement2,textNode); + IHTMLElement2* pHTMLElement2=NULL; + static_cast<MshtmlVBufStorage_controlFieldNode_t*>(parentNode)->pHTMLDOMNode->QueryInterface(IID_IHTMLElement2,(void**)&pHTMLElement2); + if(pHTMLElement2) { + fillTextFormatting_helper(pHTMLElement2,textNode); + pHTMLElement2->Release(); + } } const int TABLEHEADER_COLUMN = 0x1; @@ -1227,11 +1231,11 @@ void MshtmlVBufBackend_t::render(VBufStorage_buffer_t* buffer, int docHandle, in LOG_DEBUG(L"Error getting document using WM_HTML_GETOBJECT"); return; } -IHTMLDOMNode* pHTMLDOMNode=NULL; + IHTMLDOMNode* pHTMLDOMNode=NULL; if(oldNode!=NULL) { - IHTMLElement2* pHTMLElement2=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(oldNode))->pHTMLElement2; - nhAssert(pHTMLElement2); - pHTMLElement2->QueryInterface(IID_IHTMLDOMNode,(void**)&pHTMLDOMNode); + pHTMLDOMNode=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(oldNode))->pHTMLDOMNode; + nhAssert(pHTMLDOMNode); + pHTMLDOMNode->AddRef(); } else { IHTMLDocument3* pHTMLDocument3=NULL; if(ObjectFromLresult(res,IID_IHTMLDocument3,0,(void**)&pHTMLDocument3)!=S_OK) { diff --git a/nvdaHelper/vbufBackends/mshtml/node.cpp b/nvdaHelper/vbufBackends/mshtml/node.cpp index c7d9b5b..c3f02d1 100755 --- a/nvdaHelper/vbufBackends/mshtml/node.cpp +++ b/nvdaHelper/vbufBackends/mshtml/node.cpp @@ -14,8 +14,10 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <list> #include <windows.h> +#include <objbase.h> #include <oleidl.h> #include <mshtml.h> +#include <mshtmdid.h> #include <common/log.h> #include "mshtml.h" #include "node.h" @@ -25,33 +27,71 @@ using namespace std; class CDispatchChangeSink : public IDispatch { private: ULONG refCount; - bool hasFired; + MshtmlVBufStorage_controlFieldNode_t* storageNode; + IConnectionPoint* pConnectionPoint; + DWORD dwCookie; public: - MshtmlVBufStorage_controlFieldNode_t* storageNode; - bool allowDelete; - CDispatchChangeSink(MshtmlVBufStorage_controlFieldNode_t* storageNode): - refCount(1), - hasFired(false), - allowDelete(true) { + CDispatchChangeSink(MshtmlVBufStorage_controlFieldNode_t* storageNode) : + refCount(1), dwCookie(0), pConnectionPoint(NULL) { nhAssert(storageNode); this->storageNode=storageNode; incBackendLibRefCount(); } + BOOL connect(IHTMLDOMNode* pHTMLDOMNode, REFIID iid) { + if(dwCookie) { + LOG_DEBUGWARNING(L"Already connected"); + return false; + } + IHTMLElement* pHTMLElement=NULL; + pHTMLDOMNode->QueryInterface(IID_IHTMLElement,(void**)&pHTMLElement); + if(!pHTMLElement) { + LOG_DEBUGWARNING(L"QueryInterface to IHTMLElement failed"); + return false; + } + IConnectionPointContainer* pConnectionPointContainer=NULL; + pHTMLElement->QueryInterface(IID_IConnectionPointContainer,(void**)&pConnectionPointContainer); + pHTMLElement->Release(); + if(!pConnectionPointContainer) { + LOG_DEBUGWARNING(L"QueryInterface to IConnectionPointContainer failed"); + return false; + } + IConnectionPoint* pConnectionPoint=NULL; + pConnectionPointContainer->FindConnectionPoint(iid,&pConnectionPoint); + pConnectionPointContainer->Release(); + if(!pConnectionPoint) { + return false; + } + DWORD dwCookie=0; + pConnectionPoint->Advise(this,&dwCookie); + if(!dwCookie) { + pConnectionPoint->Release(); + return false; + } + this->pConnectionPoint=pConnectionPoint; + this->dwCookie=dwCookie; + return true; + } + + BOOL disconnect() { + if(this->dwCookie==0) return false; + this->pConnectionPoint->Unadvise(this->dwCookie); + this->dwCookie=0; + this->pConnectionPoint->Release(); + this->pConnectionPoint=NULL; + return true; + } + ~CDispatchChangeSink() { + this->disconnect(); decBackendLibRefCount(); } void onChange() { - if(hasFired||allowDelete) { - return; - } - hasFired=true; LOG_DEBUG(L"Marking storage node as invalid"); this->storageNode->backend->invalidateSubtree(this->storageNode); - LOG_DEBUG(L"Done"); } HRESULT STDMETHODCALLTYPE IUnknown::QueryInterface(REFIID riid, void** pvpObject) { @@ -77,28 +117,18 @@ class CDispatchChangeSink : public IDispatch { if(this->refCount>0) this->refCount--; if(this->refCount==0) { - if (this->allowDelete) { - delete this; - } else { - #ifdef DEBUG - Beep(660,50); - #endif - LOG_DEBUG(L"refCount hit 0 before it should, not deleting, node info: " << this->storageNode->getDebugInfo()); - } + delete this; return 0; } return this->refCount; } HRESULT STDMETHODCALLTYPE IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { - if(dispIdMember==0) { - LOG_DEBUG(L"calling onChange"); + if(1) { //dispIdMember==DISPID_HTMLELEMENTEVENTS2_ONPROPERTYCHANGE||dispIdMember==DISPID_IHTMLELEMENT2_ONPROPERTYCHANGE) { this->onChange(); - LOG_DEBUG(L"Done, returning S_OK"); return S_OK; } - LOG_DEBUG(L"invoke called with unknown member ID, returning E_INVALIDARG"); - return E_INVALIDARG; + return E_FAIL; } HRESULT STDMETHODCALLTYPE IDispatch::GetTypeInfoCount(UINT* count) { @@ -238,48 +268,22 @@ class CHTMLChangeSink : public IHTMLChangeSink { }; MshtmlVBufStorage_controlFieldNode_t::MshtmlVBufStorage_controlFieldNode_t(int docHandle, int ID, bool isBlock, MshtmlVBufBackend_t* backend, IHTMLDOMNode* pHTMLDOMNode,const wstring& lang): VBufStorage_controlFieldNode_t(docHandle,ID,isBlock), language(lang) { - VARIANT_BOOL varBool; nhAssert(backend); nhAssert(pHTMLDOMNode); this->backend=backend; - this->pHTMLElement2=NULL; + pHTMLDOMNode->AddRef(); + this->pHTMLDOMNode=pHTMLDOMNode; this->propChangeSink=NULL; this->loadSink=NULL; this->pHTMLChangeSink=NULL; this->HTMLChangeSinkCookey=0; - pHTMLDOMNode->QueryInterface(IID_IHTMLElement2,(void**)&(this->pHTMLElement2)); - if(!this->pHTMLElement2) { - LOG_DEBUG(L"Could not queryInterface from IHTMLDOMNode to IHTMLElement2"); - } - if(this->pHTMLElement2) { - CDispatchChangeSink* propChangeSink=new CDispatchChangeSink(this); - // It seems that IE 6 sometimes calls Release() once too many times. - // We don't want propChangeSink to be deleted until we're finished with it. - propChangeSink->allowDelete=false; - if((pHTMLElement2->attachEvent(L"onpropertychange",propChangeSink,&varBool)==S_OK)&&varBool) { - this->propChangeSink=propChangeSink; - } else { - LOG_DEBUG(L"Error attaching onPropertyChange event sink to IHTMLElement2 at "<<pHTMLElement2); - propChangeSink->allowDelete=true; - propChangeSink->Release(); - } - } BSTR nodeName=NULL; pHTMLDOMNode->get_nodeName(&nodeName); - if(nodeName!=NULL&&(_wcsicmp(nodeName,L"frame")==0||_wcsicmp(nodeName,L"iframe")==0||_wcsicmp(nodeName,L"img")==0||_wcsicmp(nodeName,L"input")==0)) { - if(this->pHTMLElement2) { - CDispatchChangeSink* loadSink=new CDispatchChangeSink(this); - // It seems that IE 6 sometimes calls Release() once too many times. - // We don't want loadSink to be deleted until we're finished with it. - loadSink->allowDelete=false; - if((pHTMLElement2->attachEvent(L"onload",loadSink,&varBool)==S_OK)&&varBool) { - this->loadSink=loadSink; - } else { - LOG_DEBUG(L"Error attaching onload event sink to IHTMLElement2 at "<<pHTMLElement2); - loadSink->allowDelete=true; - loadSink->Release(); - } - } + CDispatchChangeSink* propChangeSink=new CDispatchChangeSink(this); + if(propChangeSink->connect(pHTMLDOMNode,IID_IDispatch)) { + this->propChangeSink=propChangeSink; + } else { + propChangeSink->Release(); } if(nodeName!=NULL&&(_wcsicmp(nodeName,L"body")==0||_wcsicmp(nodeName,L"frameset")==0)) { IHTMLDOMNode2* pHTMLDOMNode2=NULL; @@ -317,23 +321,15 @@ MshtmlVBufStorage_controlFieldNode_t::MshtmlVBufStorage_controlFieldNode_t(int d MshtmlVBufStorage_controlFieldNode_t::~MshtmlVBufStorage_controlFieldNode_t() { if(this->propChangeSink) { - nhAssert(this->pHTMLElement2); - if(pHTMLElement2->detachEvent(L"onpropertychange",this->propChangeSink)!=S_OK) { - LOG_DEBUG(L"Error detaching onpropertychange event sink from IHTMLElement2"); + if(!(static_cast<CDispatchChangeSink*>(this->propChangeSink)->disconnect())) { + LOG_DEBUGWARNING(L"Failed to stop listening with HTMLElementEvents2 for node "<<this->getDebugInfo()); } - static_cast<CDispatchChangeSink*>(this->propChangeSink)->allowDelete=true; this->propChangeSink->Release(); + this->propChangeSink=NULL; } - if(this->loadSink) { - nhAssert(this->pHTMLElement2); - if(pHTMLElement2->detachEvent(L"onload",this->loadSink)!=S_OK) { - LOG_DEBUG(L"Error detaching onload event sink from IHTMLElement2"); - } - static_cast<CDispatchChangeSink*>(this->loadSink)->allowDelete=true; - this->loadSink->Release(); - } - if(this->pHTMLElement2) { - this->pHTMLElement2->Release(); + if(this->pHTMLDOMNode) { + this->pHTMLDOMNode->Release(); + this->pHTMLDOMNode=NULL; } if(this->pHTMLChangeSink) { nhAssert(this->pMarkupContainer2); diff --git a/nvdaHelper/vbufBackends/mshtml/node.h b/nvdaHelper/vbufBackends/mshtml/node.h index 7fc7ae6..ec43b80 100755 --- a/nvdaHelper/vbufBackends/mshtml/node.h +++ b/nvdaHelper/vbufBackends/mshtml/node.h @@ -24,7 +24,7 @@ class MshtmlVBufStorage_controlFieldNode_t : public VBufStorage_controlFieldNode public: MshtmlVBufBackend_t* backend; - IHTMLElement2* pHTMLElement2; + IHTMLDOMNode* pHTMLDOMNode; IDispatch* propChangeSink; IDispatch* loadSink; IMarkupContainer2* pMarkupContainer2; https://bitbucket.org/nvdaaddonteam/nvda/commits/44c554e49489/ Changeset: 44c554e49489 Branch: None User: mdcurran Date: 2013-09-02 01:08:07 Summary: MSHTML VBufBackend: only handle dispid_evmeth_onload and dispid_evmeth_onpropchange in invoke for our IDispatch implementation that handles events. Also call backend->update directly by getting rid of the onchange method. These changes should improve performance. Affected #: 1 file diff --git a/nvdaHelper/vbufBackends/mshtml/node.cpp b/nvdaHelper/vbufBackends/mshtml/node.cpp index c3f02d1..84f44a8 100755 --- a/nvdaHelper/vbufBackends/mshtml/node.cpp +++ b/nvdaHelper/vbufBackends/mshtml/node.cpp @@ -89,11 +89,6 @@ class CDispatchChangeSink : public IDispatch { decBackendLibRefCount(); } - void onChange() { - LOG_DEBUG(L"Marking storage node as invalid"); - this->storageNode->backend->invalidateSubtree(this->storageNode); - } - HRESULT STDMETHODCALLTYPE IUnknown::QueryInterface(REFIID riid, void** pvpObject) { if(!pvpObject) return E_INVALIDARG; *pvpObject=NULL; @@ -124,8 +119,8 @@ class CDispatchChangeSink : public IDispatch { } HRESULT STDMETHODCALLTYPE IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { - if(1) { //dispIdMember==DISPID_HTMLELEMENTEVENTS2_ONPROPERTYCHANGE||dispIdMember==DISPID_IHTMLELEMENT2_ONPROPERTYCHANGE) { - this->onChange(); + if(dispIdMember==DISPID_EVMETH_ONPROPERTYCHANGE||dispIdMember==DISPID_EVMETH_ONLOAD) { + this->storageNode->backend->invalidateSubtree(this->storageNode); return S_OK; } return E_FAIL; https://bitbucket.org/nvdaaddonteam/nvda/commits/c1adf89116ed/ Changeset: c1adf89116ed Branch: None User: mdcurran Date: 2013-09-02 01:08:43 Summary: Merge branch 't3397' into next. Incubates #3397. Affected #: 3 files diff --git a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp index 8f2adb6..e8590b4 100755 --- a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp +++ b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp @@ -561,8 +561,12 @@ inline void fillTextFormattingForNode(IHTMLDOMNode* pHTMLDOMNode, VBufStorage_fi inline void fillTextFormattingForTextNode(VBufStorage_controlFieldNode_t* parentNode, VBufStorage_textFieldNode_t* textNode) //text nodes don't support IHTMLElement2 interface, so using style information from parent node { - IHTMLElement2* pHTMLElement2=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(parentNode))->pHTMLElement2; - fillTextFormatting_helper(pHTMLElement2,textNode); + IHTMLElement2* pHTMLElement2=NULL; + static_cast<MshtmlVBufStorage_controlFieldNode_t*>(parentNode)->pHTMLDOMNode->QueryInterface(IID_IHTMLElement2,(void**)&pHTMLElement2); + if(pHTMLElement2) { + fillTextFormatting_helper(pHTMLElement2,textNode); + pHTMLElement2->Release(); + } } const int TABLEHEADER_COLUMN = 0x1; @@ -1227,11 +1231,11 @@ void MshtmlVBufBackend_t::render(VBufStorage_buffer_t* buffer, int docHandle, in LOG_DEBUG(L"Error getting document using WM_HTML_GETOBJECT"); return; } -IHTMLDOMNode* pHTMLDOMNode=NULL; + IHTMLDOMNode* pHTMLDOMNode=NULL; if(oldNode!=NULL) { - IHTMLElement2* pHTMLElement2=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(oldNode))->pHTMLElement2; - nhAssert(pHTMLElement2); - pHTMLElement2->QueryInterface(IID_IHTMLDOMNode,(void**)&pHTMLDOMNode); + pHTMLDOMNode=(static_cast<MshtmlVBufStorage_controlFieldNode_t*>(oldNode))->pHTMLDOMNode; + nhAssert(pHTMLDOMNode); + pHTMLDOMNode->AddRef(); } else { IHTMLDocument3* pHTMLDocument3=NULL; if(ObjectFromLresult(res,IID_IHTMLDocument3,0,(void**)&pHTMLDocument3)!=S_OK) { diff --git a/nvdaHelper/vbufBackends/mshtml/node.cpp b/nvdaHelper/vbufBackends/mshtml/node.cpp index c7d9b5b..84f44a8 100755 --- a/nvdaHelper/vbufBackends/mshtml/node.cpp +++ b/nvdaHelper/vbufBackends/mshtml/node.cpp @@ -14,8 +14,10 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <list> #include <windows.h> +#include <objbase.h> #include <oleidl.h> #include <mshtml.h> +#include <mshtmdid.h> #include <common/log.h> #include "mshtml.h" #include "node.h" @@ -25,33 +27,66 @@ using namespace std; class CDispatchChangeSink : public IDispatch { private: ULONG refCount; - bool hasFired; + MshtmlVBufStorage_controlFieldNode_t* storageNode; + IConnectionPoint* pConnectionPoint; + DWORD dwCookie; public: - MshtmlVBufStorage_controlFieldNode_t* storageNode; - bool allowDelete; - CDispatchChangeSink(MshtmlVBufStorage_controlFieldNode_t* storageNode): - refCount(1), - hasFired(false), - allowDelete(true) { + CDispatchChangeSink(MshtmlVBufStorage_controlFieldNode_t* storageNode) : + refCount(1), dwCookie(0), pConnectionPoint(NULL) { nhAssert(storageNode); this->storageNode=storageNode; incBackendLibRefCount(); } - ~CDispatchChangeSink() { - decBackendLibRefCount(); + BOOL connect(IHTMLDOMNode* pHTMLDOMNode, REFIID iid) { + if(dwCookie) { + LOG_DEBUGWARNING(L"Already connected"); + return false; + } + IHTMLElement* pHTMLElement=NULL; + pHTMLDOMNode->QueryInterface(IID_IHTMLElement,(void**)&pHTMLElement); + if(!pHTMLElement) { + LOG_DEBUGWARNING(L"QueryInterface to IHTMLElement failed"); + return false; + } + IConnectionPointContainer* pConnectionPointContainer=NULL; + pHTMLElement->QueryInterface(IID_IConnectionPointContainer,(void**)&pConnectionPointContainer); + pHTMLElement->Release(); + if(!pConnectionPointContainer) { + LOG_DEBUGWARNING(L"QueryInterface to IConnectionPointContainer failed"); + return false; + } + IConnectionPoint* pConnectionPoint=NULL; + pConnectionPointContainer->FindConnectionPoint(iid,&pConnectionPoint); + pConnectionPointContainer->Release(); + if(!pConnectionPoint) { + return false; + } + DWORD dwCookie=0; + pConnectionPoint->Advise(this,&dwCookie); + if(!dwCookie) { + pConnectionPoint->Release(); + return false; + } + this->pConnectionPoint=pConnectionPoint; + this->dwCookie=dwCookie; + return true; } - void onChange() { - if(hasFired||allowDelete) { - return; - } - hasFired=true; - LOG_DEBUG(L"Marking storage node as invalid"); - this->storageNode->backend->invalidateSubtree(this->storageNode); - LOG_DEBUG(L"Done"); + BOOL disconnect() { + if(this->dwCookie==0) return false; + this->pConnectionPoint->Unadvise(this->dwCookie); + this->dwCookie=0; + this->pConnectionPoint->Release(); + this->pConnectionPoint=NULL; + return true; + } + + ~CDispatchChangeSink() { + this->disconnect(); + decBackendLibRefCount(); } HRESULT STDMETHODCALLTYPE IUnknown::QueryInterface(REFIID riid, void** pvpObject) { @@ -77,28 +112,18 @@ class CDispatchChangeSink : public IDispatch { if(this->refCount>0) this->refCount--; if(this->refCount==0) { - if (this->allowDelete) { - delete this; - } else { - #ifdef DEBUG - Beep(660,50); - #endif - LOG_DEBUG(L"refCount hit 0 before it should, not deleting, node info: " << this->storageNode->getDebugInfo()); - } + delete this; return 0; } return this->refCount; } HRESULT STDMETHODCALLTYPE IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { - if(dispIdMember==0) { - LOG_DEBUG(L"calling onChange"); - this->onChange(); - LOG_DEBUG(L"Done, returning S_OK"); + if(dispIdMember==DISPID_EVMETH_ONPROPERTYCHANGE||dispIdMember==DISPID_EVMETH_ONLOAD) { + this->storageNode->backend->invalidateSubtree(this->storageNode); return S_OK; } - LOG_DEBUG(L"invoke called with unknown member ID, returning E_INVALIDARG"); - return E_INVALIDARG; + return E_FAIL; } HRESULT STDMETHODCALLTYPE IDispatch::GetTypeInfoCount(UINT* count) { @@ -238,48 +263,22 @@ class CHTMLChangeSink : public IHTMLChangeSink { }; MshtmlVBufStorage_controlFieldNode_t::MshtmlVBufStorage_controlFieldNode_t(int docHandle, int ID, bool isBlock, MshtmlVBufBackend_t* backend, IHTMLDOMNode* pHTMLDOMNode,const wstring& lang): VBufStorage_controlFieldNode_t(docHandle,ID,isBlock), language(lang) { - VARIANT_BOOL varBool; nhAssert(backend); nhAssert(pHTMLDOMNode); this->backend=backend; - this->pHTMLElement2=NULL; + pHTMLDOMNode->AddRef(); + this->pHTMLDOMNode=pHTMLDOMNode; this->propChangeSink=NULL; this->loadSink=NULL; this->pHTMLChangeSink=NULL; this->HTMLChangeSinkCookey=0; - pHTMLDOMNode->QueryInterface(IID_IHTMLElement2,(void**)&(this->pHTMLElement2)); - if(!this->pHTMLElement2) { - LOG_DEBUG(L"Could not queryInterface from IHTMLDOMNode to IHTMLElement2"); - } - if(this->pHTMLElement2) { - CDispatchChangeSink* propChangeSink=new CDispatchChangeSink(this); - // It seems that IE 6 sometimes calls Release() once too many times. - // We don't want propChangeSink to be deleted until we're finished with it. - propChangeSink->allowDelete=false; - if((pHTMLElement2->attachEvent(L"onpropertychange",propChangeSink,&varBool)==S_OK)&&varBool) { - this->propChangeSink=propChangeSink; - } else { - LOG_DEBUG(L"Error attaching onPropertyChange event sink to IHTMLElement2 at "<<pHTMLElement2); - propChangeSink->allowDelete=true; - propChangeSink->Release(); - } - } BSTR nodeName=NULL; pHTMLDOMNode->get_nodeName(&nodeName); - if(nodeName!=NULL&&(_wcsicmp(nodeName,L"frame")==0||_wcsicmp(nodeName,L"iframe")==0||_wcsicmp(nodeName,L"img")==0||_wcsicmp(nodeName,L"input")==0)) { - if(this->pHTMLElement2) { - CDispatchChangeSink* loadSink=new CDispatchChangeSink(this); - // It seems that IE 6 sometimes calls Release() once too many times. - // We don't want loadSink to be deleted until we're finished with it. - loadSink->allowDelete=false; - if((pHTMLElement2->attachEvent(L"onload",loadSink,&varBool)==S_OK)&&varBool) { - this->loadSink=loadSink; - } else { - LOG_DEBUG(L"Error attaching onload event sink to IHTMLElement2 at "<<pHTMLElement2); - loadSink->allowDelete=true; - loadSink->Release(); - } - } + CDispatchChangeSink* propChangeSink=new CDispatchChangeSink(this); + if(propChangeSink->connect(pHTMLDOMNode,IID_IDispatch)) { + this->propChangeSink=propChangeSink; + } else { + propChangeSink->Release(); } if(nodeName!=NULL&&(_wcsicmp(nodeName,L"body")==0||_wcsicmp(nodeName,L"frameset")==0)) { IHTMLDOMNode2* pHTMLDOMNode2=NULL; @@ -317,23 +316,15 @@ MshtmlVBufStorage_controlFieldNode_t::MshtmlVBufStorage_controlFieldNode_t(int d MshtmlVBufStorage_controlFieldNode_t::~MshtmlVBufStorage_controlFieldNode_t() { if(this->propChangeSink) { - nhAssert(this->pHTMLElement2); - if(pHTMLElement2->detachEvent(L"onpropertychange",this->propChangeSink)!=S_OK) { - LOG_DEBUG(L"Error detaching onpropertychange event sink from IHTMLElement2"); + if(!(static_cast<CDispatchChangeSink*>(this->propChangeSink)->disconnect())) { + LOG_DEBUGWARNING(L"Failed to stop listening with HTMLElementEvents2 for node "<<this->getDebugInfo()); } - static_cast<CDispatchChangeSink*>(this->propChangeSink)->allowDelete=true; this->propChangeSink->Release(); + this->propChangeSink=NULL; } - if(this->loadSink) { - nhAssert(this->pHTMLElement2); - if(pHTMLElement2->detachEvent(L"onload",this->loadSink)!=S_OK) { - LOG_DEBUG(L"Error detaching onload event sink from IHTMLElement2"); - } - static_cast<CDispatchChangeSink*>(this->loadSink)->allowDelete=true; - this->loadSink->Release(); - } - if(this->pHTMLElement2) { - this->pHTMLElement2->Release(); + if(this->pHTMLDOMNode) { + this->pHTMLDOMNode->Release(); + this->pHTMLDOMNode=NULL; } if(this->pHTMLChangeSink) { nhAssert(this->pMarkupContainer2); diff --git a/nvdaHelper/vbufBackends/mshtml/node.h b/nvdaHelper/vbufBackends/mshtml/node.h index 7fc7ae6..ec43b80 100755 --- a/nvdaHelper/vbufBackends/mshtml/node.h +++ b/nvdaHelper/vbufBackends/mshtml/node.h @@ -24,7 +24,7 @@ class MshtmlVBufStorage_controlFieldNode_t : public VBufStorage_controlFieldNode public: MshtmlVBufBackend_t* backend; - IHTMLElement2* pHTMLElement2; + IHTMLDOMNode* pHTMLDOMNode; IDispatch* propChangeSink; IDispatch* loadSink; IMarkupContainer2* pMarkupContainer2; https://bitbucket.org/nvdaaddonteam/nvda/commits/81cb363df6ee/ Changeset: 81cb363df6ee Branch: None User: jteh Date: 2013-09-02 03:51:46 Summary: In Poedit, messages that are untranslated or fuzzy are again indicated with an asterisk and a beep. This broke due to the removal of the empty control field in display model output. Also, optimise slightly by using UNIT_CHARACTER instead of UINT_LINE, since we're only looking at the first character anyway. Re #3485. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index c6e0c69..78c4d34 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -1,6 +1,6 @@ #appModules/poedit.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2012 Mesar Hameed <mhameed@xxxxxxxxxxxxx> +#Copyright (C) 2012-2013 Mesar Hameed, NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -130,10 +130,10 @@ class PoeditListItem(sysListView32.ListItem): def _get_isBold(self): info=displayModel.DisplayModelTextInfo(self,position=textInfos.POSITION_FIRST) - info.expand(textInfos.UNIT_LINE) + info.expand(textInfos.UNIT_CHARACTER) fields=info.getTextWithFields() try: - return fields[1].field['bold'] + return fields[0].field['bold'] except: return False https://bitbucket.org/nvdaaddonteam/nvda/commits/546fa302d905/ Changeset: 546fa302d905 Branch: None User: jteh Date: 2013-09-02 04:12:38 Summary: Update messages and translator comments to reflect the rename of Automatic comments to Notes for translators in Poedit. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index 78c4d34..4c84a64 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -73,15 +73,14 @@ class AppModule(appModuleHandler.AppModule): ui.message(obj.name + " " + obj.value) except: # Translators: this message is reported when there are no - # comments to be presented to the user in the automatic - # comments window in poedit. - ui.message(_("No automatic comments.")) + # notes for translators to be presented to the user in Poedit. + ui.message(_("No notes for translators.")) else: # Translators: this message is reported when NVDA is unable to find - # the 'automatic comments' window in poedit. - ui.message(_("Could not find automatic comments window.")) + # the 'Notes for translators' window in poedit. + ui.message(_("Could not find Notes for translators window.")) # Translators: The description of an NVDA command for Poedit. - script_reportAutoCommentsWindow.__doc__ = _("Reports any comments in the automatic comments window") + script_reportAutoCommentsWindow.__doc__ = _("Reports any notes for translators") def script_reportCommentsWindow(self,gesture): obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 1, 0]) https://bitbucket.org/nvdaaddonteam/nvda/commits/59d71f580a42/ Changeset: 59d71f580a42 Branch: None User: jteh Date: 2013-09-02 04:38:43 Summary: poedit: Fix reporting of translator added comments broken due to changes in Poedit. Re #3485. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index 4c84a64..94e947c 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -15,6 +15,9 @@ import textInfos import tones import ui from NVDAObjects.IAccessible import sysListView32 +import windowUtils +import NVDAObjects.IAccessible +import winUser def getPath(obj, ancestor): @@ -83,23 +86,22 @@ class AppModule(appModuleHandler.AppModule): script_reportAutoCommentsWindow.__doc__ = _("Reports any notes for translators") def script_reportCommentsWindow(self,gesture): - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 1, 0]) - # if it isnt in the normal location, try to find it in the - # location of the automatic window. - if not obj: - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 0, 0]) - if obj and obj.windowControlID == 105: - try: - ui.message(obj.name + " " + obj.value) - except: - # Translators: this message is reported when there are no - # comments to be presented to the user in the translator - # comments window in poedit. - ui.message(_("No comment.")) - else: + try: + obj = NVDAObjects.IAccessible.getNVDAObjectFromEvent( + windowUtils.findDescendantWindow(api.getForegroundObject().windowHandle, visible=True, controlID=104), + winUser.OBJID_CLIENT, 0) + except LookupError: # Translators: this message is reported when NVDA is unable to find # the 'comments' window in poedit. ui.message(_("Could not find comment window.")) + return None + try: + ui.message(obj.name + " " + obj.value) + except: + # Translators: this message is reported when there are no + # comments to be presented to the user in the translator + # comments window in poedit. + ui.message(_("No comment.")) # Translators: The description of an NVDA command for Poedit. script_reportAutoCommentsWindow.__doc__ = _("Reports any comments in the comments window") https://bitbucket.org/nvdaaddonteam/nvda/commits/dd9a00180f5c/ Changeset: dd9a00180f5c Branch: None User: jteh Date: 2013-09-02 07:00:38 Summary: poedit: Fix the labels for the editable text fields in Poedit 1.5.7. The control IDs have changed and some of them are duplicated. The label is always just above the field on the screen, so rather than using control IDs, grab the label object and use that to label the field. Re #3485. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index 94e947c..cc0bcd3 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -113,19 +113,17 @@ class AppModule(appModuleHandler.AppModule): def chooseNVDAObjectOverlayClasses(self, obj, clsList): if "SysListView32" in obj.windowClassName and obj.role==controlTypes.ROLE_LISTITEM: clsList.insert(0,PoeditListItem) - if obj.role == controlTypes.ROLE_EDITABLETEXT: - if obj.windowControlID == 102: - # Translators: Automatic comments is the name of the poedit - # window that displays comments extracted from code. - obj.name = _("Automatic comments:") - if obj.windowControlID == 104: - # Translators: this is the label for the edit area in poedit - # that contains a translation. - obj.name = _("Translation:") - if obj.windowControlID == 105: - # Translators: 'comments:' is the name of the poedit window - # that displays comments entered by the translator. - obj.name = _("Comments:") + + def event_NVDAObject_init(self, obj): + if obj.role == controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_MULTILINE in obj.states and obj.isInForeground: + # Oleacc often gets the name wrong. + # The label object is positioned just above the field on the screen. + l, t, w, h = obj.location + try: + obj.name = NVDAObjects.NVDAObject.objectFromPoint(l + 10, t - 10).name + except AttributeError: + pass + return class PoeditListItem(sysListView32.ListItem): https://bitbucket.org/nvdaaddonteam/nvda/commits/312b69972c5f/ Changeset: 312b69972c5f Branch: None User: jteh Date: 2013-09-02 07:18:14 Summary: poedit: Fix reporting of notes for translators for Poedit 1.5.7. Re #3485. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index cc0bcd3..3d21ed1 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -68,10 +68,15 @@ def fetchObject(obj, path): class AppModule(appModuleHandler.AppModule): def script_reportAutoCommentsWindow(self,gesture): - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 0, 0]) - # check the controlid, because in certain situations - # autoComments and comment windows change places. - if obj and obj.windowControlID == 102: + obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 1]) + if obj and obj.windowControlID != 101: + try: + obj = obj.next.firstChild + except AttributeError: + obj = None + elif obj: + obj = obj.firstChild + if obj: try: ui.message(obj.name + " " + obj.value) except: https://bitbucket.org/nvdaaddonteam/nvda/commits/c3b74e24281c/ Changeset: c3b74e24281c Branch: None User: jteh Date: 2013-09-02 07:20:10 Summary: poedit: Fix script docstrings. Affected #: 1 file diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index 3d21ed1..995618d 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -108,7 +108,7 @@ class AppModule(appModuleHandler.AppModule): # comments window in poedit. ui.message(_("No comment.")) # Translators: The description of an NVDA command for Poedit. - script_reportAutoCommentsWindow.__doc__ = _("Reports any comments in the comments window") + script_reportCommentsWindow.__doc__ = _("Reports any comments in the comments window") __gestures = { "kb:control+shift+c": "reportCommentsWindow", https://bitbucket.org/nvdaaddonteam/nvda/commits/87fab437eeb4/ Changeset: 87fab437eeb4 Branch: None User: jteh Date: 2013-09-02 07:20:52 Summary: Update User Guide. Affected #: 1 file diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 3e1b68d..1091803 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -554,7 +554,7 @@ Note: The report remaining time shortcut works only with the default formatting %kc:beginInclude || Name | Key | Description | | Report Comments Window | control+shift+c | Reports any comments in the comments window. | -| Report automatic comments window | control+shift+a | Reports any comments in the automatic comments window. | +| Report notes for translators | control+shift+a | Reports any notes for translators. | %kc:endInclude + Configuring NVDA + https://bitbucket.org/nvdaaddonteam/nvda/commits/5c5cc2ed89b8/ Changeset: 5c5cc2ed89b8 Branch: None User: mdcurran Date: 2013-09-02 07:50:44 Summary: Merge branch 'master' into t1532 Affected #: 38 files diff --git a/contributors.txt b/contributors.txt index ed1df41..d18fdd4 100644 --- a/contributors.txt +++ b/contributors.txt @@ -133,3 +133,4 @@ Rui Fontes (Tiflotecnia, lda.) Kevin Scannell Hamid Rezaey Bue Vester-Andersen +Yogesh Kumar diff --git a/include/minhook b/include/minhook index ff0888d..e21b54a 160000 --- a/include/minhook +++ b/include/minhook @@ -1 +1 @@ -Subproject commit ff0888de0d34399e69155481f70b9e11b771eebe +Subproject commit e21b54a88190ca477ed73fb7ab24203dfaef28a0 diff --git a/nvdaHelper/minHook/dllmain.cpp b/nvdaHelper/minHook/dllmain.cpp new file mode 100644 index 0000000..25d6823 --- /dev/null +++ b/nvdaHelper/minHook/dllmain.cpp @@ -0,0 +1,28 @@ +/* +This file is a part of the NVDA project. +URL: http://www.nvda-project.org/ +Copyright 2006-2010 NVDA contributers. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2.0, as published by + the Free Software Foundation. + 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. +This license can be found at: +http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +*/ + +#include <windows.h> +#include <minhook.h> + +BOOL WINAPI DllMain(HINSTANCE hModule,DWORD reason,LPVOID lpReserved) { + //Process exiting, we must clean up any pending hooks + if(reason==DLL_PROCESS_DETACH&&lpReserved) { + if(MH_DisableAllHooks()!=MH_ERROR_NOT_INITIALIZED) { + //Give enough time for all hook functions to complete. + Sleep(250); + MH_Uninitialize(); + } + } + return 1; +} diff --git a/nvdaHelper/minHook/minHook.def b/nvdaHelper/minHook/minHook.def deleted file mode 100644 index 75e1b07..0000000 --- a/nvdaHelper/minHook/minHook.def +++ /dev/null @@ -1,8 +0,0 @@ -EXPORTS - MH_Initialize - MH_Uninitialize - MH_CreateHook - MH_EnableHook - MH_DisableHook - MH_EnableAllHooks - MH_DisableAllHooks diff --git a/nvdaHelper/minHook/newHook.cpp b/nvdaHelper/minHook/newHook.cpp deleted file mode 100644 index 06778b8..0000000 --- a/nvdaHelper/minHook/newHook.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://www.nvda-project.org/ -Copyright 2006-2010 NVDA contributers. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - 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. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include <windows.h> -#include "hook.cpp" - -using namespace MinHook; - -//Based on MH_EnableHook from minHook -MH_STATUS _doAllHooks(bool enable) { - CriticalSection::ScopedLock lock(gCS); - if (!gIsInitialized) { - return MH_ERROR_NOT_INITIALIZED; - } - std::vector<uintptr_t> oldIPs; - std::vector<uintptr_t> newIPs; - for(std::vector<HOOK_ENTRY>::iterator hooksIter=gHooks.begin();hooksIter!=gHooks.end();++hooksIter) { - oldIPs.insert(oldIPs.end(),hooksIter->oldIPs.begin(),hooksIter->oldIPs.end()); - newIPs.insert(newIPs.end(),hooksIter->newIPs.begin(),hooksIter->newIPs.end()); - } - { - ScopedThreadExclusive tex(oldIPs, newIPs); - for(std::vector<HOOK_ENTRY>::iterator hooksIter=gHooks.begin();hooksIter!=gHooks.end();++hooksIter) { - HOOK_ENTRY*pHook=&(*hooksIter); - if (pHook->isEnabled==enable) continue; - DWORD oldProtect; - if (!VirtualProtect(pHook->pTarget, sizeof(JMP_REL), PAGE_EXECUTE_READWRITE, &oldProtect)) { - return MH_ERROR_MEMORY_PROTECT; - } - if(enable) { -#if defined _M_X64 - WriteRelativeJump(pHook->pTarget, pHook->pRelay); -#elif defined _M_IX86 - WriteRelativeJump(pHook->pTarget, pHook->pDetour); -#endif - } else { - memcpy(pHook->pTarget, pHook->pBackup, sizeof(JMP_REL)); - } - VirtualProtect(pHook->pTarget, sizeof(JMP_REL), oldProtect, &oldProtect); - pHook->isEnabled = enable; - } - } - return MH_OK; -} - -MH_STATUS MH_EnableAllHooks() { - return _doAllHooks(true); -} - -MH_STATUS MH_DisableAllHooks() { - return _doAllHooks(false); -} - -BOOL WINAPI DllMain(HINSTANCE hModule,DWORD reason,LPVOID lpReserved) { - //Process exiting, we must clean up any pending hooks - if(reason==DLL_PROCESS_DETACH&&lpReserved) { - if(gIsInitialized) { - MH_DisableAllHooks(); - //Give enough time for all hook functions to complete. - Sleep(250); - MH_Uninitialize(); - } - } - return 1; -} diff --git a/nvdaHelper/minHook/newMinHook.h b/nvdaHelper/minHook/newMinHook.h deleted file mode 100644 index 831d612..0000000 --- a/nvdaHelper/minHook/newMinHook.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://www.nvda-project.org/ -Copyright 2006-2010 NVDA contributers. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - 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. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#ifndef NVDAHELPER_NEWMINHOOK_H -#define NVDAHELPER_NEWMINHOOK_H - -#include <minhook/libMinHook/minHook.h> - -//Some new batch calls -MH_STATUS MH_EnableAllHooks(); -MH_STATUS MH_DisableAllHooks(); - -//function pointer typedefs for all minHook functions for use with getProcAddress -typedef MH_STATUS(WINAPI *MH_Initialize_funcType)(); -typedef MH_STATUS(WINAPI *MH_Uninitialize_funcType)(); -typedef MH_STATUS(WINAPI *MH_CreateHook_funcType)(void*,void*,void**); -typedef MH_STATUS(WINAPI *MH_EnableHook_funcType)(void*); -typedef MH_STATUS(WINAPI *MH_DisableHook_funcType)(void*); -typedef MH_STATUS(*MH_EnableAllHooks_funcType)(); -typedef MH_STATUS(*MH_DisableAllHooks_funcType)(); - -#endif diff --git a/nvdaHelper/minHook/sconscript b/nvdaHelper/minHook/sconscript index c076a68..497a242 100644 --- a/nvdaHelper/minHook/sconscript +++ b/nvdaHelper/minHook/sconscript @@ -2,19 +2,27 @@ Import([ 'env', ]) + +minhookPath=Dir('#include/minhook') +env=env.Clone(CPPPATH=minhookPath.Dir('include')) + HDESourceFile='HDE64/src/HDE64.c' if env['TARGET_ARCH']=='x86_64' else 'HDE32/HDE32.c' sourceFiles=[ HDESourceFile, 'buffer.cpp', 'export.cpp', + 'hook.cpp', 'thread.cpp', 'trampoline.cpp', ] -objFiles=[env.Object('_minHook_%s.obj'%x.replace('/','_'),'#/include/minhook/libMinHook/src/%s'%x) for x in sourceFiles] -objFiles.append(env.Object('newHook.cpp',CPPPATH=['#include/minhook/libMinHook/src','${CPPPATH}'])) +objFiles=[env.Object('_minHook_%s.obj'%x.replace('/','_'),minhookPath.File('src/%s'%x)) for x in sourceFiles] +objFiles.append('dllmain.cpp') -minHookLib=env.SharedLibrary('minHook',objFiles+['minHook.def']) +minHookLib=env.SharedLibrary( + target='minHook', + source=objFiles+[minhookPath.File('dll_resources/minHook.def')], +) Return('minHookLib') diff --git a/nvdaHelper/remote/apiHook.cpp b/nvdaHelper/remote/apiHook.cpp index 3a5955b..0a3430b 100644 --- a/nvdaHelper/remote/apiHook.cpp +++ b/nvdaHelper/remote/apiHook.cpp @@ -17,7 +17,7 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <delayimp.h> -#include <minHook/newMinHook.h> +#include <minhook/include/minhook.h> #include "nvdaControllerInternal.h" #include <common/log.h> #include "dllmain.h" @@ -33,6 +33,15 @@ functionSet_t g_hookedFunctions; HMODULE minhookLibHandle=NULL; bool error_setNHFP=false; +//function pointer typedefs for all minHook functions for use with getProcAddress +typedef MH_STATUS(WINAPI *MH_Initialize_funcType)(); +typedef MH_STATUS(WINAPI *MH_Uninitialize_funcType)(); +typedef MH_STATUS(WINAPI *MH_CreateHook_funcType)(void*,void*,void**); +typedef MH_STATUS(WINAPI *MH_EnableHook_funcType)(void*); +typedef MH_STATUS(WINAPI *MH_DisableHook_funcType)(void*); +typedef MH_STATUS(*MH_EnableAllHooks_funcType)(); +typedef MH_STATUS(*MH_DisableAllHooks_funcType)(); + #define defMHFP(funcName) funcName##_funcType funcName##_fp=NULL #define setMHFP(funcName) {\ diff --git a/nvdaHelper/remote/winword.cpp b/nvdaHelper/remote/winword.cpp index 9c2aaf5..f4ddbba 100644 --- a/nvdaHelper/remote/winword.cpp +++ b/nvdaHelper/remote/winword.cpp @@ -35,6 +35,7 @@ using namespace std; #define wdDISPID_SELECTION_RANGE 400 #define wdDISPID_SELECTION_SETRANGE 100 #define wdDISPID_SELECTION_STARTISACTIVE 404 +#define wdDISPID_RANGE_INRANGE 126 #define wdDISPID_RANGE_STORYTYPE 7 #define wdDISPID_RANGE_MOVEEND 111 #define wdDISPID_RANGE_COLLAPSE 101 @@ -47,6 +48,19 @@ using namespace std; #define wdDISPID_RANGE_INFORMATION 313 #define wdDISPID_RANGE_STYLE 151 #define wdDISPID_RANGE_LANGUAGEID 153 +#define wdDISPID_RANGE_DUPLICATE 6 +#define wdDISPID_RANGE_FORMFIELDS 65 +#define wdDISPID_RANGE_CONTENTCONTROLS 424 +#define wdDISPID_FORMFIELDS_ITEM 0 +#define wdDISPID_FORMFIELD_RANGE 17 +#define wdDISPID_FORMFIELD_TYPE 0 +#define wdDISPID_FORMFIELD_RESULT 10 +#define wdDISPID_FORMFIELD_STATUSTEXT 8 +#define wdDISPID_CONTENTCONTROLS_ITEM 0 +#define wdDISPID_CONTENTCONTROL_RANGE 1 +#define wdDISPID_CONTENTCONTROL_TYPE 5 +#define wdDISPID_CONTENTCONTROL_CHECKED 28 +#define wdDISPID_CONTENTCONTROL_TITLE 12 #define wdDISPID_STYLE_NAMELOCAL 0 #define wdDISPID_RANGE_SPELLINGERRORS 316 #define wdDISPID_SPELLINGERRORS_COUNT 1 @@ -102,6 +116,7 @@ using namespace std; #define wdCharacter 1 #define wdWord 2 +#define wdParagraph 4 #define wdLine 5 #define wdCharacterFormatting 13 @@ -192,6 +207,70 @@ void winword_expandToLine_helper(HWND hwnd, winword_expandToLine_args* args) { _com_dispatch_raw_propput(pDispatchApplication,wdDISPID_APPLICATION_SCREENUPDATING,VT_BOOL,true); } +BOOL generateFormFieldXML(IDispatch* pDispatchRange, wostringstream& XMLStream, int& chunkEnd) { + IDispatchPtr pDispatchRange2=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_DUPLICATE,VT_DISPATCH,&pDispatchRange2)!=S_OK||!pDispatchRange2) { + return false; + } + _com_dispatch_raw_method(pDispatchRange2,wdDISPID_RANGE_EXPAND,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdParagraph,1); + BOOL foundFormField=false; + IDispatchPtr pDispatchFormFields=NULL; + _com_dispatch_raw_propget(pDispatchRange2,wdDISPID_RANGE_FORMFIELDS,VT_DISPATCH,&pDispatchFormFields); + if(pDispatchFormFields) for(int count=1;!foundFormField&&count<100;++count) { + IDispatchPtr pDispatchFormField=NULL; + if(_com_dispatch_raw_method(pDispatchFormFields,wdDISPID_FORMFIELDS_ITEM,DISPATCH_METHOD,VT_DISPATCH,&pDispatchFormField,L"\x0003",count)!=S_OK||!pDispatchFormField) { + break; + } + IDispatchPtr pDispatchFormFieldRange=NULL; + if(_com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_RANGE,VT_DISPATCH,&pDispatchFormFieldRange)!=S_OK||!pDispatchFormFieldRange) { + break; + } + if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_INRANGE,DISPATCH_METHOD,VT_BOOL,&foundFormField,L"\x0009",pDispatchFormFieldRange)!=S_OK||!foundFormField) { + continue; + } + long fieldType=-1; + _com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_TYPE,VT_I4,&fieldType); + BSTR fieldResult=NULL; + _com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_RESULT,VT_BSTR,&fieldResult); + BSTR fieldStatusText=NULL; + _com_dispatch_raw_propget(pDispatchFormField,wdDISPID_FORMFIELD_STATUSTEXT,VT_BSTR,&fieldStatusText); + XMLStream<<L"<control wdFieldType=\""<<fieldType<<L"\" wdFieldResult=\""<<(fieldResult?fieldResult:L"")<<L"\" wdFieldStatusText=\""<<(fieldStatusText?fieldStatusText:L"")<<L"\">"; + if(fieldResult) SysFreeString(fieldResult); + if(fieldStatusText) SysFreeString(fieldStatusText); + _com_dispatch_raw_propget(pDispatchFormFieldRange,wdDISPID_RANGE_END,VT_I4,&chunkEnd); + _com_dispatch_raw_propput(pDispatchRange,wdDISPID_RANGE_END,VT_I4,chunkEnd); + break; + } + if(foundFormField) return true; + IDispatchPtr pDispatchContentControls=NULL; + _com_dispatch_raw_propget(pDispatchRange2,wdDISPID_RANGE_CONTENTCONTROLS,VT_DISPATCH,&pDispatchContentControls); + if(pDispatchContentControls)for(int count=1;!foundFormField&&count<100;++count) { + IDispatchPtr pDispatchContentControl=NULL; + if(_com_dispatch_raw_method(pDispatchContentControls,wdDISPID_CONTENTCONTROLS_ITEM,DISPATCH_METHOD,VT_DISPATCH,&pDispatchContentControl,L"\x0003",count)!=S_OK||!pDispatchContentControl) { + break; + } + IDispatchPtr pDispatchContentControlRange=NULL; + if(_com_dispatch_raw_propget(pDispatchContentControl,wdDISPID_CONTENTCONTROL_RANGE,VT_DISPATCH,&pDispatchContentControlRange)!=S_OK||!pDispatchContentControlRange) { + break; + } + if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_INRANGE,DISPATCH_METHOD,VT_BOOL,&foundFormField,L"\x0009",pDispatchContentControlRange)!=S_OK||!foundFormField) { + continue; + } + long fieldType=-1; + _com_dispatch_raw_propget(pDispatchContentControl,wdDISPID_CONTENTCONTROL_TYPE,VT_I4,&fieldType); + BOOL fieldChecked=false; + _com_dispatch_raw_propget(pDispatchContentControl,wdDISPID_CONTENTCONTROL_CHECKED,VT_BOOL,&fieldChecked); + BSTR fieldTitle=NULL; + _com_dispatch_raw_propget(pDispatchContentControl,wdDISPID_CONTENTCONTROL_TITLE,VT_BSTR,&fieldTitle); + XMLStream<<L"<control wdContentControlType=\""<<fieldType<<L"\" wdContentControlChecked=\""<<fieldChecked<<L"\" wdContentControlTitle=\""<<(fieldTitle?fieldTitle:L"")<<L"\">"; + if(fieldTitle) SysFreeString(fieldTitle); + _com_dispatch_raw_propget(pDispatchContentControlRange,wdDISPID_RANGE_END,VT_I4,&chunkEnd); + _com_dispatch_raw_propput(pDispatchRange,wdDISPID_RANGE_END,VT_I4,chunkEnd); + break; + } + return foundFormField; +} + int generateHeadingXML(IDispatch* pDispatchRange, wostringstream& XMLStream) { IDispatchPtr pDispatchParagraphs=NULL; IDispatchPtr pDispatchParagraph=NULL; @@ -544,15 +623,27 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) int chunkEndOffset=chunkStartOffset; int unitsMoved=0; BSTR text=NULL; + if(initialFormatConfig&formatConfig_reportTables) { + neededClosingControlTagCount+=generateTableXML(pDispatchRange,args->startOffset,args->endOffset,XMLStream); + } + if(initialFormatConfig&formatConfig_reportHeadings) { + neededClosingControlTagCount+=generateHeadingXML(pDispatchRange,XMLStream); + } + generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,initialFormatConfig,initialFormatAttribsStream); + bool firstLoop=true; //Walk the range from the given start to end by characterFormatting or word units //And grab any text and formatting and generate appropriate xml - bool firstLoop=true; do { - //Move the end by word - if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdWord,1)!=S_OK||unitsMoved<=0) { - break; + //generated form field xml if in a form field + //Also automatically extends the range and chunkEndOffset to the end of the field + BOOL isFormField=generateFormFieldXML(pDispatchRange,XMLStream,chunkEndOffset); + if(!isFormField) { + //Move the end by word + if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdWord,1)!=S_OK||unitsMoved<=0) { + break; + } + _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset); } - _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset); //Make sure that the end is not past the requested end after the move if(chunkEndOffset>(args->endOffset)) { _com_dispatch_raw_propput(pDispatchRange,wdDISPID_RANGE_END,VT_I4,args->endOffset); @@ -564,34 +655,29 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) break; } _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_TEXT,VT_BSTR,&text); + if(!text) SysAllocString(L""); if(text) { - //Force a new chunk before and after control+b (note characters) int noteCharOffset=-1; - for(int i=0;text[i]!=L'\0';++i) { - if(text[i]==L'\x0002') { - noteCharOffset=i; - if(i==0) text[i]=L' '; - break; - } - } - bool isNoteChar=(noteCharOffset==0); - if(noteCharOffset==0) noteCharOffset=1; - if(noteCharOffset>0) { - text[noteCharOffset]=L'\0'; - _com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_COLLAPSE,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdCollapseStart); - if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdCharacter,noteCharOffset)!=S_OK||unitsMoved<=0) { - break; - } - _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset); - } - if(firstLoop) { - if(initialFormatConfig&formatConfig_reportTables) { - neededClosingControlTagCount+=generateTableXML(pDispatchRange,args->startOffset,args->endOffset,XMLStream); + bool isNoteChar=false; + if(!isFormField) { + //Force a new chunk before and after control+b (note characters) + for(int i=0;text[i]!=L'\0';++i) { + if(text[i]==L'\x0002') { + noteCharOffset=i; + if(i==0) text[i]=L' '; + break; + } } - if(initialFormatConfig&formatConfig_reportHeadings) { - neededClosingControlTagCount+=generateHeadingXML(pDispatchRange,XMLStream); + isNoteChar=(noteCharOffset==0); + if(noteCharOffset==0) noteCharOffset=1; + if(noteCharOffset>0) { + text[noteCharOffset]=L'\0'; + _com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_COLLAPSE,DISPATCH_METHOD,VT_EMPTY,NULL,L"\x0003",wdCollapseStart); + if(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_MOVEEND,DISPATCH_METHOD,VT_I4,&unitsMoved,L"\x0003\x0003",wdCharacter,noteCharOffset)!=S_OK||unitsMoved<=0) { + break; + } + _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset); } - generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,initialFormatConfig,initialFormatAttribsStream); } if(isNoteChar) { isNoteChar=generateFootnoteEndnoteXML(pDispatchRange,XMLStream,true); @@ -625,6 +711,7 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) SysFreeString(text); text=NULL; XMLStream<<L"</text>"; + if(isFormField) XMLStream<<L"</control>"; if(isNoteChar) XMLStream<<L"</control>"; if(inlineShapesCount>0) XMLStream<<L"</control>"; } diff --git a/nvdaHelper/sconscript b/nvdaHelper/sconscript index 168b499..585d72d 100644 --- a/nvdaHelper/sconscript +++ b/nvdaHelper/sconscript @@ -32,6 +32,6 @@ archClientInstallDirs={ #Build nvdaHelper for needed architectures for arch in env['targetArchitectures']: - archEnv=env.Clone(TARGET_ARCH=arch,tools=['windowsSdk','midl','msrpc','boost','default']) + archEnv=env.Clone(TARGET_ARCH=arch,tools=['windowsSdk','midl','msrpc','default']) archEnv.SConscript('archBuild_sconscript',exports={'env':archEnv,'clientInstallDir':archClientInstallDirs[arch],'libInstallDir':archLibInstallDirs[arch]},variant_dir='build/%s'%arch) diff --git a/readme.txt b/readme.txt index 236cd17..e5ea569 100644 --- a/readme.txt +++ b/readme.txt @@ -42,12 +42,8 @@ General dependencies: * Copy the txt2tags Python script to the global Python site-packages directory, naming it txt2tags.py. * Microsoft Windows SDK, version 7.0: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=c17ba869-9671-4330-a63e-1fd44e0e2505&displaylang=en * You need to install both the 32 bit and 64 bit libraries and tools. - * MinHook, version 1.1.0: http://www.codeproject.com/KB/winsdk/LibMinHook.aspx + * MinHook, rev e21b54a: http://www.codeproject.com/KB/winsdk/LibMinHook.aspx * This is included as a git submodule - * Boost C++ Libraries, version 1.47: - * You can download the latest Windows installer from http://www.boostpro.com/download - * On the components page of the installer, make sure to install at least all of the defaults (whatever is already checked). - * NVDA only uses the Boost headers; none of the pre-compiled libraries are necessary. * SCons, version 2.2.0: http://www.scons.org/ * As the scons command (scons.bat) is installed in to the scripts directory inside the directory where you installed Python, it is necessary to add the scripts directory to your path variable so that you can run scons from anywhere. The rest of this readme assumes that scons can be run in this way. diff --git a/site_scons/site_tools/boost.py b/site_scons/site_tools/boost.py deleted file mode 100644 index e9cffdb..0000000 --- a/site_scons/site_tools/boost.py +++ /dev/null @@ -1,50 +0,0 @@ -### -#This file is a part of the NVDA project. -#URL: http://www.nvda-project.org/ -#Copyright 2006-2010 NVDA contributers. -#This program is free software: you can redistribute it and/or modify -#it under the terms of the GNU General Public License version 2.0, as published by -#the Free Software Foundation. -#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. -#This license can be found at: -#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -### - -import _winreg - -_boostRoot=None - -def findBoostRoot(): - global _boostRoot - if _boostRoot is None: - try: - k=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,r'Software\boostpro.com') - except: - print "boost tool: can't find bostpro.com registry key" - return None - kLen=_winreg.QueryInfoKey(k)[0] - if kLen<1: - print "boost tool: no subkeys in boostpro.com registry key" - return None - subkeyString=_winreg.EnumKey(k,kLen-1) - try: - k=_winreg.OpenKey(k,subkeyString) - except: - print "boost tool: failed to open %s subkey of boostpro.com registry key"%subkeyString - return None - try: - boostRoot=_winreg.QueryValueEx(k,"InstallRoot") - except: - print "no InstallRoot value in %s subkey of boostpro.com registry key"%subkeyString - return None - _boostRoot=boostRoot - return _boostRoot - -def exists(): - return True if findBoostRoot() is not None else False - -def generate(env): - boostRoot=findBoostRoot() - env.Append(CPPPATH=boostRoot) diff --git a/source/IAccessibleHandler.py b/source/IAccessibleHandler.py index 572a564..94db85d 100644 --- a/source/IAccessibleHandler.py +++ b/source/IAccessibleHandler.py @@ -302,6 +302,7 @@ IAccessible2StatesToNVDAStates={ IA2_STATE_MULTI_LINE:controlTypes.STATE_MULTILINE, IA2_STATE_ICONIFIED:controlTypes.STATE_ICONIFIED, IA2_STATE_EDITABLE:controlTypes.STATE_EDITABLE, + IA2_STATE_PINNED:controlTypes.STATE_PINNED, } #A list to store handles received from setWinEventHook, for use with unHookWinEvent diff --git a/source/NVDAObjects/window/winword.py b/source/NVDAObjects/window/winword.py index d43cdbf..38ee650 100755 --- a/source/NVDAObjects/window/winword.py +++ b/source/NVDAObjects/window/winword.py @@ -10,6 +10,7 @@ import comtypes.client import comtypes.automation import operator import locale +import braille import languageHandler import ui import NVDAHelper @@ -81,6 +82,19 @@ wdPrimaryFooterStory=9 wdPrimaryHeaderStory=7 wdTextFrameStory=5 +wdFieldFormTextInput=70 +wdFieldFormCheckBox=71 +wdFieldFormDropDown=83 +wdContentControlRichText=0 +wdContentControlText=1 +wdContentControlPicture=2 +wdContentControlComboBox=3 +wdContentControlDropdownList=4 +wdContentControlBuildingBlockGallery=5 +wdContentControlDate=6 +wdContentControlGroup=7 +wdContentControlCheckBox=8 + storyTypeLocalizedLabels={ wdCommentsStory:_("Comments"), wdEndnotesStory:_("Endnotes"), @@ -94,6 +108,23 @@ storyTypeLocalizedLabels={ wdTextFrameStory:_("Text frame"), } +wdFieldTypesToNVDARoles={ + wdFieldFormTextInput:controlTypes.ROLE_EDITABLETEXT, + wdFieldFormCheckBox:controlTypes.ROLE_CHECKBOX, + wdFieldFormDropDown:controlTypes.ROLE_COMBOBOX, +} + +wdContentControlTypesToNVDARoles={ + wdContentControlRichText:controlTypes.ROLE_EDITABLETEXT, + wdContentControlText:controlTypes.ROLE_EDITABLETEXT, + wdContentControlPicture:controlTypes.ROLE_GRAPHIC, + wdContentControlComboBox:controlTypes.ROLE_COMBOBOX, + wdContentControlDropdownList:controlTypes.ROLE_COMBOBOX, + wdContentControlDate:controlTypes.ROLE_EDITABLETEXT, + wdContentControlGroup:controlTypes.ROLE_GROUPING, + wdContentControlCheckBox:controlTypes.ROLE_CHECKBOX, +} + winwordWindowIid=GUID('{00020962-0000-0000-C000-000000000046}') wm_winword_expandToLine=ctypes.windll.user32.RegisterWindowMessageW(u"wm_winword_expandToLine") @@ -136,7 +167,7 @@ class WordDocumentTextInfo(textInfos.TextInfo): lineStart=ctypes.c_int() lineEnd=ctypes.c_int() res=NVDAHelper.localLib.nvdaInProcUtils_winword_expandToLine(self.obj.appModule.helperLocalBindingHandle,self.obj.windowHandle,self._rangeObj.start,ctypes.byref(lineStart),ctypes.byref(lineEnd)) - if res!=0 or (lineStart.value==lineEnd.value==-1): + if res!=0 or lineStart.value==lineEnd.value or lineStart.value==-1 or lineEnd.value==-1: log.debugWarning("winword_expandToLine failed") self._rangeObj.expand(wdParagraph) return @@ -179,8 +210,6 @@ class WordDocumentTextInfo(textInfos.TextInfo): formatConfig['autoLanguageSwitching']=config.conf['speech'].get('autoLanguageSwitching',False) startOffset=self._rangeObj.start endOffset=self._rangeObj.end - if startOffset==endOffset: - return [] text=BSTR() formatConfigFlags=sum(y for x,y in formatConfigFlagsMap.iteritems() if formatConfig.get(x,False)) res=NVDAHelper.localLib.nvdaInProcUtils_winword_getTextInRange(self.obj.appModule.helperLocalBindingHandle,self.obj.windowHandle,startOffset,endOffset,formatConfigFlags,ctypes.byref(text)) @@ -226,8 +255,30 @@ class WordDocumentTextInfo(textInfos.TextInfo): elif role=="object": role=controlTypes.ROLE_EMBEDDEDOBJECT else: - role=controlTypes.ROLE_UNKNOWN - field['role']=role + fieldType=int(field.pop('wdFieldType',-1)) + if fieldType!=-1: + role=wdFieldTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN) + if fieldType==wdFieldFormCheckBox and int(field.get('wdFieldResult','0'))>0: + field['states']=set([controlTypes.STATE_CHECKED]) + elif fieldType==wdFieldFormDropDown: + field['value']=field.get('wdFieldResult',None) + fieldStatusText=field.pop('wdFieldStatusText',None) + if fieldStatusText: + field['name']=fieldStatusText + field['alwaysReportName']=True + else: + fieldType=int(field.get('wdContentControlType',-1)) + if fieldType!=-1: + role=wdContentControlTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN) + if role==controlTypes.ROLE_CHECKBOX: + fieldChecked=bool(int(field.get('wdContentControlChecked','0'))) + if fieldChecked: + field['states']=set([controlTypes.STATE_CHECKED]) + fieldTitle=field.get('wdContentControlTitle',None) + if fieldTitle: + field['name']=fieldTitle + field['alwaysReportName']=True + if role is not None: field['role']=role storyType=int(field.pop('wdStoryType',0)) if storyType: name=storyTypeLocalizedLabels.get(storyType,None) @@ -413,10 +464,10 @@ class WordDocument(EditableTextWithoutAutoSelectDetection, Window): def script_tab(self,gesture): gesture.send() - info=self.makeTextInfo(textInfos.POSITION_CARET) - if info._rangeObj.tables.count>0: - info.expand(textInfos.UNIT_LINE) - speech.speakTextInfo(info,reason=controlTypes.REASON_CARET) + info=self.makeTextInfo(textInfos.POSITION_SELECTION) + if not info.isCollapsed or info._rangeObj.tables.count>0: + speech.speakTextInfo(info,reason=controlTypes.REASON_FOCUS) + braille.handler.handleCaretMove(info) def _moveInTable(self,row=True,forward=True): info=self.makeTextInfo(textInfos.POSITION_CARET) diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py index affdf80..96bad4f 100644 --- a/source/_UIAHandler.py +++ b/source/_UIAHandler.py @@ -167,7 +167,7 @@ class UIAHandler(COMObject): self.clientObject.RemoveAllEventHandlers() def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID): - if not self.MTAThreadInitEvent.isSet: + if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. return if eventID==UIA_MenuOpenedEventId and eventHandler.isPendingEvents("gainFocus"): @@ -186,7 +186,7 @@ class UIAHandler(COMObject): eventHandler.queueEvent(NVDAEventName,obj) def IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent(self,sender): - if not self.MTAThreadInitEvent.isSet: + if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. return if not self.isNativeUIAElement(sender): @@ -211,7 +211,7 @@ class UIAHandler(COMObject): eventHandler.queueEvent("gainFocus",obj) def IUIAutomationPropertyChangedEventHandler_HandlePropertyChangedEvent(self,sender,propertyId,newValue): - if not self.MTAThreadInitEvent.isSet: + if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. return NVDAEventName=UIAPropertyIdsToNVDAEventNames.get(propertyId,None) diff --git a/source/appModules/outlook.py b/source/appModules/outlook.py index 5506930..761f5dc 100644 --- a/source/appModules/outlook.py +++ b/source/appModules/outlook.py @@ -86,7 +86,9 @@ class AppModule(appModuleHandler.AppModule): windowClassName=obj.windowClassName states=obj.states controlID=obj.windowControlID - if role==controlTypes.ROLE_LISTITEM and (windowClassName.startswith("REListBox") or windowClassName.startswith("NetUIHWND")): + if windowClassName=="REListBox20W" and role==controlTypes.ROLE_CHECKBOX: + clsList.insert(0,REListBox20W_CheckBox) + elif role==controlTypes.ROLE_LISTITEM and (windowClassName.startswith("REListBox") or windowClassName.startswith("NetUIHWND")): clsList.insert(0,AutoCompleteListItem) if role==controlTypes.ROLE_LISTITEM and windowClassName=="OUTEXVLB": clsList.insert(0, AddressBookEntry) @@ -98,6 +100,16 @@ class AppModule(appModuleHandler.AppModule): elif isinstance(obj,IAccessible) and obj.event_objectID==winUser.OBJID_CLIENT and obj.event_childID==0: clsList.insert(0,SuperGridClient2010) +class REListBox20W_CheckBox(IAccessible): + + def script_checkbox(self, gesture): + gesture.send() + self.event_stateChange() + + __gestures={ + "kb:space":"checkbox", + } + class SuperGridClient2010(IAccessible): def isDuplicateIAccessibleEvent(self,obj): diff --git a/source/appModules/zendstudio.py b/source/appModules/zendstudio.py new file mode 100644 index 0000000..d5ab377 --- /dev/null +++ b/source/appModules/zendstudio.py @@ -0,0 +1,5 @@ +"""App module for Zend Studio +This simply uses the app module for Eclipse. +""" + +from eclipse import * diff --git a/source/controlTypes.py b/source/controlTypes.py index b4d92f0..35745c8 100644 --- a/source/controlTypes.py +++ b/source/controlTypes.py @@ -183,6 +183,7 @@ STATE_SORTED_ASCENDING=0x100000000 STATE_SORTED_DESCENDING=0x200000000 STATES_SORTED=frozenset([STATE_SORTED,STATE_SORTED_ASCENDING,STATE_SORTED_DESCENDING]) STATE_HASLONGDESC=0x400000000 +STATE_PINNED=0x800000000 roleLabels={ # Translators: The word for an unknown control type. @@ -514,6 +515,8 @@ stateLabels={ STATE_SORTED_DESCENDING:_("sorted descending"), # Translators: a state that denotes that an object (usually a graphic) has a long description. STATE_HASLONGDESC:_("has long description"), + # Translators: a state that denotes that an object is pinned in its current location + STATE_PINNED:_("pinned"), } negativeStateLabels={ This diff is so big that we needed to truncate the remainder. https://bitbucket.org/nvdaaddonteam/nvda/commits/5281be32c51a/ Changeset: 5281be32c51a Branch: None User: mdcurran Date: 2013-09-02 08:11:09 Summary: The Input Gestures dialog can be opened with NVDA+control+i. Re #1532 Affected #: 1 file diff --git a/source/globalCommands.py b/source/globalCommands.py index d0a3258..c7078cd 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -1036,6 +1036,11 @@ class GlobalCommands(ScriptableObject): # Translators: Input help mode message for apply last saved or default settings command. script_revertConfiguration.__doc__ = _("Pressing once reverts the current configuration to the most recently saved state. Pressing three times reverts to factory defaults.") + def script_activateInputGesturesDialog(self, gesture): + wx.CallAfter(gui.mainFrame.onInputGesturesCommand, None) + # Translators: Input help mode message for go to Input Gestures dialog command. + script_activateInputGesturesDialog.__doc__ = _("Shows the NVDA input gestures dialog") + def script_activatePythonConsole(self,gesture): if globalVars.appArgs.secure: return @@ -1356,6 +1361,7 @@ class GlobalCommands(ScriptableObject): "kb:NVDA+control+o": "activateObjectPresentationDialog", "kb:NVDA+control+b": "activateBrowseModeDialog", "kb:NVDA+control+d": "activateDocumentFormattingDialog", + "kb:NVDA+control+i": "activateInputGesturesDialog", # Save/reload configuration "kb:NVDA+control+c": "saveConfiguration", https://bitbucket.org/nvdaaddonteam/nvda/commits/8d076275085b/ Changeset: 8d076275085b Branch: None User: mdcurran Date: 2013-09-02 08:13:06 Summary: Give all scripts on virtualBuffers a script category (for the input guestures dialog) of 'Browse mode'. Re #1532 Affected #: 1 file diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 0cdccd6..37ed488 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -564,6 +564,9 @@ class ElementsListDialog(wx.Dialog): class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInterceptor): + # Translators: the script category for browse mode + scriptCategory=_("Browse mode") + REASON_QUICKNAV = "quickNav" TextInfo=VirtualBufferTextInfo https://bitbucket.org/nvdaaddonteam/nvda/commits/c9e945183c38/ Changeset: c9e945183c38 Branch: None User: mdcurran Date: 2013-09-02 08:42:30 Summary: Set the scriptCategory of 'Browse mode' on the cursorManager class rather than the VirtualBuffer class, so that it also includes NVDA-specific text find commands. Re #1532 Affected #: 2 files diff --git a/source/cursorManager.py b/source/cursorManager.py index 9ae3b89..4864ebb 100644 --- a/source/cursorManager.py +++ b/source/cursorManager.py @@ -32,6 +32,9 @@ class CursorManager(baseObject.ScriptableObject): @type selection: L{textInfos.TextInfo} """ + # Translators: the script category for browse mode + scriptCategory=_("Browse mode") + _lastFindText="" def __init__(self, *args, **kwargs): diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 37ed488..0cdccd6 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -564,9 +564,6 @@ class ElementsListDialog(wx.Dialog): class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInterceptor): - # Translators: the script category for browse mode - scriptCategory=_("Browse mode") - REASON_QUICKNAV = "quickNav" TextInfo=VirtualBufferTextInfo https://bitbucket.org/nvdaaddonteam/nvda/commits/43d9ae1f03d2/ Changeset: 43d9ae1f03d2 Branch: None User: jteh Date: 2013-09-02 09:19:52 Summary: Merge branch 't3485' into next Incubates #3485. Affected #: 2 files diff --git a/source/appModules/poedit.py b/source/appModules/poedit.py index c6e0c69..995618d 100644 --- a/source/appModules/poedit.py +++ b/source/appModules/poedit.py @@ -1,6 +1,6 @@ #appModules/poedit.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2012 Mesar Hameed <mhameed@xxxxxxxxxxxxx> +#Copyright (C) 2012-2013 Mesar Hameed, NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -15,6 +15,9 @@ import textInfos import tones import ui from NVDAObjects.IAccessible import sysListView32 +import windowUtils +import NVDAObjects.IAccessible +import winUser def getPath(obj, ancestor): @@ -65,44 +68,47 @@ def fetchObject(obj, path): class AppModule(appModuleHandler.AppModule): def script_reportAutoCommentsWindow(self,gesture): - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 0, 0]) - # check the controlid, because in certain situations - # autoComments and comment windows change places. - if obj and obj.windowControlID == 102: + obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 1]) + if obj and obj.windowControlID != 101: + try: + obj = obj.next.firstChild + except AttributeError: + obj = None + elif obj: + obj = obj.firstChild + if obj: try: ui.message(obj.name + " " + obj.value) except: # Translators: this message is reported when there are no - # comments to be presented to the user in the automatic - # comments window in poedit. - ui.message(_("No automatic comments.")) + # notes for translators to be presented to the user in Poedit. + ui.message(_("No notes for translators.")) else: # Translators: this message is reported when NVDA is unable to find - # the 'automatic comments' window in poedit. - ui.message(_("Could not find automatic comments window.")) + # the 'Notes for translators' window in poedit. + ui.message(_("Could not find Notes for translators window.")) # Translators: The description of an NVDA command for Poedit. - script_reportAutoCommentsWindow.__doc__ = _("Reports any comments in the automatic comments window") + script_reportAutoCommentsWindow.__doc__ = _("Reports any notes for translators") def script_reportCommentsWindow(self,gesture): - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 1, 0]) - # if it isnt in the normal location, try to find it in the - # location of the automatic window. - if not obj: - obj = fetchObject(api.getForegroundObject(), [2, 0, 1, 0, 1, 0, 0, 0]) - if obj and obj.windowControlID == 105: - try: - ui.message(obj.name + " " + obj.value) - except: - # Translators: this message is reported when there are no - # comments to be presented to the user in the translator - # comments window in poedit. - ui.message(_("No comment.")) - else: + try: + obj = NVDAObjects.IAccessible.getNVDAObjectFromEvent( + windowUtils.findDescendantWindow(api.getForegroundObject().windowHandle, visible=True, controlID=104), + winUser.OBJID_CLIENT, 0) + except LookupError: # Translators: this message is reported when NVDA is unable to find # the 'comments' window in poedit. ui.message(_("Could not find comment window.")) + return None + try: + ui.message(obj.name + " " + obj.value) + except: + # Translators: this message is reported when there are no + # comments to be presented to the user in the translator + # comments window in poedit. + ui.message(_("No comment.")) # Translators: The description of an NVDA command for Poedit. - script_reportAutoCommentsWindow.__doc__ = _("Reports any comments in the comments window") + script_reportCommentsWindow.__doc__ = _("Reports any comments in the comments window") __gestures = { "kb:control+shift+c": "reportCommentsWindow", @@ -112,28 +118,26 @@ class AppModule(appModuleHandler.AppModule): def chooseNVDAObjectOverlayClasses(self, obj, clsList): if "SysListView32" in obj.windowClassName and obj.role==controlTypes.ROLE_LISTITEM: clsList.insert(0,PoeditListItem) - if obj.role == controlTypes.ROLE_EDITABLETEXT: - if obj.windowControlID == 102: - # Translators: Automatic comments is the name of the poedit - # window that displays comments extracted from code. - obj.name = _("Automatic comments:") - if obj.windowControlID == 104: - # Translators: this is the label for the edit area in poedit - # that contains a translation. - obj.name = _("Translation:") - if obj.windowControlID == 105: - # Translators: 'comments:' is the name of the poedit window - # that displays comments entered by the translator. - obj.name = _("Comments:") + + def event_NVDAObject_init(self, obj): + if obj.role == controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_MULTILINE in obj.states and obj.isInForeground: + # Oleacc often gets the name wrong. + # The label object is positioned just above the field on the screen. + l, t, w, h = obj.location + try: + obj.name = NVDAObjects.NVDAObject.objectFromPoint(l + 10, t - 10).name + except AttributeError: + pass + return class PoeditListItem(sysListView32.ListItem): def _get_isBold(self): info=displayModel.DisplayModelTextInfo(self,position=textInfos.POSITION_FIRST) - info.expand(textInfos.UNIT_LINE) + info.expand(textInfos.UNIT_CHARACTER) fields=info.getTextWithFields() try: - return fields[1].field['bold'] + return fields[0].field['bold'] except: return False diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 7a7ad21..73fdc5f 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -554,7 +554,7 @@ Note: The report remaining time shortcut works only with the default formatting %kc:beginInclude || Name | Key | Description | | Report Comments Window | control+shift+c | Reports any comments in the comments window. | -| Report automatic comments window | control+shift+a | Reports any comments in the automatic comments window. | +| Report notes for translators | control+shift+a | Reports any notes for translators. | %kc:endInclude + Configuring NVDA + https://bitbucket.org/nvdaaddonteam/nvda/commits/f28d3553b633/ Changeset: f28d3553b633 Branch: None User: mdcurran Date: 2013-09-03 02:32:03 Summary: Again move the browse mode script category string, this time into inputCore as a constant of SCRCAT_BROWSEMODE. this is so it can also be used by globalcommands (toggle pasThrough). Affected #: 3 files diff --git a/source/cursorManager.py b/source/cursorManager.py index 4864ebb..74c2f51 100644 --- a/source/cursorManager.py +++ b/source/cursorManager.py @@ -18,6 +18,7 @@ import speech import config import braille import controlTypes +from inputCore import SCRCAT_BROWSEMODE class CursorManager(baseObject.ScriptableObject): """ @@ -33,7 +34,7 @@ class CursorManager(baseObject.ScriptableObject): """ # Translators: the script category for browse mode - scriptCategory=_("Browse mode") + scriptCategory=SCRCAT_BROWSEMODE _lastFindText="" diff --git a/source/globalCommands.py b/source/globalCommands.py index c7078cd..ae2e995 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -724,6 +724,7 @@ class GlobalCommands(ScriptableObject): virtualBuffers.reportPassThrough(vbuf) # Translators: Input help mode message for toggle focus and browse mode command in web browsing and other situations. script_toggleVirtualBufferPassThrough.__doc__=_("Toggles between browse mode and focus mode. When in focus mode, keys will pass straight through to the application, allowing you to interact directly with a control. When in browse mode, you can navigate the document with the cursor, quick navigation keys, etc.") + script_toggleVirtualBufferPassThrough.category=inputCore.SCRCAT_BROWSEMODE def script_quit(self,gesture): gui.quit() diff --git a/source/inputCore.py b/source/inputCore.py index 3b70125..62236c6 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -33,6 +33,9 @@ SCRCAT_KBEMU = _("Emulated system keyboard keys") #: Script category for miscellaneous commands. # Translators: The name of a category of NVDA commands. SCRCAT_MISC = _("Miscellaneous") +#: Script category for Browse Mode commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BROWSEMODE = _("Browse mode") class NoInputGestureAction(LookupError): """Informs that there is no action to execute for a gesture. https://bitbucket.org/nvdaaddonteam/nvda/commits/fba591f91e17/ Changeset: fba591f91e17 Branch: None User: mdcurran Date: 2013-09-03 07:15:46 Summary: Added script categories of mouse, textReview, objectNavigation and systemCaret to all related scripts in globalCommands. Affected #: 1 file diff --git a/source/globalCommands.py b/source/globalCommands.py index ae2e995..1a8f4c9 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -37,6 +37,19 @@ import virtualBuffers import characterProcessing from baseObject import ScriptableObject +#: Script category for text review commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TEXTREVIEW = _("Text review") +#: Script category for Object navigation commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_OBJECTNAVIGATION = _("Object navigation") +#: Script category for system caret commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEMCARET = _("System caret") +#: Script category for mouse commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_MOUSE = _("Mouse") + class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. """ @@ -85,6 +98,7 @@ class GlobalCommands(ScriptableObject): speech.speakSpelling(info.text) # Translators: Input help mode message for report current line command. script_reportCurrentLine.__doc__=_("Reports the current line under the application cursor. Pressing this key twice will spell the current line") + script_reportCurrentLine.category=SCRCAT_SYSTEMCARET def script_leftMouseClick(self,gesture): # Translators: Reported when left mouse button is clicked. @@ -93,6 +107,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_LEFTUP,0,0,None,None) # Translators: Input help mode message for left mouse click command. script_leftMouseClick.__doc__=_("Clicks the left mouse button once at the current mouse position") + script_leftMouseClick.category=SCRCAT_MOUSE def script_rightMouseClick(self,gesture): # Translators: Reported when right mouse button is clicked. @@ -101,6 +116,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_RIGHTUP,0,0,None,None) # Translators: Input help mode message for right mouse click command. script_rightMouseClick.__doc__=_("Clicks the right mouse button once at the current mouse position") + script_rightMouseClick.category=SCRCAT_MOUSE def script_toggleLeftMouseButton(self,gesture): if winUser.getKeyState(winUser.VK_LBUTTON)&32768: @@ -113,6 +129,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_LEFTDOWN,0,0,None,None) # Translators: Input help mode message for left mouse lock/unlock toggle command. script_toggleLeftMouseButton.__doc__=_("Locks or unlocks the left mouse button") + script_toggleLeftMouseButton.category=SCRCAT_MOUSE def script_toggleRightMouseButton(self,gesture): if winUser.getKeyState(winUser.VK_RBUTTON)&32768: @@ -125,6 +142,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_RIGHTDOWN,0,0,None,None) # Translators: Input help mode message for right mouse lock/unlock command. script_toggleRightMouseButton.__doc__=_("Locks or unlocks the right mouse button") + script_toggleRightMouseButton.category=SCRCAT_MOUSE def script_reportCurrentSelection(self,gesture): obj=api.getFocusObject() @@ -141,6 +159,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("selected %s")%info.text) # Translators: Input help mode message for report current selection command. script_reportCurrentSelection.__doc__=_("Announces the current selection in edit controls and documents. If there is no selection it says so.") + script_reportCurrentSelection.category=SCRCAT_SYSTEMCARET def script_dateTime(self,gesture): if scriptHandler.getLastScriptRepeatCount()==0: @@ -269,6 +288,7 @@ class GlobalCommands(ScriptableObject): mouseHandler.executeMouseMoveEvent(x,y) # Translators: Input help mode message for move mouse to navigator object command. script_moveMouseToNavigatorObject.__doc__=_("Moves the mouse pointer to the current navigator object") + script_moveMouseToNavigatorObject.category=SCRCAT_MOUSE def script_moveNavigatorObjectToMouse(self,gesture): # Translators: Reported when attempting to move the navigator object to the object under mouse pointer. @@ -278,6 +298,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(obj) # Translators: Input help mode message for move navigator object to mouse command. script_moveNavigatorObjectToMouse.__doc__=_("Sets the navigator object to the current object under the mouse pointer and speaks it") + script_moveNavigatorObjectToMouse.category=SCRCAT_MOUSE def script_reviewMode_next(self,gesture): label=review.nextMode() @@ -340,6 +361,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(curObject,reason=controlTypes.REASON_QUERY) # Translators: Input help mode message for report current navigator object command. script_navigatorObject_current.__doc__=_("Reports the current navigator object. Pressing twice spells this information,and pressing three times Copies name and value of this object to the clipboard") + script_navigatorObject_current.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_currentDimensions(self,gesture): obj=api.getNavigatorObject() @@ -363,6 +385,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Object edges positioned {left:.1f} per cent from left edge of screen, {top:.1f} per cent from top edge of screen, width is {width:.1f} per cent of screen, height is {height:.1f} per cent of screen").format(left=percentFromLeft,top=percentFromTop,width=percentWidth,height=percentHeight)) # Translators: Input help mode message for report object dimensions command. script_navigatorObject_currentDimensions.__doc__=_("Reports the hight, width and position of the current navigator object") + script_navigatorObject_currentDimensions.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_toFocus(self,gesture): obj=api.getFocusObject() @@ -376,6 +399,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(obj,reason=controlTypes.REASON_FOCUS) # Translators: Input help mode message for move navigator object to current focus command. script_navigatorObject_toFocus.__doc__=_("Sets the navigator object to the current focus, and the review cursor to the position of the caret inside it, if possible.") + script_navigatorObject_toFocus.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_moveFocus(self,gesture): obj=api.getNavigatorObject() @@ -401,6 +425,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move focus to current navigator object command. script_navigatorObject_moveFocus.__doc__=_("Pressed once Sets the keyboard focus to the navigator object, pressed twice sets the system caret to the position of the review cursor") + script_navigatorObject_moveFocus.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_parent(self,gesture): curObject=api.getNavigatorObject() @@ -417,6 +442,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No containing object")) # Translators: Input help mode message for move to parent object command. script_navigatorObject_parent.__doc__=_("Moves the navigator object to the object containing it") + script_navigatorObject_parent.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_next(self,gesture): curObject=api.getNavigatorObject() @@ -433,6 +459,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No next")) # Translators: Input help mode message for move to next object command. script_navigatorObject_next.__doc__=_("Moves the navigator object to the next object") + script_navigatorObject_next.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_previous(self,gesture): curObject=api.getNavigatorObject() @@ -449,6 +476,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No previous")) # Translators: Input help mode message for move to previous object command. script_navigatorObject_previous.__doc__=_("Moves the navigator object to the previous object") + script_navigatorObject_previous.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_firstChild(self,gesture): curObject=api.getNavigatorObject() @@ -465,6 +493,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No objects inside")) # Translators: Input help mode message for move to first child object command. script_navigatorObject_firstChild.__doc__=_("Moves the navigator object to the first object inside it") + script_navigatorObject_firstChild.category=SCRCAT_OBJECTNAVIGATION def script_review_activate(self,gesture): # Translators: a message reported when the action at the position of the review cursor or navigator object is performed. @@ -498,6 +527,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("No action")) # Translators: Input help mode message for activate current object command. script_review_activate.__doc__=_("Performs the default action on the current navigator object (example: presses it if it is a button).") + script_review_activate.category=SCRCAT_OBJECTNAVIGATION def script_review_top(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_FIRST) @@ -507,6 +537,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to top line command. script_review_top.__doc__=_("Moves the review cursor to the top line of the current navigator object and speaks it") + script_review_top.category=SCRCAT_TEXTREVIEW def script_review_previousLine(self,gesture): info=api.getReviewPosition().copy() @@ -521,6 +552,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to previous line command. script_review_previousLine.__doc__=_("Moves the review cursor to the previous line of the current navigator object and speaks it") + script_review_previousLine.category=SCRCAT_TEXTREVIEW def script_review_currentLine(self,gesture): info=api.getReviewPosition().copy() @@ -532,6 +564,7 @@ class GlobalCommands(ScriptableObject): speech.spellTextInfo(info,useCharacterDescriptions=scriptCount>1) # Translators: Input help mode message for read current line under review cursor command. script_review_currentLine.__doc__=_("Reports the line of the current navigator object where the review cursor is situated. If this key is pressed twice, the current line will be spelled. Pressing three times will spell the line using character descriptions.") + script_review_currentLine.category=SCRCAT_TEXTREVIEW def script_review_nextLine(self,gesture): info=api.getReviewPosition().copy() @@ -546,6 +579,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to next line command. script_review_nextLine.__doc__=_("Moves the review cursor to the next line of the current navigator object and speaks it") + script_review_nextLine.category=SCRCAT_TEXTREVIEW def script_review_bottom(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) @@ -555,6 +589,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to bottom line command. script_review_bottom.__doc__=_("Moves the review cursor to the bottom line of the current navigator object and speaks it") + script_review_bottom.category=SCRCAT_TEXTREVIEW def script_review_previousWord(self,gesture): info=api.getReviewPosition().copy() @@ -570,6 +605,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET,unit=textInfos.UNIT_WORD) # Translators: Input help mode message for move review cursor to previous word command. script_review_previousWord.__doc__=_("Moves the review cursor to the previous word of the current navigator object and speaks it") + script_review_previousWord.category=SCRCAT_TEXTREVIEW def script_review_currentWord(self,gesture): info=api.getReviewPosition().copy() @@ -581,6 +617,7 @@ class GlobalCommands(ScriptableObject): speech.spellTextInfo(info,useCharacterDescriptions=scriptCount>1) # Translators: Input help mode message for report current word under review cursor command. script_review_currentWord.__doc__=_("Speaks the word of the current navigator object where the review cursor is situated. Pressing twice spells the word. Pressing three times spells the word using character descriptions") + script_review_currentWord.category=SCRCAT_TEXTREVIEW def script_review_nextWord(self,gesture): info=api.getReviewPosition().copy() @@ -595,6 +632,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET,unit=textInfos.UNIT_WORD) # Translators: Input help mode message for move review cursor to next word command. script_review_nextWord.__doc__=_("Moves the review cursor to the next word of the current navigator object and speaks it") + script_review_nextWord.category=SCRCAT_TEXTREVIEW def script_review_startOfLine(self,gesture): info=api.getReviewPosition().copy() @@ -606,6 +644,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to start of current line command. script_review_startOfLine.__doc__=_("Moves the review cursor to the first character of the line where it is situated in the current navigator object and speaks it") + script_review_startOfLine.category=SCRCAT_TEXTREVIEW def script_review_previousCharacter(self,gesture): lineInfo=api.getReviewPosition().copy() @@ -625,6 +664,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(charInfo,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to previous character command. script_review_previousCharacter.__doc__=_("Moves the review cursor to the previous character of the current navigator object and speaks it") + script_review_previousCharacter.category=SCRCAT_TEXTREVIEW def script_review_currentCharacter(self,gesture): info=api.getReviewPosition().copy() @@ -643,6 +683,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for report current character under review cursor command. script_review_currentCharacter.__doc__=_("Reports the character of the current navigator object where the review cursor is situated. Pressing twice reports a description or example of that character. Pressing three times reports the numeric value of the character in decimal and hexadecimal") + script_review_currentCharacter.category=SCRCAT_TEXTREVIEW def script_review_nextCharacter(self,gesture): lineInfo=api.getReviewPosition().copy() @@ -662,6 +703,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(charInfo,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to next character command. script_review_nextCharacter.__doc__=_("Moves the review cursor to the next character of the current navigator object and speaks it") + script_review_nextCharacter.category=SCRCAT_TEXTREVIEW def script_review_endOfLine(self,gesture): info=api.getReviewPosition().copy() @@ -674,6 +716,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to end of current line command. script_review_endOfLine.__doc__=_("Moves the review cursor to the last character of the line where it is situated in the current navigator object and speaks it") + script_review_endOfLine.category=SCRCAT_TEXTREVIEW def script_speechMode(self,gesture): curMode=speech.speechMode @@ -740,11 +783,13 @@ class GlobalCommands(ScriptableObject): sayAllHandler.readText(sayAllHandler.CURSOR_REVIEW) # Translators: Input help mode message for say all in review cursor command. script_review_sayAll.__doc__ = _("reads from the review cursor up to end of current text, moving the review cursor as it goes") + script_review_sayAll.category=SCRCAT_TEXTREVIEW def script_sayAll(self,gesture): sayAllHandler.readText(sayAllHandler.CURSOR_CARET) # Translators: Input help mode message for say all with system caret command. script_sayAll.__doc__ = _("reads from the system caret up to the end of the text, moving the caret as it goes") + script_sayAll.category=SCRCAT_SYSTEMCARET def script_reportFormatting(self,gesture): formatConfig={ @@ -782,6 +827,7 @@ class GlobalCommands(ScriptableObject): ui.message(" ".join(textList)) # Translators: Input help mode message for report formatting command. script_reportFormatting.__doc__ = _("Reports formatting info for the current review cursor position within a document") + script_reportFormatting.category=SCRCAT_TEXTREVIEW def script_reportCurrentFocus(self,gesture): focusObject=api.getFocusObject() @@ -835,6 +881,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle mouse tracking command. script_toggleMouseTracking.__doc__=_("Toggles the reporting of information as the mouse moves") + script_toggleMouseTracking.category=SCRCAT_MOUSE def script_title(self,gesture): obj=api.getForegroundObject() @@ -1089,6 +1136,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Start marked")) # Translators: Input help mode message for mark review cursor position for copy command (that is, marks the current review cursor position as the starting point for text to be copied). script_review_markStartForCopy.__doc__ = _("Marks the current position of the review cursor as the start of text to be copied") + script_review_markStartForCopy.category=SCRCAT_TEXTREVIEW def script_review_copy(self, gesture): if not getattr(self, "_copyStartMarker", None): @@ -1112,6 +1160,7 @@ class GlobalCommands(ScriptableObject): self._copyStartMarker = None # Translators: Input help mode message for copy selected review cursor text to clipboard command. script_review_copy.__doc__ = _("Retrieves the text from the previously set start marker up to and including the current position of the review cursor and copies it to the clipboard") + script_review_copy.category=SCRCAT_TEXTREVIEW def script_braille_scrollBack(self, gesture): braille.handler.scrollBack() @@ -1178,6 +1227,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("no next")) # Translators: Input help mode message for a touchscreen gesture. script_navigatorObject_nextInFlow.__doc__=_("Moves to the next object in a flattened view of the object navigation hierarchy") + script_navigatorObject_nextInFlow.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_previousInFlow(self,gesture): curObject=api.getNavigatorObject() @@ -1195,6 +1245,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("no next")) # Translators: Input help mode message for a touchscreen gesture. script_navigatorObject_previousInFlow.__doc__=_("Moves to the previous object in a flattened view of the object navigation hierarchy") + script_navigatorObject_previousInFlow.category=SCRCAT_OBJECTNAVIGATION def script_touch_changeMode(self,gesture): mode=touchHandler.handler._curTouchMode https://bitbucket.org/nvdaaddonteam/nvda/commits/a09af714be5e/ Changeset: a09af714be5e Branch: None User: mdcurran Date: 2013-09-04 01:24:46 Summary: Added more script categories to globalCommands and associated many more scripts with them. speech, braille, tools, system, focus, config. Affected #: 1 file diff --git a/source/globalCommands.py b/source/globalCommands.py index 1a8f4c9..3ebcba4 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -49,6 +49,27 @@ SCRCAT_SYSTEMCARET = _("System caret") #: Script category for mouse commands. # Translators: The name of a category of NVDA commands. SCRCAT_MOUSE = _("Mouse") +#: Script category for mouse commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SPEECH = _("Speech") +#: Script category for configuration dialogs commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_CONFIG = _("Configuration") +#: Script category for Braille commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BRAILLE = _("Braille") +#: Script category for tools commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOOLS = _("Tools") +#: Script category for touch commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOUCH = _("Touch screen") +#: Script category for focus commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_FOCUS = _("System focus") +#: Script category for system status commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEM = _("System status") class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. @@ -169,6 +190,7 @@ class GlobalCommands(ScriptableObject): ui.message(text) # Translators: Input help mode message for report date and time command. script_dateTime.__doc__=_("If pressed once, reports the current time. If pressed twice, reports the current date") + script_dateTime.category=SCRCAT_SYSTEM def script_increaseSynthSetting(self,gesture): settingName=globalVars.settingsRing.currentSettingName @@ -180,6 +202,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s" % (settingName,settingValue)) # Translators: Input help mode message for increase synth setting value command. script_increaseSynthSetting.__doc__=_("Increases the currently active setting in the synth settings ring") + script_increaseSynthSetting.category=SCRCAT_SPEECH def script_decreaseSynthSetting(self,gesture): settingName=globalVars.settingsRing.currentSettingName @@ -190,6 +213,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s" % (settingName,settingValue)) # Translators: Input help mode message for decrease synth setting value command. script_decreaseSynthSetting.__doc__=_("Decreases the currently active setting in the synth settings ring") + script_decreaseSynthSetting.category=SCRCAT_SPEECH def script_nextSynthSetting(self,gesture): nextSettingName=globalVars.settingsRing.next() @@ -200,6 +224,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s"%(nextSettingName,nextSettingValue)) # Translators: Input help mode message for next synth setting command. script_nextSynthSetting.__doc__=_("Moves to the next available setting in the synth settings ring") + script_nextSynthSetting.category=SCRCAT_SPEECH def script_previousSynthSetting(self,gesture): previousSettingName=globalVars.settingsRing.previous() @@ -210,6 +235,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s"%(previousSettingName,previousSettingValue)) # Translators: Input help mode message for previous synth setting command. script_previousSynthSetting.__doc__=_("Moves to the previous available setting in the synth settings ring") + script_previousSynthSetting.category=SCRCAT_SPEECH def script_toggleSpeakTypedCharacters(self,gesture): if config.conf["keyboard"]["speakTypedCharacters"]: @@ -223,6 +249,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speaked typed characters command. script_toggleSpeakTypedCharacters.__doc__=_("Toggles on and off the speaking of typed characters") + script_toggleSpeakTypedCharacters.category=SCRCAT_SPEECH def script_toggleSpeakTypedWords(self,gesture): if config.conf["keyboard"]["speakTypedWords"]: @@ -236,6 +263,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speak typed words command. script_toggleSpeakTypedWords.__doc__=_("Toggles on and off the speaking of typed words") + script_toggleSpeakTypedWords.category=SCRCAT_SPEECH def script_toggleSpeakCommandKeys(self,gesture): if config.conf["keyboard"]["speakCommandKeys"]: @@ -249,6 +277,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speak command keys command. script_toggleSpeakCommandKeys.__doc__=_("Toggles on and off the speaking of typed keys, that are not specifically characters") + script_toggleSpeakCommandKeys.category=SCRCAT_SPEECH def script_cycleSpeechSymbolLevel(self,gesture): curLevel = config.conf["speech"]["symbolLevel"] @@ -265,6 +294,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("symbol level %s") % name) # Translators: Input help mode message for cycle speech symbol level command. script_cycleSpeechSymbolLevel.__doc__=_("Cycles through speech symbol levels which determine what symbols are spoken") + script_cycleSpeechSymbolLevel.category=SCRCAT_SPEECH def script_moveMouseToNavigatorObject(self,gesture): obj=api.getNavigatorObject() @@ -311,6 +341,7 @@ class GlobalCommands(ScriptableObject): # Translators: reported when there are no other available review modes for this object ui.message(_("No next review mode")) script_reviewMode_next.__doc__=_("Switches to the next review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") + script_reviewMode_next.category=SCRCAT_TEXTREVIEW def script_reviewMode_previous(self,gesture): label=review.nextMode(prev=True) @@ -323,7 +354,8 @@ class GlobalCommands(ScriptableObject): # Translators: reported when there are no other available review modes for this object ui.message(_("No previous review mode")) script_reviewMode_previous.__doc__=_("Switches to the previous review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") - + script_reviewMode_previous.category=SCRCAT_TEXTREVIEW + def script_navigatorObject_current(self,gesture): curObject=api.getNavigatorObject() if not isinstance(curObject,NVDAObject): @@ -736,6 +768,7 @@ class GlobalCommands(ScriptableObject): speech.speechMode=newMode # Translators: Input help mode message for toggle speech mode command. script_speechMode.__doc__=_("Toggles between the speech modes of off, beep and talk. When set to off NVDA will not speak anything. If beeps then NVDA will simply beep each time it its supposed to speak something. If talk then NVDA wil just speak normally.") + script_speechMode.category=SCRCAT_SPEECH def script_moveToParentTreeInterceptor(self,gesture): obj=api.getFocusObject() @@ -754,6 +787,7 @@ class GlobalCommands(ScriptableObject): wx.CallLater(50,eventHandler.executeEvent,"gainFocus",parent.treeInterceptor.rootNVDAObject) # Translators: Input help mode message for move to next document with focus command, mostly used in web browsing to move from embedded object to the webpage document. script_moveToParentTreeInterceptor.__doc__=_("Moves the focus to the next closest document that contains the focus") + script_moveToParentTreeInterceptor.category=SCRCAT_FOCUS def script_toggleVirtualBufferPassThrough(self,gesture): vbuf = api.getFocusObject().treeInterceptor @@ -840,6 +874,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("no focus")) # Translators: Input help mode message for report current focus command. script_reportCurrentFocus.__doc__ = _("reports the object with focus") + script_reportCurrentFocus.category=SCRCAT_FOCUS def script_reportStatusLine(self,gesture): obj = api.getStatusBar() @@ -868,6 +903,7 @@ class GlobalCommands(ScriptableObject): speech.speakSpelling(text) # Translators: Input help mode message for report status line text command. script_reportStatusLine.__doc__ = _("reads the current application status bar and moves the navigator to it") + script_reportStatusLine.category=SCRCAT_FOCUS def script_toggleMouseTracking(self,gesture): if config.conf["mouse"]["enableMouseTracking"]: @@ -901,6 +937,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("%s copied to clipboard")%title) # Translators: Input help mode message for report title bar command. script_title.__doc__=_("Reports the title of the current application or foreground window. If pressed twice, spells the title. If pressed three times, copies the title to the clipboard") + script_title.category=SCRCAT_FOCUS def script_speakForeground(self,gesture): obj=api.getForegroundObject() @@ -908,6 +945,7 @@ class GlobalCommands(ScriptableObject): sayAllHandler.readObjects(obj) # Translators: Input help mode message for read foreground object command (usually the foreground window). script_speakForeground.__doc__ = _("speaks the current foreground object") + script_speakForeground.category=SCRCAT_FOCUS def script_test_navigatorDisplayModelText(self,gesture): obj=api.getNavigatorObject() @@ -920,6 +958,7 @@ class GlobalCommands(ScriptableObject): log.info("Developer info for navigator object:\n%s" % "\n".join(obj.devInfo), activateLogViewer=True) # Translators: Input help mode message for developer info for current navigator object command, used by developers to examine technical info on navigator object. This command also serves as a shortcut to open NVDA log viewer. script_navigatorObject_devInfo.__doc__ = _("Logs information about the current navigator object which is useful to developers and activates the log viewer so the information can be examined.") + script_navigatorObject_devInfo.category=SCRCAT_TOOLS def script_toggleProgressBarOutput(self,gesture): outputMode=config.conf["presentation"]["progressBarUpdates"]["progressBarOutputMode"] @@ -1005,6 +1044,7 @@ class GlobalCommands(ScriptableObject): ui.message(text) # Translators: Input help mode message for report battery status command. script_say_battery_status.__doc__ = _("reports battery status and time remaining if AC is not plugged in") + script_say_battery_status.category=SCRCAT_SYSTEM def script_passNextKeyThrough(self,gesture): keyboardHandler.passNextKeyThrough() @@ -1029,51 +1069,62 @@ class GlobalCommands(ScriptableObject): ui.message(message) # Translators: Input help mode message for report current program name and app module name command. script_reportAppModuleInfo.__doc__ = _("Speaks the filename of the active application along with the name of the currently loaded appModule") + script_reportAppModuleInfo.category=SCRCAT_TOOLS def script_activateGeneralSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onGeneralSettingsCommand, None) # Translators: Input help mode message for go to general settings dialog command. script_activateGeneralSettingsDialog.__doc__ = _("Shows the NVDA general settings dialog") + script_activateGeneralSettingsDialog.category=SCRCAT_CONFIG + def script_activateSynthesizerDialog(self, gesture): wx.CallAfter(gui.mainFrame.onSynthesizerCommand, None) # Translators: Input help mode message for go to synthesizer dialog command. script_activateSynthesizerDialog.__doc__ = _("Shows the NVDA synthesizer dialog") + script_activateSynthesizerDialog.category=SCRCAT_CONFIG def script_activateVoiceDialog(self, gesture): wx.CallAfter(gui.mainFrame.onVoiceCommand, None) # Translators: Input help mode message for go to voice settings dialog command. script_activateVoiceDialog.__doc__ = _("Shows the NVDA voice settings dialog") + script_activateVoiceDialog.category=SCRCAT_CONFIG def script_activateKeyboardSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onKeyboardSettingsCommand, None) # Translators: Input help mode message for go to keyboard settings dialog command. script_activateKeyboardSettingsDialog.__doc__ = _("Shows the NVDA keyboard settings dialog") + script_activateKeyboardSettingsDialog.category=SCRCAT_CONFIG def script_activateMouseSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onMouseSettingsCommand, None) # Translators: Input help mode message for go to mouse settings dialog command. script_activateMouseSettingsDialog.__doc__ = _("Shows the NVDA mouse settings dialog") + script_activateMouseSettingsDialog.category=SCRCAT_CONFIG def script_activateObjectPresentationDialog(self, gesture): wx.CallAfter(gui.mainFrame. onObjectPresentationCommand, None) # Translators: Input help mode message for go to object presentation dialog command. script_activateObjectPresentationDialog.__doc__ = _("Shows the NVDA object presentation settings dialog") + script_activateObjectPresentationDialog.category=SCRCAT_CONFIG def script_activateBrowseModeDialog(self, gesture): wx.CallAfter(gui.mainFrame.onBrowseModeCommand, None) # Translators: Input help mode message for go to browse mode dialog command. script_activateBrowseModeDialog.__doc__ = _("Shows the NVDA browse mode settings dialog") + script_activateBrowseModeDialog.category=SCRCAT_CONFIG def script_activateDocumentFormattingDialog(self, gesture): wx.CallAfter(gui.mainFrame.onDocumentFormattingCommand, None) # Translators: Input help mode message for go to document formatting dialog command. script_activateDocumentFormattingDialog.__doc__ = _("Shows the NVDA document formatting settings dialog") + script_activateDocumentFormattingDialog.category=SCRCAT_CONFIG def script_saveConfiguration(self,gesture): wx.CallAfter(gui.mainFrame.onSaveConfigurationCommand, None) # Translators: Input help mode message for save current configuration command. script_saveConfiguration.__doc__ = _("Saves the current NVDA configuration") + script_saveConfiguration.category=SCRCAT_CONFIG def script_revertConfiguration(self,gesture): scriptCount=scriptHandler.getLastScriptRepeatCount() @@ -1083,11 +1134,13 @@ class GlobalCommands(ScriptableObject): gui.mainFrame.onRevertToDefaultConfigurationCommand(None) # Translators: Input help mode message for apply last saved or default settings command. script_revertConfiguration.__doc__ = _("Pressing once reverts the current configuration to the most recently saved state. Pressing three times reverts to factory defaults.") + script_revertConfiguration.category=SCRCAT_CONFIG def script_activateInputGesturesDialog(self, gesture): wx.CallAfter(gui.mainFrame.onInputGesturesCommand, None) # Translators: Input help mode message for go to Input Gestures dialog command. script_activateInputGesturesDialog.__doc__ = _("Shows the NVDA input gestures dialog") + script_activateInputGesturesDialog.category=SCRCAT_CONFIG def script_activatePythonConsole(self,gesture): if globalVars.appArgs.secure: @@ -1099,6 +1152,7 @@ class GlobalCommands(ScriptableObject): pythonConsole.activate() # Translators: Input help mode message for activate python console command. script_activatePythonConsole.__doc__ = _("Activates the NVDA Python Console, primarily useful for development") + script_activatePythonConsole.category=SCRCAT_TOOLS def script_braille_toggleTether(self, gesture): if braille.handler.tether == braille.handler.TETHER_FOCUS: @@ -1111,6 +1165,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Braille tethered to %s") % tetherMsg) # Translators: Input help mode message for toggle braille tether to command (tethered means connected to or follows). script_braille_toggleTether.__doc__ = _("Toggle tethering of braille between the focus and the review position") + script_braille_toggleTether.category=SCRCAT_BRAILLE def script_reportClipboardText(self,gesture): try: @@ -1129,6 +1184,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("The clipboard contains a large portion of text. It is %s characters long") % len(text)) # Translators: Input help mode message for report clipboard text command. script_reportClipboardText.__doc__ = _("Reports the text on the Windows clipboard") + script_reportClipboardText.category=SCRCAT_SYSTEM def script_review_markStartForCopy(self, gesture): self._copyStartMarker = api.getReviewPosition().copy() @@ -1167,34 +1223,40 @@ class GlobalCommands(ScriptableObject): # Translators: Input help mode message for a braille command. script_braille_scrollBack.__doc__ = _("Scrolls the braille display back") script_braille_scrollBack.bypassInputHelp = True + script_braille_scrollBack.category=SCRCAT_BRAILLE def script_braille_scrollForward(self, gesture): braille.handler.scrollForward() # Translators: Input help mode message for a braille command. script_braille_scrollForward.__doc__ = _("Scrolls the braille display forward") script_braille_scrollForward.bypassInputHelp = True + script_braille_scrollForward.category=SCRCAT_BRAILLE def script_braille_routeTo(self, gesture): braille.handler.routeTo(gesture.routingIndex) # Translators: Input help mode message for a braille command. script_braille_routeTo.__doc__ = _("Routes the cursor to or activates the object under this braille cell") + script_braille_routeTo.category=SCRCAT_BRAILLE def script_braille_previousLine(self, gesture): if braille.handler.buffer.regions: braille.handler.buffer.regions[-1].previousLine(start=True) # Translators: Input help mode message for a braille command. script_braille_previousLine.__doc__ = _("Moves the braille display to the previous line") + script_braille_previousLine.category=SCRCAT_BRAILLE def script_braille_nextLine(self, gesture): if braille.handler.buffer.regions: braille.handler.buffer.regions[-1].nextLine() # Translators: Input help mode message for a braille command. script_braille_nextLine.__doc__ = _("Moves the braille display to the next line") + script_braille_nextLine.category=SCRCAT_BRAILLE def script_braille_dots(self, gesture): brailleInput.handler.input(gesture.dots) # Translators: Input help mode message for a braille command. script_braille_dots.__doc__= _("Inputs braille dots via the braille keyboard") + script_braille_dots.category=SCRCAT_BRAILLE def script_reloadPlugins(self, gesture): import globalPluginHandler @@ -1205,6 +1267,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Plugins reloaded")) # Translators: Input help mode message for reload plugins command. script_reloadPlugins.__doc__=_("Reloads app modules and global plugins without restarting NVDA, which can be Useful for developers") + script_reloadPlugins.category=SCRCAT_TOOLS def script_navigatorObject_nextInFlow(self,gesture): curObject=api.getNavigatorObject() @@ -1256,16 +1319,20 @@ class GlobalCommands(ScriptableObject): ui.message(_("%s mode")%newMode) # Translators: Input help mode message for a touchscreen gesture. script_touch_changeMode.__doc__=_("cycles between available touch modes") + script_touch_changeMode.category=SCRCAT_TOUCH + def script_touch_newExplore(self,gesture): touchHandler.handler.screenExplorer.moveTo(gesture.tracker.x,gesture.tracker.y,new=True) # Translators: Input help mode message for a touchscreen gesture. script_touch_newExplore.__doc__=_("Reports the object and content directly under your finger") + script_touch_newExplore.category=SCRCAT_TOUCH def script_touch_explore(self,gesture): touchHandler.handler.screenExplorer.moveTo(gesture.tracker.x,gesture.tracker.y) # Translators: Input help mode message for a touchscreen gesture. script_touch_explore.__doc__=_("Reports the new object or content under your finger if different to where your finger was last") + script_touch_explore.category=SCRCAT_TOUCH def script_touch_hoverUp(self,gesture): #Specifically for touch typing with onscreen keyboard keys @@ -1273,6 +1340,7 @@ class GlobalCommands(ScriptableObject): import NVDAObjects.UIA if isinstance(obj,NVDAObjects.UIA.UIA) and obj.UIAElement.cachedClassName=="CRootKey": obj.doAction() + script_touch_hoverUp.category=SCRCAT_TOUCH __gestures = { # Basic https://bitbucket.org/nvdaaddonteam/nvda/commits/4d16dce51182/ Changeset: 4d16dce51182 Branch: None User: mdcurran Date: 2013-09-04 03:49:43 Summary: More script categories for globalCommands. Affected #: 1 file diff --git a/source/globalCommands.py b/source/globalCommands.py index 3ebcba4..4221545 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -70,6 +70,9 @@ SCRCAT_FOCUS = _("System focus") #: Script category for system status commands. # Translators: The name of a category of NVDA commands. SCRCAT_SYSTEM = _("System status") +#: Script category for input commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_INPUT = _("Input") class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. @@ -85,6 +88,8 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle input help command. script_toggleInputHelp.__doc__=_("Turns input help on or off. When on, any input such as pressing a key on the keyboard will tell you what script is associated with that input, if any.") + script_toggleInputHelp.category=SCRCAT_INPUT + def script_toggleCurrentAppSleepMode(self,gesture): curFocus=api.getFocusObject() @@ -981,6 +986,7 @@ class GlobalCommands(ScriptableObject): config.conf["presentation"]["progressBarUpdates"]["progressBarOutputMode"]=outputMode # Translators: Input help mode message for toggle progress bar output command. script_toggleProgressBarOutput.__doc__=_("Toggles between beeps, speech, beeps and speech, and off, for reporting progress bar updates") + script_toggleProgressBarOutput.category=SCRCAT_SPEECH def script_toggleReportDynamicContentChanges(self,gesture): if config.conf["presentation"]["reportDynamicContentChanges"]: @@ -994,6 +1000,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle dynamic content changes command. script_toggleReportDynamicContentChanges.__doc__=_("Toggles on and off the reporting of dynamic content changes, such as new text in dos console windows") + script_toggleReportDynamicContentChanges.category=SCRCAT_SPEECH def script_toggleCaretMovesReviewCursor(self,gesture): if config.conf["reviewCursor"]["followCaret"]: @@ -1007,6 +1014,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle caret moves review cursor command. script_toggleCaretMovesReviewCursor.__doc__=_("Toggles on and off the movement of the review cursor due to the caret moving.") + script_toggleCaretMovesReviewCursor.category=SCRCAT_TEXTREVIEW def script_toggleFocusMovesNavigatorObject(self,gesture): if config.conf["reviewCursor"]["followFocus"]: @@ -1020,6 +1028,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle focus moves navigator object command. script_toggleFocusMovesNavigatorObject.__doc__=_("Toggles on and off the movement of the navigator object due to focus changes") + script_toggleFocusMovesNavigatorObject.category=SCRCAT_OBJECTNAVIGATION #added by Rui Batista<ruiandrebatista@xxxxxxxxx> to implement a battery status script def script_say_battery_status(self,gesture): @@ -1052,6 +1061,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Pass next key through")) # Translators: Input help mode message for pass next key through command. script_passNextKeyThrough.__doc__=_("The next key that is pressed will not be handled at all by NVDA, it will be passed directly through to Windows.") + script_passNextKeyThrough.category=SCRCAT_INPUT def script_reportAppModuleInfo(self,gesture): focus=api.getFocusObject() https://bitbucket.org/nvdaaddonteam/nvda/commits/7caf27df5c80/ Changeset: 7caf27df5c80 Branch: None User: jteh Date: 2013-09-05 08:10:19 Summary: inputCore: Provide a way to get display text from a gesture identifier. In order to support this for a particular source, an InputGesture subclass for that source must implement a new getDisplayTextForIdentifier class method. That InputGesture subclass must also be registered for the source prefix using registerGestureSource. Once this is done, inputCore.getDisplayTextForGestureIdentifier can be used for any associated gesture identifiers. Affected #: 1 file diff --git a/source/inputCore.py b/source/inputCore.py index 62236c6..67292d3 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -13,6 +13,7 @@ For example, it is used to execute gestures and handle input help. import sys import os import itertools +import weakref import configobj import baseObject import scriptHandler @@ -124,6 +125,22 @@ class InputGesture(baseObject.AutoPropertyObject): """ return None + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + """Get the text to be presented to the user describing a given gesture identifier. + This should only be called for gesture identifiers associated with this class. + Most callers will want L{inputCore.getDisplayTextForIdentifier} instead. + The display text consists of two strings: + the gesture's source (e.g. "laptop keyboard") + and the specific gesture (e.g. "alt+tab"). + @param identifier: The normalized gesture identifier in question. + @type identifier: basestring + @return: A tuple of (source, specificGesture). + @rtype: tuple of (basestring, basestring) + @raise Exception: If no display text can be determined. + """ + raise NotImplementedError + class GlobalGestureMap(object): """Maps gestures to scripts anywhere in NVDA. This is used to allow users and locales to bind gestures in addition to those bound by individual scriptable objects. @@ -636,6 +653,59 @@ def normalizeGestureIdentifier(identifier): main = "+".join(frozenset(main)) return u"{0}:{1}".format(prefix, main).lower() +#: Maps registered source prefix strings to L{InputGesture} classes. +gestureSources = weakref.WeakValueDictionary() + +def registerGestureSource(source, gestureCls): + """Register an input gesture class for a source prefix string. + The specified gesture class will be used for queries regarding all gesture identifiers with the given source prefix. + For example, if "kb" is registered with the C{KeyboardInputGesture} class, + any queries for "kb:tab" or "kb(desktop):tab" will be directed to the C{KeyboardInputGesture} class. + If there is no exact match for the source, any parenthesised portion is stripped. + For example, for "br(baum):d1", if "br(baum)" isn't registered, + "br" will be used if it is registered. + This registration is used, for example, to get the display text for a gesture identifier. + @param source: The source prefix for associated gesture identifiers. + @type source: basestring + @param gestureCls: The input gesture class. + @type gestureCls: L{InputGesture} + """ + gestureSources[source] = gestureCls + +def _getGestureClsForIdentifier(identifier): + """Get the registered gesture class for an identifier. + """ + source = identifier.split(":", 1)[0] + try: + return gestureSources[source] + except KeyError: + pass + genSource = source.split("(", 1)[0] + if genSource: + try: + return gestureSources[genSource] + except KeyError: + pass + raise LookupError("Gesture source not registered: %s" % source) + +def getDisplayTextForGestureIdentifier(identifier): + """Get the text to be presented to the user describing a given gesture identifier. + The display text consists of two strings: + the gesture's source (e.g. "laptop keyboard") + and the specific gesture (e.g. "alt+tab"). + @param identifier: The normalized gesture identifier in question. + @type identifier: basestring + @return: A tuple of (source, specificGesture). + @rtype: tuple of (basestring, basestring) + @raise LookupError: If no display text can be determined. + """ + gcls = _getGestureClsForIdentifier(identifier) + try: + return gcls.getDisplayTextForIdentifier(identifier) + except: + raise + raise LookupError("Couldn't get display text for identifier: %s" % identifier) + #: The singleton input manager instance. #: @type: L{InputManager} manager = None https://bitbucket.org/nvdaaddonteam/nvda/commits/99b58fbc54e6/ Changeset: 99b58fbc54e6 Branch: None User: jteh Date: 2013-09-05 08:15:02 Summary: Implement support for getting display text for keyboard gesture identifiers. Because this deals with normalized identifiers, all of the key names in the keyLabels module had to be converted to entirely lower case. Also, the generalized modifiers had to be added to KeyboardInputGesture.NORMAL_MODIFIER_KEYS so we have a way to tell they are modifiers. Affected #: 2 files diff --git a/source/keyLabels.py b/source/keyLabels.py index 082c35b..3675259 100644 --- a/source/keyLabels.py +++ b/source/keyLabels.py @@ -6,45 +6,45 @@ localizedKeyLabels = { # Translators: This is the name of the back key found on multimedia keyboards for controlling the web-browser. - 'browserBack': _("back"), + 'browserback': _("back"), # Translators: This is the name of the forward key found on multimedia keyboards for controlling the web-browser. - 'browserForward': _("forward"), + 'browserforward': _("forward"), # Translators: This is the name of the refresh key found on multimedia keyboards for controlling the web-browser. - 'browserRefresh': _("refresh"), + 'browserrefresh': _("refresh"), # Translators: This is the name of the stop key found on multimedia keyboards for controlling the web-browser. - 'browserStop': _("browser stop"), + 'browserstop': _("browser stop"), # Translators: This is the name of the back key found on multimedia keyboards to goto the search page of the web-browser. - 'browserSearch': _("search page"), + 'browsersearch': _("search page"), # Translators: This is the name of the favorites key found on multimedia keyboards to open favorites in the web-browser. - 'browserFavorites': _("favorites"), + 'browserfavorites': _("favorites"), # Translators: This is the name of the home key found on multimedia keyboards to goto the home page in the web-browser. - 'browserHome': _("home page"), + 'browserhome': _("home page"), # Translators: This is the name of the mute key found on multimedia keyboards to control playback volume. - 'volumeMute': _("mute"), + 'volumemute': _("mute"), # Translators: This is the name of the volume down key found on multimedia keyboards to reduce playback volume. - 'volumeDown': _("volume down"), + 'volumedown': _("volume down"), # Translators: This is the name of the volume up key found on multimedia keyboards to increase playback volume. - 'volumeUp': _("volume up"), + 'volumeup': _("volume up"), # Translators: This is the name of the next track key found on multimedia keyboards to skip to next track in the mediaplayer. - 'mediaNextTrack': _("next track"), + 'medianexttrack': _("next track"), # Translators: This is the name of the next track key found on multimedia keyboards to skip to next track in the mediaplayer. - 'mediaPrevTrack': _("previous track"), + 'mediaprevtrack': _("previous track"), # Translators: This is the name of the stop key found on multimedia keyboards to stop the current playing track in the mediaplayer. - 'mediaStop': _("stop"), + 'mediastop': _("stop"), # Translators: This is the name of the play/pause key found on multimedia keyboards to play/pause the current playing track in the mediaplayer. - 'mediaPlayPause': _("play pause"), + 'mediaplaypause': _("play pause"), # Translators: This is the name of the launch email key found on multimedia keyboards to open an email client. - 'launchMail': _("email"), + 'launchmail': _("email"), # Translators: This is the name of the launch mediaplayer key found on multimedia keyboards to launch the mediaplayer. - 'launchMediaPlayer': _("media player"), + 'launchmediaplayer': _("media player"), # Translators: This is the name of the launch custom application 1 key found on multimedia keyboards to launch a user-defined application. - 'launchApp1': _("custom application 1"), + 'launchapp1': _("custom application 1"), # Translators: This is the name of the launch custom application 2 key found on multimedia keyboards to launch a user-defined application. - 'launchApp2': _("custom application 2"), + 'launchapp2': _("custom application 2"), # Translators: This is the name of a key on the keyboard. 'backspace': _("backspace"), # Translators: This is the name of a key on the keyboard. - 'capsLock': _("caps lock"), + 'capslock': _("caps lock"), # Translators: This is the name of a key on the keyboard. 'control': _("ctrl"), # Translators: This is the name of a key on the keyboard. @@ -56,15 +56,15 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'enter': _("enter"), # Translators: This is the name of a key on the keyboard. - 'numpadEnter': _("numpad enter"), + 'numpadenter': _("numpad enter"), # Translators: This is the name of a key on the keyboard. 'escape': _("escape"), # Translators: This is the name of a key on the keyboard. 'space': _("space"), # Translators: This is the name of a key on the keyboard. - 'pageUp': _("page up"), + 'pageup': _("page up"), # Translators: This is the name of a key on the keyboard. - 'pageDown': _("page down"), + 'pagedown': _("page down"), # Translators: This is the name of a key on the keyboard. 'end': _("end"), # Translators: This is the name of a key on the keyboard. @@ -72,23 +72,23 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'delete': _("delete"), # Translators: This is the name of a key on the keyboard. - 'numpadDelete': _("numpad delete"), + 'numpaddelete': _("numpad delete"), # Translators: This is the name of a key on the keyboard. - 'leftArrow': _("left arrow"), + 'leftarrow': _("left arrow"), # Translators: This is the name of a key on the keyboard. - 'rightArrow': _("right arrow"), + 'rightarrow': _("right arrow"), # Translators: This is the name of a key on the keyboard. - 'upArrow': _("up arrow"), + 'uparrow': _("up arrow"), # Translators: This is the name of a key on the keyboard. - 'downArrow': _("down arrow"), + 'downarrow': _("down arrow"), # Translators: This is the name of a key on the keyboard. 'applications': _("applications"), # Translators: This is the name of a key on the keyboard. - 'numLock': _("num lock"), + 'numlock': _("num lock"), # Translators: This is the name of a key on the keyboard. - 'printScreen': _("print screen"), + 'printscreen': _("print screen"), # Translators: This is the name of a key on the keyboard. - 'scrollLock': _("scroll lock"), + 'scrolllock': _("scroll lock"), # Translators: This is the name of a key on the keyboard. 'numpad4': _("numpad 4"), # Translators: This is the name of a key on the keyboard. @@ -108,29 +108,29 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'numpad5': _("numpad 5"), # Translators: This is the name of a key on the keyboard. - 'numpadDivide': _("numpad divide"), + 'numpaddivide': _("numpad divide"), # Translators: This is the name of a key on the keyboard. - 'numpadMultiply': _("numpad multiply"), + 'numpadmultiply': _("numpad multiply"), # Translators: This is the name of a key on the keyboard. - 'numpadMinus': _("numpad minus"), + 'numpadminus': _("numpad minus"), # Translators: This is the name of a key on the keyboard. - 'numpadPlus': _("numpad plus"), + 'numpadplus': _("numpad plus"), # Translators: This is the name of a key on the keyboard. - 'leftControl': _("left control"), + 'leftcontrol': _("left control"), # Translators: This is the name of a key on the keyboard. - 'rightControl': _("right control"), + 'rightcontrol': _("right control"), # Translators: This is the name of a key on the keyboard. - 'leftWindows': _("left windows"), + 'leftwindows': _("left windows"), # Translators: This is the name of a key on the keyboard. - 'leftShift': _("left shift"), + 'leftshift': _("left shift"), # Translators: This is the name of a key on the keyboard. - 'rightShift': _("right shift"), + 'rightshift': _("right shift"), # Translators: This is the name of a key on the keyboard. - 'leftAlt': _("left alt"), + 'leftalt': _("left alt"), # Translators: This is the name of a key on the keyboard. - 'rightAlt': _("right alt"), + 'rightalt': _("right alt"), # Translators: This is the name of a key on the keyboard. - 'rightWindows': _("right windows"), + 'rightwindows': _("right windows"), # Translators: This is the name of a key on the keyboard. 'break': _("break"), # Translators: This is the name of a key on the keyboard. diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index 7ae3e9c..009e7a4 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -8,6 +8,7 @@ """Keyboard support""" import time +import re import wx import winUser import vkCodes @@ -221,12 +222,16 @@ class KeyboardInputGesture(inputCore.InputGesture): NORMAL_MODIFIER_KEYS = { winUser.VK_LCONTROL: winUser.VK_CONTROL, winUser.VK_RCONTROL: winUser.VK_CONTROL, + winUser.VK_CONTROL: None, winUser.VK_LSHIFT: winUser.VK_SHIFT, winUser.VK_RSHIFT: winUser.VK_SHIFT, + winUser.VK_SHIFT: None, winUser.VK_LMENU: winUser.VK_MENU, winUser.VK_RMENU: winUser.VK_MENU, + winUser.VK_MENU: None, winUser.VK_LWIN: VK_WIN, winUser.VK_RWIN: VK_WIN, + VK_WIN: None, } #: All possible toggle key vk codes. @@ -312,7 +317,7 @@ class KeyboardInputGesture(inputCore.InputGesture): key="+".join(self._keyNamesInDisplayOrder)) def _get_displayName(self): - return "+".join(localizedKeyLabels.get(key, key) for key in self._keyNamesInDisplayOrder) + return "+".join(localizedKeyLabels.get(key.lower(), key) for key in self._keyNamesInDisplayOrder) def _get_identifiers(self): keyNames = set(self.modifierNames) @@ -357,7 +362,7 @@ class KeyboardInputGesture(inputCore.InputGesture): toggleState = winUser.getKeyState(self.vkCode) & 1 key = self.mainKeyName ui.message(u"{key} {state}".format( - key=localizedKeyLabels.get(key, key), + key=localizedKeyLabels.get(key.lower(), key), state=_("on") if toggleState else _("off"))) def send(self): @@ -431,3 +436,44 @@ class KeyboardInputGesture(inputCore.InputGesture): raise ValueError return cls(keys[:-1], vk, 0, ext) + + RE_IDENTIFIER = re.compile(r"^kb(?:\((.+?)\))?:(.*)$") + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + layout, keys = cls.RE_IDENTIFIER.match(identifier).groups() + dispSource = None + if layout: + try: + # Translators: Used when describing keys on the system keyboard with a particular layout. + # %s is replaced with the layout name. + # For example, in English, this might produce "laptop keyboard". + dispSource = _("%s keyboard") % cls.LAYOUTS[layout] + except KeyError: + pass + if not dispSource: + # Translators: Used when describing keys on the system keyboard applying to all layouts. + dispSource = _("system keyboard") + + keys = set(keys.split("+")) + names = [] + main = None + try: + keys.remove("nvda") + names.append("NVDA") + except KeyError: + pass + for key in keys: + label = localizedKeyLabels.get(key, key) + vk = vkCodes.byName.get(key) + # vkCodes.byName values are (vk, ext) + if vk: + vk = vk[0] + if vk in cls.NORMAL_MODIFIER_KEYS: + names.append(label) + else: + # The main key must be last, so handle that outside the loop. + main = label + names.append(main) + return dispSource, "+".join(names) + +inputCore.registerGestureSource("kb", KeyboardInputGesture) https://bitbucket.org/nvdaaddonteam/nvda/commits/74c1df1c39f8/ Changeset: 74c1df1c39f8 Branch: None User: jteh Date: 2013-09-05 08:20:17 Summary: InputGesturesDialog: Use proper display text when displaying gestures if possible. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 6b5421b..a983ebb 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1531,7 +1531,7 @@ class InputGesturesDialog(SettingsDialog): commandInfo = commands[command] tree.SetItemPyData(treeCom, commandInfo) for gesture in commandInfo.gestures: - treeGes = tree.AppendItem(treeCom, gesture) + treeGes = tree.AppendItem(treeCom, self._formatGesture(gesture)) tree.SetItemPyData(treeGes, gesture) sizer = wx.BoxSizer(wx.HORIZONTAL) @@ -1552,6 +1552,16 @@ class InputGesturesDialog(SettingsDialog): def postInit(self): self.tree.SetFocus() + def _formatGesture(self, identifier): + try: + source, main = inputCore.getDisplayTextForGestureIdentifier(identifier) + # Translators: Describes a gesture in the Input Gestures dialog. + # {main} is replaced with the main part of the gesture; e.g. alt+tab. + # {source} is replaced with the gesture's source; e.g. laptop keyboard. + return _("{main} ({source})").format(main=main, source=source) + except LookupError: + return identifier + def onTreeSelect(self, evt): item = self.tree.Selection data = self.tree.GetItemPyData(item) @@ -1583,9 +1593,9 @@ class InputGesturesDialog(SettingsDialog): inputCore.manager._captureFunc = addGestureCaptor def _finishAdd(self, treeGes, scriptInfo, gesture): - gestureId = gesture.logIdentifier + gestureId = gesture.identifiers[0] self.pendingAdds.add((gestureId, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) - self.tree.SetItemText(treeGes, gesture.logIdentifier) + self.tree.SetItemText(treeGes, self._formatGesture(gestureId)) self.tree.SetItemPyData(treeGes, gestureId) self.onTreeSelect(None) https://bitbucket.org/nvdaaddonteam/nvda/commits/e8e103523542/ Changeset: e8e103523542 Branch: None User: jteh Date: 2013-09-05 08:26:59 Summary: Add comment. Affected #: 1 file diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index 009e7a4..9741fd7 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -458,6 +458,7 @@ class KeyboardInputGesture(inputCore.InputGesture): names = [] main = None try: + # If present, the NVDA key should appear first. keys.remove("nvda") names.append("NVDA") except KeyError: https://bitbucket.org/nvdaaddonteam/nvda/commits/e659d79e0cb5/ Changeset: e659d79e0cb5 Branch: None User: jteh Date: 2013-09-05 08:53:29 Summary: BrailleDisplayGesture: Base implementation of getDisplayTextForIdentifier. Obviously, this can't make the specific part of the gesture friendlier, but it does use the braille display driver's description for the source portion. Affected #: 1 file diff --git a/source/braille.py b/source/braille.py index c798aa8..d5a5764 100644 --- a/source/braille.py +++ b/source/braille.py @@ -1611,3 +1611,9 @@ class BrailleDisplayGesture(inputCore.InputGesture): if isinstance(display, baseObject.ScriptableObject): return display return super(BrailleDisplayGesture, self).scriptableObject + + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + return handler.display.description, identifier.split(":", 1)[1] + +inputCore.registerGestureSource("br", BrailleDisplayGesture) https://bitbucket.org/nvdaaddonteam/nvda/commits/7dbe5acab31b/ Changeset: 7dbe5acab31b Branch: None User: pzajda Date: 2013-09-05 12:42:03 Summary: The NVDA service now displays a description in the service manager. Affected #: 1 file diff --git a/source/nvda_service.py b/source/nvda_service.py index 1c5297f..5f3a389 100644 --- a/source/nvda_service.py +++ b/source/nvda_service.py @@ -327,7 +327,9 @@ def installService(nvdaDir): servicePath = os.path.join(nvdaDir, __name__ + ".exe") if not os.path.isfile(servicePath): raise RuntimeError("Could not find service executable") - win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath) + win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath, + # Translators: The NVDA service description + description=_(u"Allows NVDA to run on the Windows Logon screen, UAC screen and other secure screens.")) def removeService(): win32serviceutil.RemoveService(NVDAService._svc_name_) https://bitbucket.org/nvdaaddonteam/nvda/commits/84a743379686/ Changeset: 84a743379686 Branch: None User: jteh Date: 2013-09-05 12:46:30 Summary: InputGesturesDialog: When adding a gesture, allow the user to choose if there are multiple identifiers; e.g. with and without keyboard layout. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index a983ebb..d96fd29 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1588,15 +1588,35 @@ class InputGesturesDialog(SettingsDialog): if gesture.isModifier: return False inputCore.manager._captureFunc = None - wx.CallAfter(self._finishAdd, treeGes, scriptInfo, gesture) + wx.CallAfter(self._addCaptured, treeGes, scriptInfo, gesture) return False inputCore.manager._captureFunc = addGestureCaptor - def _finishAdd(self, treeGes, scriptInfo, gesture): - gestureId = gesture.identifiers[0] - self.pendingAdds.add((gestureId, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) - self.tree.SetItemText(treeGes, self._formatGesture(gestureId)) - self.tree.SetItemPyData(treeGes, gestureId) + def _addCaptured(self, treeGes, scriptInfo, gesture): + gids = gesture.identifiers + if len(gids) > 1: + # Multiple choices. Present them in a pop-up menu. + menu = wx.Menu() + for gid in gids: + disp = self._formatGesture(gid) + item = menu.Append(wx.ID_ANY, disp) + self.Bind(wx.EVT_MENU, + lambda evt: self._addChoice(treeGes, scriptInfo, gesture, gid, disp), + item) + self.PopupMenu(menu) + if not self.tree.GetItemPyData(treeGes): + # No item was selected, so use the first. + self._addChoice(treeGes, scriptInfo, gesture, gids[0], + self._formatGesture(gids[0])) + menu.Destroy() + else: + self._addChoice(treeGes, scriptInfo, gesture, gids[0], + self._formatGesture(gids[0])) + + def _addChoice(self, treeGes, scriptInfo, gesture, gid, disp): + self.pendingAdds.add((gid, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) + self.tree.SetItemText(treeGes, disp) + self.tree.SetItemPyData(treeGes, gid) self.onTreeSelect(None) def onRemove(self, evt): https://bitbucket.org/nvdaaddonteam/nvda/commits/24f3850891b2/ Changeset: 24f3850891b2 Branch: None User: jteh Date: 2013-09-05 12:49:03 Summary: KeyboardInputGesture: Change display source for no layout to "keyboard, all layouts", which is longer but clearer. Affected #: 1 file diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index 9741fd7..20709ad 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -452,7 +452,7 @@ class KeyboardInputGesture(inputCore.InputGesture): pass if not dispSource: # Translators: Used when describing keys on the system keyboard applying to all layouts. - dispSource = _("system keyboard") + dispSource = _("keyboard, all layouts") keys = set(keys.split("+")) names = [] https://bitbucket.org/nvdaaddonteam/nvda/commits/f6e8360ef1ea/ Changeset: f6e8360ef1ea Branch: None User: jteh Date: 2013-09-05 13:28:20 Summary: Don't report all context menus in NVDA's GUI as if they were the NVDA menu itself. Re #3503. Affected #: 2 files diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 32d7dad..5d7a9ba 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -1,6 +1,6 @@ #appModules/nvda.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2008-2011 NV Access Inc +#Copyright (C) 2008-2013 NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -8,13 +8,14 @@ import appModuleHandler import api import controlTypes import versionInfo +import gui class AppModule(appModuleHandler.AppModule): def event_NVDAObject_init(self, obj): # It seems that context menus always get the name "context" and this cannot be overridden. # Fudge the name of the NVDA system tray menu to make it more friendly. - if obj.role == controlTypes.ROLE_POPUPMENU: + if gui.mainFrame.sysTrayIcon.isMenuOpen and obj.role == controlTypes.ROLE_POPUPMENU: parent = obj.parent if parent and parent.parent==api.getDesktopObject(): obj.name=versionInfo.name diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 6428d32..9b54763 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -426,6 +426,9 @@ class SysTrayIcon(wx.TaskBarIcon): item = self.menu.Append(wx.ID_EXIT, _("E&xit"),_("Exit NVDA")) self.Bind(wx.EVT_MENU, frame.onExitCommand, item) + # We need to know if an item gets activated before its real code runs. + self.menu.Bind(wx.EVT_MENU, self.onMenuItem) + self.isMenuOpen = False self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.onActivate) def Destroy(self): @@ -434,9 +437,16 @@ class SysTrayIcon(wx.TaskBarIcon): def onActivate(self, evt): mainFrame.prePopup() + self.isMenuOpen = True self.PopupMenu(self.menu) + self.isMenuOpen = False mainFrame.postPopup() + def onMenuItem(self, evt): + # An item has been activated, so the menu has been closed. + self.isMenuOpen = False + evt.Skip() + def initialize(): global mainFrame mainFrame = MainFrame() https://bitbucket.org/nvdaaddonteam/nvda/commits/3bacefffc160/ Changeset: 3bacefffc160 Branch: None User: jteh Date: 2013-09-05 13:32:03 Summary: Merge branch 't3503' into next Incubates #3503. Affected #: 2 files diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 32d7dad..5d7a9ba 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -1,6 +1,6 @@ #appModules/nvda.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2008-2011 NV Access Inc +#Copyright (C) 2008-2013 NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -8,13 +8,14 @@ import appModuleHandler import api import controlTypes import versionInfo +import gui class AppModule(appModuleHandler.AppModule): def event_NVDAObject_init(self, obj): # It seems that context menus always get the name "context" and this cannot be overridden. # Fudge the name of the NVDA system tray menu to make it more friendly. - if obj.role == controlTypes.ROLE_POPUPMENU: + if gui.mainFrame.sysTrayIcon.isMenuOpen and obj.role == controlTypes.ROLE_POPUPMENU: parent = obj.parent if parent and parent.parent==api.getDesktopObject(): obj.name=versionInfo.name diff --git a/source/gui/__init__.py b/source/gui/__init__.py index bdd02c9..9bb778e 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -438,6 +438,9 @@ class SysTrayIcon(wx.TaskBarIcon): item = self.menu.Append(wx.ID_EXIT, _("E&xit"),_("Exit NVDA")) self.Bind(wx.EVT_MENU, frame.onExitCommand, item) + # We need to know if an item gets activated before its real code runs. + self.menu.Bind(wx.EVT_MENU, self.onMenuItem) + self.isMenuOpen = False self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.onActivate) def Destroy(self): @@ -446,9 +449,16 @@ class SysTrayIcon(wx.TaskBarIcon): def onActivate(self, evt): mainFrame.prePopup() + self.isMenuOpen = True self.PopupMenu(self.menu) + self.isMenuOpen = False mainFrame.postPopup() + def onMenuItem(self, evt): + # An item has been activated, so the menu has been closed. + self.isMenuOpen = False + evt.Skip() + def initialize(): global mainFrame mainFrame = MainFrame() https://bitbucket.org/nvdaaddonteam/nvda/commits/f3604ab4f4ee/ Changeset: f3604ab4f4ee Branch: None User: jteh Date: 2013-09-05 15:22:39 Summary: User Guide: Add documentation for the Input Gestures dialog. Affected #: 1 file diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 3e1b68d..87db83c 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1056,6 +1056,26 @@ Using the Level field, you can adjust the lowest symbol level at which this symb When you are finished, press the OK button to save your changes or the Cancel button to discard them. ++++ Input Gestures (NVDA+control+i) +++ +In this dialog, you can customize the input gestures (keys on the keyboard, buttons on a braille display, etc.) for NVDA commands. + +Only commands that are applicable immediately before the dialog is opened are shown. +Therefore, you should generally open this dialog using its key command so that all commands applicable to what you were just doing are shown. +For example, if you want to customize commands related to browse mode, you should press the key command to open the Input Gestures dialog while you are in browse mode. + +The tree in this dialog lists all of the applicable NVDA commands grouped by category. +Any gestures associated with a command are listed beneath the command. + +To add an input gesture to a command, select the command and press the Add button. +Then, perform the input gesture you wish to associate; e.g. press a key on the keyboard or a button on a braille display. +Often, a gesture can be interpreted in more than one way. +For example, if you pressed a key on the keyboard, you may wish it to be specific to the current keyboard layout (e.g. desktop or laptop) or you may wish it to apply for all layouts. +In this case, a menu will appear allowing you to select the desired option. + +To remove a gesture from a command, select the gesture and press the Remove button. + +When you are finished making changes, press the OK button to save them or the Cancel button to discard them. + ++ Saving and Reloading the configuration ++ By default NVDA will automatically save your settings on exit. Note, however, that this option can be changed under the general options in the preferences menu. https://bitbucket.org/nvdaaddonteam/nvda/commits/7b244e703b18/ Changeset: 7b244e703b18 Branch: None User: jteh Date: 2013-09-06 11:20:28 Summary: nvda_service: cosmetic: Minor tweak to translators comment for service and fix indentation. Affected #: 1 file diff --git a/source/nvda_service.py b/source/nvda_service.py index 5f3a389..4f75a2c 100644 --- a/source/nvda_service.py +++ b/source/nvda_service.py @@ -328,8 +328,8 @@ def installService(nvdaDir): if not os.path.isfile(servicePath): raise RuntimeError("Could not find service executable") win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath, - # Translators: The NVDA service description - description=_(u"Allows NVDA to run on the Windows Logon screen, UAC screen and other secure screens.")) + # Translators: The description of the NVDA service. + description=_(u"Allows NVDA to run on the Windows Logon screen, UAC screen and other secure screens.")) def removeService(): win32serviceutil.RemoveService(NVDAService._svc_name_) https://bitbucket.org/nvdaaddonteam/nvda/commits/63675818548d/ Changeset: 63675818548d Branch: None User: jteh Date: 2013-09-06 12:33:27 Summary: Merge branch 't1213' into next Incubates #1213. Affected #: 1 file diff --git a/source/nvda_service.py b/source/nvda_service.py index 1c5297f..4f75a2c 100644 --- a/source/nvda_service.py +++ b/source/nvda_service.py @@ -327,7 +327,9 @@ def installService(nvdaDir): servicePath = os.path.join(nvdaDir, __name__ + ".exe") if not os.path.isfile(servicePath): raise RuntimeError("Could not find service executable") - win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath) + win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath, + # Translators: The description of the NVDA service. + description=_(u"Allows NVDA to run on the Windows Logon screen, UAC screen and other secure screens.")) def removeService(): win32serviceutil.RemoveService(NVDAService._svc_name_) https://bitbucket.org/nvdaaddonteam/nvda/commits/c298ecb4c0a9/ Changeset: c298ecb4c0a9 Branch: None User: jteh Date: 2013-09-09 10:37:35 Summary: Revert "Don't report all context menus in NVDA's GUI as if they were the NVDA menu itself." This implementation could break in some rare cases if a plugin bound a modal dialog to a menu item, as this wouldn't get intercepted by the event which sets isNVDAMenuOpen to False. This reverts commit f6e8360ef1ea30d6b9216b5abcdf40db1549f6e0. Re #3503. Affected #: 2 files diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 5d7a9ba..32d7dad 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -1,6 +1,6 @@ #appModules/nvda.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2008-2013 NV Access Limited +#Copyright (C) 2008-2011 NV Access Inc #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -8,14 +8,13 @@ import appModuleHandler import api import controlTypes import versionInfo -import gui class AppModule(appModuleHandler.AppModule): def event_NVDAObject_init(self, obj): # It seems that context menus always get the name "context" and this cannot be overridden. # Fudge the name of the NVDA system tray menu to make it more friendly. - if gui.mainFrame.sysTrayIcon.isMenuOpen and obj.role == controlTypes.ROLE_POPUPMENU: + if obj.role == controlTypes.ROLE_POPUPMENU: parent = obj.parent if parent and parent.parent==api.getDesktopObject(): obj.name=versionInfo.name diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 9b54763..6428d32 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -426,9 +426,6 @@ class SysTrayIcon(wx.TaskBarIcon): item = self.menu.Append(wx.ID_EXIT, _("E&xit"),_("Exit NVDA")) self.Bind(wx.EVT_MENU, frame.onExitCommand, item) - # We need to know if an item gets activated before its real code runs. - self.menu.Bind(wx.EVT_MENU, self.onMenuItem) - self.isMenuOpen = False self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.onActivate) def Destroy(self): @@ -437,16 +434,9 @@ class SysTrayIcon(wx.TaskBarIcon): def onActivate(self, evt): mainFrame.prePopup() - self.isMenuOpen = True self.PopupMenu(self.menu) - self.isMenuOpen = False mainFrame.postPopup() - def onMenuItem(self, evt): - # An item has been activated, so the menu has been closed. - self.isMenuOpen = False - evt.Skip() - def initialize(): global mainFrame mainFrame = MainFrame() https://bitbucket.org/nvdaaddonteam/nvda/commits/fc38daace9cc/ Changeset: fc38daace9cc Branch: None User: jteh Date: 2013-09-09 10:43:26 Summary: IAccessibleHandler.getIAccIdentity: Support HMENU identity strings using AccPropServices. Affected #: 1 file diff --git a/source/IAccessibleHandler.py b/source/IAccessibleHandler.py index 94db85d..200be58 100644 --- a/source/IAccessibleHandler.py +++ b/source/IAccessibleHandler.py @@ -870,8 +870,14 @@ def getIAccIdentity(pacc,childID): stringPtr,stringSize=IAccIdentityObject.getIdentityString(childID) try: if accPropServices: - hwnd,objectID,childID=accPropServices.DecomposeHwndIdentityString(stringPtr,stringSize) - return dict(windowHandle=hwnd,objectID=c_int(objectID).value,childID=childID) + try: + hwnd,objectID,childID=accPropServices.DecomposeHwndIdentityString(stringPtr,stringSize) + return dict(windowHandle=hwnd,objectID=c_int(objectID).value,childID=childID) + except COMError: + hmenu,childID=accPropServices.DecomposeHmenuIdentityString(stringPtr,stringSize) + # hmenu is a wireHMENU, but it seems we can just treat this as a number. + # comtypes transparently does this for wireHWND. + return dict(menuHandle=cast(hmenu,wintypes.HMENU).value,childID=childID) stringPtr=cast(stringPtr,POINTER(c_char*stringSize)) fields=struct.unpack('IIiI',stringPtr.contents.raw) d={} https://bitbucket.org/nvdaaddonteam/nvda/commits/297d98ae700c/ Changeset: 297d98ae700c Branch: None User: jteh Date: 2013-09-09 10:43:52 Summary: Don't report all context menus in NVDA's GUI as if they were the NVDA menu itself. The NVDA menu is identified by its IAccessible identity. To get this, the GUI sets a flag in the nvda app module the first time the NVDA menu is opened so that the NVDA app module knows that the next menu it encounters is the NVDA menu. Re #3503. Affected #: 2 files diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 32d7dad..0fadb65 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -9,15 +9,27 @@ import api import controlTypes import versionInfo +nvdaMenuIaIdentity = None + class AppModule(appModuleHandler.AppModule): + def isNvdaMenu(self, obj): + global nvdaMenuIaIdentity + if obj.IAccessibleIdentity == nvdaMenuIaIdentity: + return True + if nvdaMenuIaIdentity is not True: + return False + # nvdaMenuIaIdentity is True, so the next menu we encounter is the NVDA menu. + if obj.role == controlTypes.ROLE_POPUPMENU: + nvdaMenuIaIdentity = obj.IAccessibleIdentity + return True + return False + def event_NVDAObject_init(self, obj): # It seems that context menus always get the name "context" and this cannot be overridden. # Fudge the name of the NVDA system tray menu to make it more friendly. - if obj.role == controlTypes.ROLE_POPUPMENU: - parent = obj.parent - if parent and parent.parent==api.getDesktopObject(): - obj.name=versionInfo.name + if self.isNvdaMenu(obj): + obj.name=versionInfo.name def event_gainFocus(self, obj, nextHandler): if obj.role == controlTypes.ROLE_UNKNOWN and controlTypes.STATE_INVISIBLE in obj.states: diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 6428d32..7dfa54a 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -434,7 +434,15 @@ class SysTrayIcon(wx.TaskBarIcon): def onActivate(self, evt): mainFrame.prePopup() + import appModules.nvda + if not appModules.nvda.nvdaMenuIaIdentity: + # The NVDA app module doesn't know how to identify the NVDA menu yet. + # Signal that the NVDA menu has just been opened. + appModules.nvda.nvdaMenuIaIdentity = True self.PopupMenu(self.menu) + if appModules.nvda.nvdaMenuIaIdentity is True: + # The NVDA menu didn't actually appear for some reason. + appModules.nvda.nvdaMenuIaIdentity = None mainFrame.postPopup() def initialize(): https://bitbucket.org/nvdaaddonteam/nvda/commits/988a466a3e9e/ Changeset: 988a466a3e9e Branch: None User: jteh Date: 2013-09-09 11:21:19 Summary: Merge branch 't3503' into next Incubates #3503. Affected #: 3 files diff --git a/source/IAccessibleHandler.py b/source/IAccessibleHandler.py index 94db85d..200be58 100644 --- a/source/IAccessibleHandler.py +++ b/source/IAccessibleHandler.py @@ -870,8 +870,14 @@ def getIAccIdentity(pacc,childID): stringPtr,stringSize=IAccIdentityObject.getIdentityString(childID) try: if accPropServices: - hwnd,objectID,childID=accPropServices.DecomposeHwndIdentityString(stringPtr,stringSize) - return dict(windowHandle=hwnd,objectID=c_int(objectID).value,childID=childID) + try: + hwnd,objectID,childID=accPropServices.DecomposeHwndIdentityString(stringPtr,stringSize) + return dict(windowHandle=hwnd,objectID=c_int(objectID).value,childID=childID) + except COMError: + hmenu,childID=accPropServices.DecomposeHmenuIdentityString(stringPtr,stringSize) + # hmenu is a wireHMENU, but it seems we can just treat this as a number. + # comtypes transparently does this for wireHWND. + return dict(menuHandle=cast(hmenu,wintypes.HMENU).value,childID=childID) stringPtr=cast(stringPtr,POINTER(c_char*stringSize)) fields=struct.unpack('IIiI',stringPtr.contents.raw) d={} diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 5d7a9ba..0fadb65 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -1,6 +1,6 @@ #appModules/nvda.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2008-2013 NV Access Limited +#Copyright (C) 2008-2011 NV Access Inc #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -8,17 +8,28 @@ import appModuleHandler import api import controlTypes import versionInfo -import gui + +nvdaMenuIaIdentity = None class AppModule(appModuleHandler.AppModule): + def isNvdaMenu(self, obj): + global nvdaMenuIaIdentity + if obj.IAccessibleIdentity == nvdaMenuIaIdentity: + return True + if nvdaMenuIaIdentity is not True: + return False + # nvdaMenuIaIdentity is True, so the next menu we encounter is the NVDA menu. + if obj.role == controlTypes.ROLE_POPUPMENU: + nvdaMenuIaIdentity = obj.IAccessibleIdentity + return True + return False + def event_NVDAObject_init(self, obj): # It seems that context menus always get the name "context" and this cannot be overridden. # Fudge the name of the NVDA system tray menu to make it more friendly. - if gui.mainFrame.sysTrayIcon.isMenuOpen and obj.role == controlTypes.ROLE_POPUPMENU: - parent = obj.parent - if parent and parent.parent==api.getDesktopObject(): - obj.name=versionInfo.name + if self.isNvdaMenu(obj): + obj.name=versionInfo.name def event_gainFocus(self, obj, nextHandler): if obj.role == controlTypes.ROLE_UNKNOWN and controlTypes.STATE_INVISIBLE in obj.states: diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 9bb778e..94e7881 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -438,9 +438,6 @@ class SysTrayIcon(wx.TaskBarIcon): item = self.menu.Append(wx.ID_EXIT, _("E&xit"),_("Exit NVDA")) self.Bind(wx.EVT_MENU, frame.onExitCommand, item) - # We need to know if an item gets activated before its real code runs. - self.menu.Bind(wx.EVT_MENU, self.onMenuItem) - self.isMenuOpen = False self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.onActivate) def Destroy(self): @@ -449,16 +446,17 @@ class SysTrayIcon(wx.TaskBarIcon): def onActivate(self, evt): mainFrame.prePopup() - self.isMenuOpen = True + import appModules.nvda + if not appModules.nvda.nvdaMenuIaIdentity: + # The NVDA app module doesn't know how to identify the NVDA menu yet. + # Signal that the NVDA menu has just been opened. + appModules.nvda.nvdaMenuIaIdentity = True self.PopupMenu(self.menu) - self.isMenuOpen = False + if appModules.nvda.nvdaMenuIaIdentity is True: + # The NVDA menu didn't actually appear for some reason. + appModules.nvda.nvdaMenuIaIdentity = None mainFrame.postPopup() - def onMenuItem(self, evt): - # An item has been activated, so the menu has been closed. - self.isMenuOpen = False - evt.Skip() - def initialize(): global mainFrame mainFrame = MainFrame() https://bitbucket.org/nvdaaddonteam/nvda/commits/e4481e4b0bbe/ Changeset: e4481e4b0bbe Branch: None User: jteh Date: 2013-09-09 11:26:00 Summary: User Guide: Remove the Remapping Key Assignments and Other Input Gestures advanced section of the User Guide, as this functionality is covered by the Input Gestures dialog. Affected #: 1 file diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 87db83c..94c5221 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1785,79 +1785,6 @@ Please see the [BRLTTY key tables documentation http://mielke.cc/brltty/doc/driv + Advanced Topics + -++ Remapping Key Assignments and Other Input Gestures ++ -Users are able to provide or override mappings of input gestures (such as key presses) to scripts in a special file in the user's NVDA configuration directory. -This file is called gestures.ini. - -This file uses standard ini syntax. -The file may contain multiple sections and each section may have one or more entries. - -Each section provides mappings for scripts in a particular Python module and class inside NVDA. -- The section name should be the Python module and class separated by a dot (.). -- The key of each entry is the name of the script to which input gestures should be bound. -Alternatively, you can use None to unbind input gestures from a script to which they were previously bound. -Each entry key can only be listed once per section, including None. -- The entry value is a comma (,) separated list of gesture identifiers for the input gestures that should be bound. -Gesture identifiers ending in a comma must be enclosed in quotes (" or '). -- - -Gesture identifiers consist of a two letter device code; a sub-device, layout or mode in brackets; a colon; and then a type-specific string identifying the actual input, such as key or touch gesture names. -- For keyboard gestures, the device code is kb. -The part in brackets is the keyboard layout and is optional. -If not specified, the gesture will apply to all keyboard layouts. -The string after the colon is one or more key names separated by a plus (+) sign. -- For braille display gestures, the device code is br. -The part in brackets identifies the specific braille display and is mandatory. -The string after the colon is one or more key names separated by a plus (+) sign. -- For touch gestures, the device code is ts. -The part in brackets is the touch mode and is optional. -The string after the colon is a touch gesture; e.g. double_tap, 2fingerflickUp or 3finger_tap. -- - -In order to discover gesture identifiers, script names and the class and module in which they are contained, you can: -+ Turn on Input Help. -+ Activate the gesture (press the key, touch the screen, etc.). -+ Turn off input help. -+ Activate View log in the NVDA Tools menu. -+ Examine the recent log entries. -One of these should provide information about the input gesture you sent, including the module.class and script if it is bound to one. -+ - -Following is an example of how you could bind NVDA+shift+t to the date time script. - -To find out the correct script name and module.class for date time, you would turn on Input Help and press NVDA+f12 (as this is the current gesture for the date time script). -You would then turn off Input Help and examine the log viewer. - -Towards the bottom, you would see: - -``` -INFO - inputCore.InputManager._handleInputHelp (13:17:22): -Input help: gesture kb(desktop):NVDA+f12, bound to script dateTime on globalCommands.GlobalCommands -``` - -From this, you can see that the script name is dateTime and the module.class is globalCommands.GlobalCommands. - -If the file does not yet exist, you would create a text file called gestures.ini in the user configuration directory and add the following content: - -``` -[globalCommands.GlobalCommands] - dateTime = kb:NVDA+shift+t -``` - -This would bind the key press NVDA+shift+t (in any keyboard layout) to the dateTime script. - -Note that the original NVDA+f12 binding would still work. -If you wanted to remove this binding, you would add the following line: - -``` - None = kb:NVDA+f12 -``` - -Although you are free to have scripts bound to any available key, it may be problematic to use the alt key on the keyboard. -NVDA still sends modifier keys (such as shift, control and alt) to the Operating System, even if they eventuate in a script. -Thus, if you do use alt in a gesture, pressing this key combination may activate the menu bar, as well as executing the script. -Therefore, it is probably best to just use Shift, control and the NVDA modifier key as modifiers. - ++ Advanced Customization of Symbol Pronunciation ++ It is possible to customize the pronunciation of punctuation and other symbols beyond what can be done using the [Punctuation/symbol pronunciation #SymbolPronunciation] dialog. For example, you can specify whether the raw symbol should be sent to the synthesizer (e.g. to cause a pause or change in inflection) and you can add custom symbols. https://bitbucket.org/nvdaaddonteam/nvda/commits/d4e14a86bff9/ Changeset: d4e14a86bff9 Branch: None User: jteh Date: 2013-09-09 11:28:20 Summary: Merge branch 't1532' into next Incubates #1532. Affected #: 9 files diff --git a/source/braille.py b/source/braille.py index 3bb7e4c..f65e311 100644 --- a/source/braille.py +++ b/source/braille.py @@ -1616,3 +1616,9 @@ class BrailleDisplayGesture(inputCore.InputGesture): if isinstance(display, baseObject.ScriptableObject): return display return super(BrailleDisplayGesture, self).scriptableObject + + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + return handler.display.description, identifier.split(":", 1)[1] + +inputCore.registerGestureSource("br", BrailleDisplayGesture) diff --git a/source/cursorManager.py b/source/cursorManager.py index 9ae3b89..74c2f51 100644 --- a/source/cursorManager.py +++ b/source/cursorManager.py @@ -18,6 +18,7 @@ import speech import config import braille import controlTypes +from inputCore import SCRCAT_BROWSEMODE class CursorManager(baseObject.ScriptableObject): """ @@ -32,6 +33,9 @@ class CursorManager(baseObject.ScriptableObject): @type selection: L{textInfos.TextInfo} """ + # Translators: the script category for browse mode + scriptCategory=SCRCAT_BROWSEMODE + _lastFindText="" def __init__(self, *args, **kwargs): diff --git a/source/globalCommands.py b/source/globalCommands.py index 1420983..3b9b975 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -37,6 +37,43 @@ import virtualBuffers import characterProcessing from baseObject import ScriptableObject +#: Script category for text review commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TEXTREVIEW = _("Text review") +#: Script category for Object navigation commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_OBJECTNAVIGATION = _("Object navigation") +#: Script category for system caret commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEMCARET = _("System caret") +#: Script category for mouse commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_MOUSE = _("Mouse") +#: Script category for mouse commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SPEECH = _("Speech") +#: Script category for configuration dialogs commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_CONFIG = _("Configuration") +#: Script category for Braille commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BRAILLE = _("Braille") +#: Script category for tools commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOOLS = _("Tools") +#: Script category for touch commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOUCH = _("Touch screen") +#: Script category for focus commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_FOCUS = _("System focus") +#: Script category for system status commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEM = _("System status") +#: Script category for input commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_INPUT = _("Input") + class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. """ @@ -51,6 +88,8 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle input help command. script_toggleInputHelp.__doc__=_("Turns input help on or off. When on, any input such as pressing a key on the keyboard will tell you what script is associated with that input, if any.") + script_toggleInputHelp.category=SCRCAT_INPUT + def script_toggleCurrentAppSleepMode(self,gesture): curFocus=api.getFocusObject() @@ -85,6 +124,7 @@ class GlobalCommands(ScriptableObject): speech.speakSpelling(info.text) # Translators: Input help mode message for report current line command. script_reportCurrentLine.__doc__=_("Reports the current line under the application cursor. Pressing this key twice will spell the current line") + script_reportCurrentLine.category=SCRCAT_SYSTEMCARET def script_leftMouseClick(self,gesture): # Translators: Reported when left mouse button is clicked. @@ -93,6 +133,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_LEFTUP,0,0,None,None) # Translators: Input help mode message for left mouse click command. script_leftMouseClick.__doc__=_("Clicks the left mouse button once at the current mouse position") + script_leftMouseClick.category=SCRCAT_MOUSE def script_rightMouseClick(self,gesture): # Translators: Reported when right mouse button is clicked. @@ -101,6 +142,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_RIGHTUP,0,0,None,None) # Translators: Input help mode message for right mouse click command. script_rightMouseClick.__doc__=_("Clicks the right mouse button once at the current mouse position") + script_rightMouseClick.category=SCRCAT_MOUSE def script_toggleLeftMouseButton(self,gesture): if winUser.getKeyState(winUser.VK_LBUTTON)&32768: @@ -113,6 +155,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_LEFTDOWN,0,0,None,None) # Translators: Input help mode message for left mouse lock/unlock toggle command. script_toggleLeftMouseButton.__doc__=_("Locks or unlocks the left mouse button") + script_toggleLeftMouseButton.category=SCRCAT_MOUSE def script_toggleRightMouseButton(self,gesture): if winUser.getKeyState(winUser.VK_RBUTTON)&32768: @@ -125,6 +168,7 @@ class GlobalCommands(ScriptableObject): winUser.mouse_event(winUser.MOUSEEVENTF_RIGHTDOWN,0,0,None,None) # Translators: Input help mode message for right mouse lock/unlock command. script_toggleRightMouseButton.__doc__=_("Locks or unlocks the right mouse button") + script_toggleRightMouseButton.category=SCRCAT_MOUSE def script_reportCurrentSelection(self,gesture): obj=api.getFocusObject() @@ -141,6 +185,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("selected %s")%info.text) # Translators: Input help mode message for report current selection command. script_reportCurrentSelection.__doc__=_("Announces the current selection in edit controls and documents. If there is no selection it says so.") + script_reportCurrentSelection.category=SCRCAT_SYSTEMCARET def script_dateTime(self,gesture): if scriptHandler.getLastScriptRepeatCount()==0: @@ -150,6 +195,7 @@ class GlobalCommands(ScriptableObject): ui.message(text) # Translators: Input help mode message for report date and time command. script_dateTime.__doc__=_("If pressed once, reports the current time. If pressed twice, reports the current date") + script_dateTime.category=SCRCAT_SYSTEM def script_increaseSynthSetting(self,gesture): settingName=globalVars.settingsRing.currentSettingName @@ -161,6 +207,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s" % (settingName,settingValue)) # Translators: Input help mode message for increase synth setting value command. script_increaseSynthSetting.__doc__=_("Increases the currently active setting in the synth settings ring") + script_increaseSynthSetting.category=SCRCAT_SPEECH def script_decreaseSynthSetting(self,gesture): settingName=globalVars.settingsRing.currentSettingName @@ -171,6 +218,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s" % (settingName,settingValue)) # Translators: Input help mode message for decrease synth setting value command. script_decreaseSynthSetting.__doc__=_("Decreases the currently active setting in the synth settings ring") + script_decreaseSynthSetting.category=SCRCAT_SPEECH def script_nextSynthSetting(self,gesture): nextSettingName=globalVars.settingsRing.next() @@ -181,6 +229,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s"%(nextSettingName,nextSettingValue)) # Translators: Input help mode message for next synth setting command. script_nextSynthSetting.__doc__=_("Moves to the next available setting in the synth settings ring") + script_nextSynthSetting.category=SCRCAT_SPEECH def script_previousSynthSetting(self,gesture): previousSettingName=globalVars.settingsRing.previous() @@ -191,6 +240,7 @@ class GlobalCommands(ScriptableObject): ui.message("%s %s"%(previousSettingName,previousSettingValue)) # Translators: Input help mode message for previous synth setting command. script_previousSynthSetting.__doc__=_("Moves to the previous available setting in the synth settings ring") + script_previousSynthSetting.category=SCRCAT_SPEECH def script_toggleSpeakTypedCharacters(self,gesture): if config.conf["keyboard"]["speakTypedCharacters"]: @@ -204,6 +254,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speaked typed characters command. script_toggleSpeakTypedCharacters.__doc__=_("Toggles on and off the speaking of typed characters") + script_toggleSpeakTypedCharacters.category=SCRCAT_SPEECH def script_toggleSpeakTypedWords(self,gesture): if config.conf["keyboard"]["speakTypedWords"]: @@ -217,6 +268,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speak typed words command. script_toggleSpeakTypedWords.__doc__=_("Toggles on and off the speaking of typed words") + script_toggleSpeakTypedWords.category=SCRCAT_SPEECH def script_toggleSpeakCommandKeys(self,gesture): if config.conf["keyboard"]["speakCommandKeys"]: @@ -230,6 +282,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle speak command keys command. script_toggleSpeakCommandKeys.__doc__=_("Toggles on and off the speaking of typed keys, that are not specifically characters") + script_toggleSpeakCommandKeys.category=SCRCAT_SPEECH def script_cycleSpeechSymbolLevel(self,gesture): curLevel = config.conf["speech"]["symbolLevel"] @@ -246,6 +299,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("symbol level %s") % name) # Translators: Input help mode message for cycle speech symbol level command. script_cycleSpeechSymbolLevel.__doc__=_("Cycles through speech symbol levels which determine what symbols are spoken") + script_cycleSpeechSymbolLevel.category=SCRCAT_SPEECH def script_moveMouseToNavigatorObject(self,gesture): obj=api.getNavigatorObject() @@ -269,6 +323,7 @@ class GlobalCommands(ScriptableObject): mouseHandler.executeMouseMoveEvent(x,y) # Translators: Input help mode message for move mouse to navigator object command. script_moveMouseToNavigatorObject.__doc__=_("Moves the mouse pointer to the current navigator object") + script_moveMouseToNavigatorObject.category=SCRCAT_MOUSE def script_moveNavigatorObjectToMouse(self,gesture): # Translators: Reported when attempting to move the navigator object to the object under mouse pointer. @@ -278,6 +333,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(obj) # Translators: Input help mode message for move navigator object to mouse command. script_moveNavigatorObjectToMouse.__doc__=_("Sets the navigator object to the current object under the mouse pointer and speaks it") + script_moveNavigatorObjectToMouse.category=SCRCAT_MOUSE def script_reviewMode_next(self,gesture): label=review.nextMode() @@ -290,6 +346,7 @@ class GlobalCommands(ScriptableObject): # Translators: reported when there are no other available review modes for this object ui.message(_("No next review mode")) script_reviewMode_next.__doc__=_("Switches to the next review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") + script_reviewMode_next.category=SCRCAT_TEXTREVIEW def script_reviewMode_previous(self,gesture): label=review.nextMode(prev=True) @@ -302,7 +359,8 @@ class GlobalCommands(ScriptableObject): # Translators: reported when there are no other available review modes for this object ui.message(_("No previous review mode")) script_reviewMode_previous.__doc__=_("Switches to the previous review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") - + script_reviewMode_previous.category=SCRCAT_TEXTREVIEW + def script_navigatorObject_current(self,gesture): curObject=api.getNavigatorObject() if not isinstance(curObject,NVDAObject): @@ -340,6 +398,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(curObject,reason=controlTypes.REASON_QUERY) # Translators: Input help mode message for report current navigator object command. script_navigatorObject_current.__doc__=_("Reports the current navigator object. Pressing twice spells this information,and pressing three times Copies name and value of this object to the clipboard") + script_navigatorObject_current.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_currentDimensions(self,gesture): obj=api.getNavigatorObject() @@ -363,6 +422,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Object edges positioned {left:.1f} per cent from left edge of screen, {top:.1f} per cent from top edge of screen, width is {width:.1f} per cent of screen, height is {height:.1f} per cent of screen").format(left=percentFromLeft,top=percentFromTop,width=percentWidth,height=percentHeight)) # Translators: Input help mode message for report object dimensions command. script_navigatorObject_currentDimensions.__doc__=_("Reports the hight, width and position of the current navigator object") + script_navigatorObject_currentDimensions.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_toFocus(self,gesture): obj=api.getFocusObject() @@ -376,6 +436,7 @@ class GlobalCommands(ScriptableObject): speech.speakObject(obj,reason=controlTypes.REASON_FOCUS) # Translators: Input help mode message for move navigator object to current focus command. script_navigatorObject_toFocus.__doc__=_("Sets the navigator object to the current focus, and the review cursor to the position of the caret inside it, if possible.") + script_navigatorObject_toFocus.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_moveFocus(self,gesture): obj=api.getNavigatorObject() @@ -401,6 +462,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move focus to current navigator object command. script_navigatorObject_moveFocus.__doc__=_("Pressed once Sets the keyboard focus to the navigator object, pressed twice sets the system caret to the position of the review cursor") + script_navigatorObject_moveFocus.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_parent(self,gesture): curObject=api.getNavigatorObject() @@ -417,6 +479,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No containing object")) # Translators: Input help mode message for move to parent object command. script_navigatorObject_parent.__doc__=_("Moves the navigator object to the object containing it") + script_navigatorObject_parent.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_next(self,gesture): curObject=api.getNavigatorObject() @@ -433,6 +496,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No next")) # Translators: Input help mode message for move to next object command. script_navigatorObject_next.__doc__=_("Moves the navigator object to the next object") + script_navigatorObject_next.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_previous(self,gesture): curObject=api.getNavigatorObject() @@ -449,6 +513,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No previous")) # Translators: Input help mode message for move to previous object command. script_navigatorObject_previous.__doc__=_("Moves the navigator object to the previous object") + script_navigatorObject_previous.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_firstChild(self,gesture): curObject=api.getNavigatorObject() @@ -465,6 +530,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("No objects inside")) # Translators: Input help mode message for move to first child object command. script_navigatorObject_firstChild.__doc__=_("Moves the navigator object to the first object inside it") + script_navigatorObject_firstChild.category=SCRCAT_OBJECTNAVIGATION def script_review_activate(self,gesture): # Translators: a message reported when the action at the position of the review cursor or navigator object is performed. @@ -498,6 +564,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("No action")) # Translators: Input help mode message for activate current object command. script_review_activate.__doc__=_("Performs the default action on the current navigator object (example: presses it if it is a button).") + script_review_activate.category=SCRCAT_OBJECTNAVIGATION def script_review_top(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_FIRST) @@ -507,6 +574,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to top line command. script_review_top.__doc__=_("Moves the review cursor to the top line of the current navigator object and speaks it") + script_review_top.category=SCRCAT_TEXTREVIEW def script_review_previousLine(self,gesture): info=api.getReviewPosition().copy() @@ -521,6 +589,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to previous line command. script_review_previousLine.__doc__=_("Moves the review cursor to the previous line of the current navigator object and speaks it") + script_review_previousLine.category=SCRCAT_TEXTREVIEW def script_review_currentLine(self,gesture): info=api.getReviewPosition().copy() @@ -532,6 +601,7 @@ class GlobalCommands(ScriptableObject): speech.spellTextInfo(info,useCharacterDescriptions=scriptCount>1) # Translators: Input help mode message for read current line under review cursor command. script_review_currentLine.__doc__=_("Reports the line of the current navigator object where the review cursor is situated. If this key is pressed twice, the current line will be spelled. Pressing three times will spell the line using character descriptions.") + script_review_currentLine.category=SCRCAT_TEXTREVIEW def script_review_nextLine(self,gesture): info=api.getReviewPosition().copy() @@ -546,6 +616,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to next line command. script_review_nextLine.__doc__=_("Moves the review cursor to the next line of the current navigator object and speaks it") + script_review_nextLine.category=SCRCAT_TEXTREVIEW def script_review_bottom(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) @@ -555,6 +626,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to bottom line command. script_review_bottom.__doc__=_("Moves the review cursor to the bottom line of the current navigator object and speaks it") + script_review_bottom.category=SCRCAT_TEXTREVIEW def script_review_previousWord(self,gesture): info=api.getReviewPosition().copy() @@ -570,6 +642,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET,unit=textInfos.UNIT_WORD) # Translators: Input help mode message for move review cursor to previous word command. script_review_previousWord.__doc__=_("Moves the review cursor to the previous word of the current navigator object and speaks it") + script_review_previousWord.category=SCRCAT_TEXTREVIEW def script_review_currentWord(self,gesture): info=api.getReviewPosition().copy() @@ -581,6 +654,7 @@ class GlobalCommands(ScriptableObject): speech.spellTextInfo(info,useCharacterDescriptions=scriptCount>1) # Translators: Input help mode message for report current word under review cursor command. script_review_currentWord.__doc__=_("Speaks the word of the current navigator object where the review cursor is situated. Pressing twice spells the word. Pressing three times spells the word using character descriptions") + script_review_currentWord.category=SCRCAT_TEXTREVIEW def script_review_nextWord(self,gesture): info=api.getReviewPosition().copy() @@ -595,6 +669,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,reason=controlTypes.REASON_CARET,unit=textInfos.UNIT_WORD) # Translators: Input help mode message for move review cursor to next word command. script_review_nextWord.__doc__=_("Moves the review cursor to the next word of the current navigator object and speaks it") + script_review_nextWord.category=SCRCAT_TEXTREVIEW def script_review_startOfLine(self,gesture): info=api.getReviewPosition().copy() @@ -606,6 +681,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to start of current line command. script_review_startOfLine.__doc__=_("Moves the review cursor to the first character of the line where it is situated in the current navigator object and speaks it") + script_review_startOfLine.category=SCRCAT_TEXTREVIEW def script_review_previousCharacter(self,gesture): lineInfo=api.getReviewPosition().copy() @@ -625,6 +701,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(charInfo,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to previous character command. script_review_previousCharacter.__doc__=_("Moves the review cursor to the previous character of the current navigator object and speaks it") + script_review_previousCharacter.category=SCRCAT_TEXTREVIEW def script_review_currentCharacter(self,gesture): info=api.getReviewPosition().copy() @@ -643,6 +720,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for report current character under review cursor command. script_review_currentCharacter.__doc__=_("Reports the character of the current navigator object where the review cursor is situated. Pressing twice reports a description or example of that character. Pressing three times reports the numeric value of the character in decimal and hexadecimal") + script_review_currentCharacter.category=SCRCAT_TEXTREVIEW def script_review_nextCharacter(self,gesture): lineInfo=api.getReviewPosition().copy() @@ -662,6 +740,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(charInfo,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to next character command. script_review_nextCharacter.__doc__=_("Moves the review cursor to the next character of the current navigator object and speaks it") + script_review_nextCharacter.category=SCRCAT_TEXTREVIEW def script_review_endOfLine(self,gesture): info=api.getReviewPosition().copy() @@ -674,6 +753,7 @@ class GlobalCommands(ScriptableObject): speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to end of current line command. script_review_endOfLine.__doc__=_("Moves the review cursor to the last character of the line where it is situated in the current navigator object and speaks it") + script_review_endOfLine.category=SCRCAT_TEXTREVIEW def script_speechMode(self,gesture): curMode=speech.speechMode @@ -693,6 +773,7 @@ class GlobalCommands(ScriptableObject): speech.speechMode=newMode # Translators: Input help mode message for toggle speech mode command. script_speechMode.__doc__=_("Toggles between the speech modes of off, beep and talk. When set to off NVDA will not speak anything. If beeps then NVDA will simply beep each time it its supposed to speak something. If talk then NVDA wil just speak normally.") + script_speechMode.category=SCRCAT_SPEECH def script_moveToParentTreeInterceptor(self,gesture): obj=api.getFocusObject() @@ -711,6 +792,7 @@ class GlobalCommands(ScriptableObject): wx.CallLater(50,eventHandler.executeEvent,"gainFocus",parent.treeInterceptor.rootNVDAObject) # Translators: Input help mode message for move to next document with focus command, mostly used in web browsing to move from embedded object to the webpage document. script_moveToParentTreeInterceptor.__doc__=_("Moves the focus to the next closest document that contains the focus") + script_moveToParentTreeInterceptor.category=SCRCAT_FOCUS def script_toggleVirtualBufferPassThrough(self,gesture): vbuf = api.getFocusObject().treeInterceptor @@ -724,6 +806,7 @@ class GlobalCommands(ScriptableObject): virtualBuffers.reportPassThrough(vbuf) # Translators: Input help mode message for toggle focus and browse mode command in web browsing and other situations. script_toggleVirtualBufferPassThrough.__doc__=_("Toggles between browse mode and focus mode. When in focus mode, keys will pass straight through to the application, allowing you to interact directly with a control. When in browse mode, you can navigate the document with the cursor, quick navigation keys, etc.") + script_toggleVirtualBufferPassThrough.category=inputCore.SCRCAT_BROWSEMODE def script_quit(self,gesture): gui.quit() @@ -739,11 +822,13 @@ class GlobalCommands(ScriptableObject): sayAllHandler.readText(sayAllHandler.CURSOR_REVIEW) # Translators: Input help mode message for say all in review cursor command. script_review_sayAll.__doc__ = _("reads from the review cursor up to end of current text, moving the review cursor as it goes") + script_review_sayAll.category=SCRCAT_TEXTREVIEW def script_sayAll(self,gesture): sayAllHandler.readText(sayAllHandler.CURSOR_CARET) # Translators: Input help mode message for say all with system caret command. script_sayAll.__doc__ = _("reads from the system caret up to the end of the text, moving the caret as it goes") + script_sayAll.category=SCRCAT_SYSTEMCARET def script_reportFormatting(self,gesture): formatConfig={ @@ -781,6 +866,7 @@ class GlobalCommands(ScriptableObject): ui.message(" ".join(textList)) # Translators: Input help mode message for report formatting command. script_reportFormatting.__doc__ = _("Reports formatting info for the current review cursor position within a document") + script_reportFormatting.category=SCRCAT_TEXTREVIEW def script_reportCurrentFocus(self,gesture): focusObject=api.getFocusObject() @@ -793,6 +879,7 @@ class GlobalCommands(ScriptableObject): speech.speakMessage(_("no focus")) # Translators: Input help mode message for report current focus command. script_reportCurrentFocus.__doc__ = _("reports the object with focus") + script_reportCurrentFocus.category=SCRCAT_FOCUS def script_reportStatusLine(self,gesture): obj = api.getStatusBar() @@ -821,6 +908,7 @@ class GlobalCommands(ScriptableObject): speech.speakSpelling(text) # Translators: Input help mode message for report status line text command. script_reportStatusLine.__doc__ = _("reads the current application status bar and moves the navigator to it") + script_reportStatusLine.category=SCRCAT_FOCUS def script_toggleMouseTracking(self,gesture): if config.conf["mouse"]["enableMouseTracking"]: @@ -834,6 +922,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle mouse tracking command. script_toggleMouseTracking.__doc__=_("Toggles the reporting of information as the mouse moves") + script_toggleMouseTracking.category=SCRCAT_MOUSE def script_title(self,gesture): obj=api.getForegroundObject() @@ -853,6 +942,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("%s copied to clipboard")%title) # Translators: Input help mode message for report title bar command. script_title.__doc__=_("Reports the title of the current application or foreground window. If pressed twice, spells the title. If pressed three times, copies the title to the clipboard") + script_title.category=SCRCAT_FOCUS def script_speakForeground(self,gesture): obj=api.getForegroundObject() @@ -860,6 +950,7 @@ class GlobalCommands(ScriptableObject): sayAllHandler.readObjects(obj) # Translators: Input help mode message for read foreground object command (usually the foreground window). script_speakForeground.__doc__ = _("speaks the current foreground object") + script_speakForeground.category=SCRCAT_FOCUS def script_test_navigatorDisplayModelText(self,gesture): obj=api.getNavigatorObject() @@ -872,6 +963,7 @@ class GlobalCommands(ScriptableObject): log.info("Developer info for navigator object:\n%s" % "\n".join(obj.devInfo), activateLogViewer=True) # Translators: Input help mode message for developer info for current navigator object command, used by developers to examine technical info on navigator object. This command also serves as a shortcut to open NVDA log viewer. script_navigatorObject_devInfo.__doc__ = _("Logs information about the current navigator object which is useful to developers and activates the log viewer so the information can be examined.") + script_navigatorObject_devInfo.category=SCRCAT_TOOLS def script_toggleProgressBarOutput(self,gesture): outputMode=config.conf["presentation"]["progressBarUpdates"]["progressBarOutputMode"] @@ -894,6 +986,7 @@ class GlobalCommands(ScriptableObject): config.conf["presentation"]["progressBarUpdates"]["progressBarOutputMode"]=outputMode # Translators: Input help mode message for toggle progress bar output command. script_toggleProgressBarOutput.__doc__=_("Toggles between beeps, speech, beeps and speech, and off, for reporting progress bar updates") + script_toggleProgressBarOutput.category=SCRCAT_SPEECH def script_toggleReportDynamicContentChanges(self,gesture): if config.conf["presentation"]["reportDynamicContentChanges"]: @@ -907,6 +1000,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle dynamic content changes command. script_toggleReportDynamicContentChanges.__doc__=_("Toggles on and off the reporting of dynamic content changes, such as new text in dos console windows") + script_toggleReportDynamicContentChanges.category=SCRCAT_SPEECH def script_toggleCaretMovesReviewCursor(self,gesture): if config.conf["reviewCursor"]["followCaret"]: @@ -920,6 +1014,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle caret moves review cursor command. script_toggleCaretMovesReviewCursor.__doc__=_("Toggles on and off the movement of the review cursor due to the caret moving.") + script_toggleCaretMovesReviewCursor.category=SCRCAT_TEXTREVIEW def script_toggleFocusMovesNavigatorObject(self,gesture): if config.conf["reviewCursor"]["followFocus"]: @@ -933,6 +1028,7 @@ class GlobalCommands(ScriptableObject): ui.message(state) # Translators: Input help mode message for toggle focus moves navigator object command. script_toggleFocusMovesNavigatorObject.__doc__=_("Toggles on and off the movement of the navigator object due to focus changes") + script_toggleFocusMovesNavigatorObject.category=SCRCAT_OBJECTNAVIGATION #added by Rui Batista<ruiandrebatista@xxxxxxxxx> to implement a battery status script def script_say_battery_status(self,gesture): @@ -957,6 +1053,7 @@ class GlobalCommands(ScriptableObject): ui.message(text) # Translators: Input help mode message for report battery status command. script_say_battery_status.__doc__ = _("reports battery status and time remaining if AC is not plugged in") + script_say_battery_status.category=SCRCAT_SYSTEM def script_passNextKeyThrough(self,gesture): keyboardHandler.passNextKeyThrough() @@ -964,6 +1061,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Pass next key through")) # Translators: Input help mode message for pass next key through command. script_passNextKeyThrough.__doc__=_("The next key that is pressed will not be handled at all by NVDA, it will be passed directly through to Windows.") + script_passNextKeyThrough.category=SCRCAT_INPUT def script_reportAppModuleInfo(self,gesture): focus=api.getFocusObject() @@ -981,51 +1079,62 @@ class GlobalCommands(ScriptableObject): ui.message(message) # Translators: Input help mode message for report current program name and app module name command. script_reportAppModuleInfo.__doc__ = _("Speaks the filename of the active application along with the name of the currently loaded appModule") + script_reportAppModuleInfo.category=SCRCAT_TOOLS def script_activateGeneralSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onGeneralSettingsCommand, None) # Translators: Input help mode message for go to general settings dialog command. script_activateGeneralSettingsDialog.__doc__ = _("Shows the NVDA general settings dialog") + script_activateGeneralSettingsDialog.category=SCRCAT_CONFIG + def script_activateSynthesizerDialog(self, gesture): wx.CallAfter(gui.mainFrame.onSynthesizerCommand, None) # Translators: Input help mode message for go to synthesizer dialog command. script_activateSynthesizerDialog.__doc__ = _("Shows the NVDA synthesizer dialog") + script_activateSynthesizerDialog.category=SCRCAT_CONFIG def script_activateVoiceDialog(self, gesture): wx.CallAfter(gui.mainFrame.onVoiceCommand, None) # Translators: Input help mode message for go to voice settings dialog command. script_activateVoiceDialog.__doc__ = _("Shows the NVDA voice settings dialog") + script_activateVoiceDialog.category=SCRCAT_CONFIG def script_activateKeyboardSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onKeyboardSettingsCommand, None) # Translators: Input help mode message for go to keyboard settings dialog command. script_activateKeyboardSettingsDialog.__doc__ = _("Shows the NVDA keyboard settings dialog") + script_activateKeyboardSettingsDialog.category=SCRCAT_CONFIG def script_activateMouseSettingsDialog(self, gesture): wx.CallAfter(gui.mainFrame.onMouseSettingsCommand, None) # Translators: Input help mode message for go to mouse settings dialog command. script_activateMouseSettingsDialog.__doc__ = _("Shows the NVDA mouse settings dialog") + script_activateMouseSettingsDialog.category=SCRCAT_CONFIG def script_activateObjectPresentationDialog(self, gesture): wx.CallAfter(gui.mainFrame. onObjectPresentationCommand, None) # Translators: Input help mode message for go to object presentation dialog command. script_activateObjectPresentationDialog.__doc__ = _("Shows the NVDA object presentation settings dialog") + script_activateObjectPresentationDialog.category=SCRCAT_CONFIG def script_activateBrowseModeDialog(self, gesture): wx.CallAfter(gui.mainFrame.onBrowseModeCommand, None) # Translators: Input help mode message for go to browse mode dialog command. script_activateBrowseModeDialog.__doc__ = _("Shows the NVDA browse mode settings dialog") + script_activateBrowseModeDialog.category=SCRCAT_CONFIG def script_activateDocumentFormattingDialog(self, gesture): wx.CallAfter(gui.mainFrame.onDocumentFormattingCommand, None) # Translators: Input help mode message for go to document formatting dialog command. script_activateDocumentFormattingDialog.__doc__ = _("Shows the NVDA document formatting settings dialog") + script_activateDocumentFormattingDialog.category=SCRCAT_CONFIG def script_saveConfiguration(self,gesture): wx.CallAfter(gui.mainFrame.onSaveConfigurationCommand, None) # Translators: Input help mode message for save current configuration command. script_saveConfiguration.__doc__ = _("Saves the current NVDA configuration") + script_saveConfiguration.category=SCRCAT_CONFIG def script_revertConfiguration(self,gesture): scriptCount=scriptHandler.getLastScriptRepeatCount() @@ -1035,6 +1144,13 @@ class GlobalCommands(ScriptableObject): gui.mainFrame.onRevertToDefaultConfigurationCommand(None) # Translators: Input help mode message for apply last saved or default settings command. script_revertConfiguration.__doc__ = _("Pressing once reverts the current configuration to the most recently saved state. Pressing three times reverts to factory defaults.") + script_revertConfiguration.category=SCRCAT_CONFIG + + def script_activateInputGesturesDialog(self, gesture): + wx.CallAfter(gui.mainFrame.onInputGesturesCommand, None) + # Translators: Input help mode message for go to Input Gestures dialog command. + script_activateInputGesturesDialog.__doc__ = _("Shows the NVDA input gestures dialog") + script_activateInputGesturesDialog.category=SCRCAT_CONFIG def script_activatePythonConsole(self,gesture): if globalVars.appArgs.secure: @@ -1046,6 +1162,7 @@ class GlobalCommands(ScriptableObject): pythonConsole.activate() # Translators: Input help mode message for activate python console command. script_activatePythonConsole.__doc__ = _("Activates the NVDA Python Console, primarily useful for development") + script_activatePythonConsole.category=SCRCAT_TOOLS def script_braille_toggleTether(self, gesture): if braille.handler.tether == braille.handler.TETHER_FOCUS: @@ -1058,6 +1175,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Braille tethered to %s") % tetherMsg) # Translators: Input help mode message for toggle braille tether to command (tethered means connected to or follows). script_braille_toggleTether.__doc__ = _("Toggle tethering of braille between the focus and the review position") + script_braille_toggleTether.category=SCRCAT_BRAILLE def script_reportClipboardText(self,gesture): try: @@ -1076,6 +1194,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("The clipboard contains a large portion of text. It is %s characters long") % len(text)) # Translators: Input help mode message for report clipboard text command. script_reportClipboardText.__doc__ = _("Reports the text on the Windows clipboard") + script_reportClipboardText.category=SCRCAT_SYSTEM def script_review_markStartForCopy(self, gesture): self._copyStartMarker = api.getReviewPosition().copy() @@ -1083,6 +1202,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Start marked")) # Translators: Input help mode message for mark review cursor position for copy command (that is, marks the current review cursor position as the starting point for text to be copied). script_review_markStartForCopy.__doc__ = _("Marks the current position of the review cursor as the start of text to be copied") + script_review_markStartForCopy.category=SCRCAT_TEXTREVIEW def script_review_copy(self, gesture): if not getattr(self, "_copyStartMarker", None): @@ -1106,40 +1226,47 @@ class GlobalCommands(ScriptableObject): self._copyStartMarker = None # Translators: Input help mode message for copy selected review cursor text to clipboard command. script_review_copy.__doc__ = _("Retrieves the text from the previously set start marker up to and including the current position of the review cursor and copies it to the clipboard") + script_review_copy.category=SCRCAT_TEXTREVIEW def script_braille_scrollBack(self, gesture): braille.handler.scrollBack() # Translators: Input help mode message for a braille command. script_braille_scrollBack.__doc__ = _("Scrolls the braille display back") script_braille_scrollBack.bypassInputHelp = True + script_braille_scrollBack.category=SCRCAT_BRAILLE def script_braille_scrollForward(self, gesture): braille.handler.scrollForward() # Translators: Input help mode message for a braille command. script_braille_scrollForward.__doc__ = _("Scrolls the braille display forward") script_braille_scrollForward.bypassInputHelp = True + script_braille_scrollForward.category=SCRCAT_BRAILLE def script_braille_routeTo(self, gesture): braille.handler.routeTo(gesture.routingIndex) # Translators: Input help mode message for a braille command. script_braille_routeTo.__doc__ = _("Routes the cursor to or activates the object under this braille cell") + script_braille_routeTo.category=SCRCAT_BRAILLE def script_braille_previousLine(self, gesture): if braille.handler.buffer.regions: braille.handler.buffer.regions[-1].previousLine(start=True) # Translators: Input help mode message for a braille command. script_braille_previousLine.__doc__ = _("Moves the braille display to the previous line") + script_braille_previousLine.category=SCRCAT_BRAILLE def script_braille_nextLine(self, gesture): if braille.handler.buffer.regions: braille.handler.buffer.regions[-1].nextLine() # Translators: Input help mode message for a braille command. script_braille_nextLine.__doc__ = _("Moves the braille display to the next line") + script_braille_nextLine.category=SCRCAT_BRAILLE def script_braille_dots(self, gesture): brailleInput.handler.input(gesture.dots) # Translators: Input help mode message for a braille command. script_braille_dots.__doc__= _("Inputs braille dots via the braille keyboard") + script_braille_dots.category=SCRCAT_BRAILLE def script_reloadPlugins(self, gesture): import globalPluginHandler @@ -1150,6 +1277,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("Plugins reloaded")) # Translators: Input help mode message for reload plugins command. script_reloadPlugins.__doc__=_("Reloads app modules and global plugins without restarting NVDA, which can be Useful for developers") + script_reloadPlugins.category=SCRCAT_TOOLS def script_navigatorObject_nextInFlow(self,gesture): curObject=api.getNavigatorObject() @@ -1172,6 +1300,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("no next")) # Translators: Input help mode message for a touchscreen gesture. script_navigatorObject_nextInFlow.__doc__=_("Moves to the next object in a flattened view of the object navigation hierarchy") + script_navigatorObject_nextInFlow.category=SCRCAT_OBJECTNAVIGATION def script_navigatorObject_previousInFlow(self,gesture): curObject=api.getNavigatorObject() @@ -1189,6 +1318,7 @@ class GlobalCommands(ScriptableObject): ui.message(_("no next")) # Translators: Input help mode message for a touchscreen gesture. script_navigatorObject_previousInFlow.__doc__=_("Moves to the previous object in a flattened view of the object navigation hierarchy") + script_navigatorObject_previousInFlow.category=SCRCAT_OBJECTNAVIGATION def script_touch_changeMode(self,gesture): mode=touchHandler.handler._curTouchMode @@ -1199,16 +1329,20 @@ class GlobalCommands(ScriptableObject): ui.message(_("%s mode")%newMode) # Translators: Input help mode message for a touchscreen gesture. script_touch_changeMode.__doc__=_("cycles between available touch modes") + script_touch_changeMode.category=SCRCAT_TOUCH + def script_touch_newExplore(self,gesture): touchHandler.handler.screenExplorer.moveTo(gesture.tracker.x,gesture.tracker.y,new=True) # Translators: Input help mode message for a touchscreen gesture. script_touch_newExplore.__doc__=_("Reports the object and content directly under your finger") + script_touch_newExplore.category=SCRCAT_TOUCH def script_touch_explore(self,gesture): touchHandler.handler.screenExplorer.moveTo(gesture.tracker.x,gesture.tracker.y) # Translators: Input help mode message for a touchscreen gesture. script_touch_explore.__doc__=_("Reports the new object or content under your finger if different to where your finger was last") + script_touch_explore.category=SCRCAT_TOUCH def script_touch_hoverUp(self,gesture): #Specifically for touch typing with onscreen keyboard keys @@ -1216,6 +1350,7 @@ class GlobalCommands(ScriptableObject): import NVDAObjects.UIA if isinstance(obj,NVDAObjects.UIA.UIA) and obj.UIAElement.cachedClassName=="CRootKey": obj.doAction() + script_touch_hoverUp.category=SCRCAT_TOUCH def script_activateConfigProfilesDialog(self, gesture): wx.CallAfter(gui.mainFrame.onConfigProfilesCommand, None) @@ -1361,6 +1496,7 @@ class GlobalCommands(ScriptableObject): "kb:NVDA+control+o": "activateObjectPresentationDialog", "kb:NVDA+control+b": "activateBrowseModeDialog", "kb:NVDA+control+d": "activateDocumentFormattingDialog", + "kb:NVDA+control+i": "activateInputGesturesDialog", # Configuration management "kb:NVDA+control+c": "saveConfiguration", diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 94e7881..0324263 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -225,6 +225,9 @@ class MainFrame(wx.Frame): def onSpeechSymbolsCommand(self, evt): self._popupSettingsDialog(SpeechSymbolsDialog) + def onInputGesturesCommand(self, evt): + self._popupSettingsDialog(InputGesturesDialog) + def onAboutCommand(self,evt): # Translators: The title of the dialog to show about info for NVDA. messageBox(versionInfo.aboutMessage, _("About NVDA"), wx.OK) @@ -351,6 +354,9 @@ class SysTrayIcon(wx.TaskBarIcon): # Translators: The label for the menu item to open Punctuation/symbol pronunciation dialog. item = menu_preferences.Append(wx.ID_ANY, _("&Punctuation/symbol pronunciation...")) self.Bind(wx.EVT_MENU, frame.onSpeechSymbolsCommand, item) + # Translators: The label for the menu item to open the Input Gestures dialog. + item = menu_preferences.Append(wx.ID_ANY, _("I&nput gestures...")) + self.Bind(wx.EVT_MENU, frame.onInputGesturesCommand, item) # Translators: The label for Preferences submenu in NVDA menu. self.menu.AppendMenu(wx.ID_ANY,_("&Preferences"),menu_preferences) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index f4cd6e2..bd10732 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -30,6 +30,7 @@ try: import updateCheck except RuntimeError: updateCheck = None +import inputCore class SettingsDialog(wx.Dialog): """A settings dialog. @@ -1516,3 +1517,148 @@ class SpeechSymbolsDialog(SettingsDialog): log.error("Error saving user symbols info: %s" % e) characterProcessing._localeSpeechSymbolProcessors.invalidateLocaleData(self.symbolProcessor.locale) super(SpeechSymbolsDialog, self).onOk(evt) + +class InputGesturesDialog(SettingsDialog): + # Translators: The title of the Input Gestures dialog where the user can remap input gestures for commands. + title = _("Input Gestures") + + def makeSettings(self, settingsSizer): + tree = self.tree = wx.TreeCtrl(self, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_SINGLE) + self.treeRoot = tree.AddRoot("root") + tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect) + settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) + + gestures = inputCore.manager.getAllGestureMappings() + for category in sorted(gestures): + treeCat = tree.AppendItem(self.treeRoot, category) + commands = gestures[category] + for command in sorted(commands): + treeCom = tree.AppendItem(treeCat, command) + commandInfo = commands[command] + tree.SetItemPyData(treeCom, commandInfo) + for gesture in commandInfo.gestures: + treeGes = tree.AppendItem(treeCom, self._formatGesture(gesture)) + tree.SetItemPyData(treeGes, gesture) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + # Translators: The label of a button to add a gesture in the Input Gestures dialog. + item = self.addButton = wx.Button(self, label=_("&Add")) + item.Bind(wx.EVT_BUTTON, self.onAdd) + item.Disable() + sizer.Add(item) + # Translators: The label of a button to remove a gesture in the Input Gestures dialog. + item = self.removeButton = wx.Button(self, label=_("&Remove")) + item.Bind(wx.EVT_BUTTON, self.onRemove) + item.Disable() + self.pendingAdds = set() + self.pendingRemoves = set() + sizer.Add(item) + settingsSizer.Add(sizer) + + def postInit(self): + self.tree.SetFocus() + + def _formatGesture(self, identifier): + try: + source, main = inputCore.getDisplayTextForGestureIdentifier(identifier) + # Translators: Describes a gesture in the Input Gestures dialog. + # {main} is replaced with the main part of the gesture; e.g. alt+tab. + # {source} is replaced with the gesture's source; e.g. laptop keyboard. + return _("{main} ({source})").format(main=main, source=source) + except LookupError: + return identifier + + def onTreeSelect(self, evt): + item = self.tree.Selection + data = self.tree.GetItemPyData(item) + isCommand = isinstance(data, inputCore.AllGesturesScriptInfo) + isGesture = isinstance(data, basestring) + self.addButton.Enabled = isCommand or isGesture + self.removeButton.Enabled = isGesture + + def onAdd(self, evt): + if inputCore.manager._captureFunc: + return + + treeCom = self.tree.Selection + scriptInfo = self.tree.GetItemPyData(treeCom) + if not isinstance(scriptInfo, inputCore.AllGesturesScriptInfo): + treeCom = self.tree.GetItemParent(treeCom) + scriptInfo = self.tree.GetItemPyData(treeCom) + # Translators: The prompt to enter a gesture in the Input Gestures dialog. + treeGes = self.tree.AppendItem(treeCom, _("Enter input gesture:")) + self.tree.SelectItem(treeGes) + self.tree.SetFocus() + + def addGestureCaptor(gesture): + if gesture.isModifier: + return False + inputCore.manager._captureFunc = None + wx.CallAfter(self._addCaptured, treeGes, scriptInfo, gesture) + return False + inputCore.manager._captureFunc = addGestureCaptor + + def _addCaptured(self, treeGes, scriptInfo, gesture): + gids = gesture.identifiers + if len(gids) > 1: + # Multiple choices. Present them in a pop-up menu. + menu = wx.Menu() + for gid in gids: + disp = self._formatGesture(gid) + item = menu.Append(wx.ID_ANY, disp) + self.Bind(wx.EVT_MENU, + lambda evt: self._addChoice(treeGes, scriptInfo, gesture, gid, disp), + item) + self.PopupMenu(menu) + if not self.tree.GetItemPyData(treeGes): + # No item was selected, so use the first. + self._addChoice(treeGes, scriptInfo, gesture, gids[0], + self._formatGesture(gids[0])) + menu.Destroy() + else: + self._addChoice(treeGes, scriptInfo, gesture, gids[0], + self._formatGesture(gids[0])) + + def _addChoice(self, treeGes, scriptInfo, gesture, gid, disp): + self.pendingAdds.add((gid, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) + self.tree.SetItemText(treeGes, disp) + self.tree.SetItemPyData(treeGes, gid) + self.onTreeSelect(None) + + def onRemove(self, evt): + treeGes = self.tree.Selection + gesture = self.tree.GetItemPyData(treeGes) + treeCom = self.tree.GetItemParent(treeGes) + scriptInfo = self.tree.GetItemPyData(treeCom) + self.pendingRemoves.add((gesture, scriptInfo.moduleName, scriptInfo.className, scriptInfo.scriptName)) + self.tree.Delete(treeGes) + self.tree.SetFocus() + + def onOk(self, evt): + for gesture, module, className, scriptName in self.pendingRemoves: + try: + inputCore.manager.userGestureMap.remove(gesture, module, className, scriptName) + except ValueError: + # The user wants to unbind a gesture they didn't define. + inputCore.manager.userGestureMap.add(gesture, module, className, None) + + for gesture, module, className, scriptName in self.pendingAdds: + try: + # The user might have unbound this gesture, + # so remove this override first. + inputCore.manager.userGestureMap.remove(gesture, module, className, None) + except ValueError: + pass + inputCore.manager.userGestureMap.add(gesture, module, className, scriptName) + + if self.pendingAdds or self.pendingRemoves: + # Only save if there is something to save. + try: + inputCore.manager.userGestureMap.save() + except: + log.debugWarning("", exc_info=True) + # Translators: An error displayed when saving user defined input gestures fails. + gui.messageBox(_("Error saving user defined gestures - probably read only file system."), + _("Error"), wx.OK | wx.ICON-ERROR) + + super(InputGesturesDialog, self).onOk(evt) diff --git a/source/inputCore.py b/source/inputCore.py index 9feaf8f..67292d3 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -13,6 +13,7 @@ For example, it is used to execute gestures and handle input help. import sys import os import itertools +import weakref import configobj import baseObject import scriptHandler @@ -27,6 +28,16 @@ import globalVars import languageHandler import controlTypes +#: Script category for emulated keyboard keys. +# Translators: The name of a category of NVDA commands. +SCRCAT_KBEMU = _("Emulated system keyboard keys") +#: Script category for miscellaneous commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_MISC = _("Miscellaneous") +#: Script category for Browse Mode commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BROWSEMODE = _("Browse mode") + class NoInputGestureAction(LookupError): """Informs that there is no action to execute for a gesture. """ @@ -114,6 +125,22 @@ class InputGesture(baseObject.AutoPropertyObject): """ return None + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + """Get the text to be presented to the user describing a given gesture identifier. + This should only be called for gesture identifiers associated with this class. + Most callers will want L{inputCore.getDisplayTextForIdentifier} instead. + The display text consists of two strings: + the gesture's source (e.g. "laptop keyboard") + and the specific gesture (e.g. "alt+tab"). + @param identifier: The normalized gesture identifier in question. + @type identifier: basestring + @return: A tuple of (source, specificGesture). + @rtype: tuple of (basestring, basestring) + @raise Exception: If no display text can be determined. + """ + raise NotImplementedError + class GlobalGestureMap(object): """Maps gestures to scripts anywhere in NVDA. This is used to allow users and locales to bind gestures in addition to those bound by individual scriptable objects. @@ -130,6 +157,9 @@ class GlobalGestureMap(object): #: Indicates that the last load or update contained an error. #: @type: bool self.lastUpdateContainedError = False + #: The file name for this gesture map, if any. + #: @type: basestring + self.fileName = None if entries: self.update(entries) @@ -177,6 +207,7 @@ class GlobalGestureMap(object): @param filename: The name of the file to load. @type: str """ + self.fileName = filename try: conf = configobj.ConfigObj(filename, file_error=True, encoding="UTF-8") except (configobj.ConfigObjError,UnicodeDecodeError), e: @@ -249,15 +280,79 @@ class GlobalGestureMap(object): continue yield cls, scriptName + def getScriptsForAllGestures(self): + """Get all of the scripts and their gestures. + @return: The Python class, gesture and script name for each mapping; + the script name may be C{None} indicating that the gesture should be unbound for this class. + @rtype: generator of (class, str, str) + """ + for gesture in self._map: + for cls, scriptName in self.getScriptsForGesture(gesture): + yield cls, gesture, scriptName + + def remove(self, gesture, module, className, script): + """Remove a gesture mapping. + @param gesture: The gesture identifier. + @type gesture: str + @param module: The name of the Python module containing the target script. + @type module: str + @param className: The name of the class in L{module} containing the target script. + @type className: str + @param script: The name of the target script. + @type script: str + @raise ValueError: If the requested mapping does not exist. + """ + gesture = normalizeGestureIdentifier(gesture) + try: + scripts = self._map[gesture] + except KeyError: + raise ValueError("Mapping not found") + scripts.remove((module, className, script)) + + def save(self): + """Save this gesture map to disk. + @precondition: L{load} must have been called. + """ + if globalVars.appArgs.secure: + return + if not self.fileName: + raise ValueError("No file name") + out = configobj.ConfigObj() + out.filename = self.fileName + + for gesture, scripts in self._map.iteritems(): + for module, className, script in scripts: + key = "%s.%s" % (module, className) + try: + outSect = out[key] + except KeyError: + out[key] = {} + outSect = out[key] + if script is None: + script = "None" + try: + outVal = outSect[script] + except KeyError: + # Write the first value as a string so configobj doesn't output a comma if there's only one value. + outVal = outSect[script] = gesture + else: + if isinstance(outVal, list): + outVal.append(gesture) + else: + outSect[script] = [outVal, gesture] + + out.write() + class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user. Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. """ def __init__(self): - #: Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed. - #: @type: bool - self.isInputHelpActive = False + #: The function to call when capturing gestures. + #: If it returns C{False}, normal execution will be prevented. + #: @type: callable + self._captureFunc = None #: The gestures mapped for the NVDA locale. #: @type: L{GlobalGestureMap} self.localeGestureMap = GlobalGestureMap() @@ -293,11 +388,13 @@ class InputManager(baseObject.AutoPropertyObject): if log.isEnabledFor(log.IO) and not gesture.isModifier: log.io("Input: %s" % gesture.logIdentifier) - if self.isInputHelpActive: - bypass = getattr(script, "bypassInputHelp", False) - queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass) - if not bypass: - return + if self._captureFunc: + try: + if self._captureFunc(gesture) is False: + return + except: + log.error("Error in capture function, disabling", exc_info=True) + self._captureFunc = None if gesture.isModifier: raise NoInputGestureAction @@ -313,6 +410,23 @@ class InputManager(baseObject.AutoPropertyObject): raise NoInputGestureAction + def _get_isInputHelpActive(self): + """Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed. + @rtype: bool + """ + return self._captureFunc == self._inputHelpCaptor + + def _set_isInputHelpActive(self, enable): + if enable: + self._captureFunc = self._inputHelpCaptor + elif self.isInputHelpActive: + self._captureFunc = None + + def _inputHelpCaptor(self, gesture): + bypass = getattr(gesture.script, "bypassInputHelp", False) + queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass) + return bypass + def _handleInputHelp(self, gesture, onlyLog=False): textList = [gesture.displayName] script = gesture.script @@ -379,6 +493,152 @@ class InputManager(baseObject.AutoPropertyObject): except NotImplementedError: pass + def getAllGestureMappings(self, obj=None, ancestors=None): + if not obj: + obj = api.getFocusObject() + ancestors = api.getFocusAncestors() + return _AllGestureMappingsRetriever(obj, ancestors).results + +class _AllGestureMappingsRetriever(object): + + def __init__(self, obj, ancestors): + self.results = {} + self.scriptInfo = {} + self.handledGestures = set() + + self.addGlobalMap(manager.userGestureMap) + self.addGlobalMap(manager.localeGestureMap) + import braille + gmap = braille.handler.display.gestureMap + if gmap: + self.addGlobalMap(gmap) + + # Global plugins. + import globalPluginHandler + for plugin in globalPluginHandler.runningPlugins: + self.addObj(plugin) + + # App module. + app = obj.appModule + if app: + self.addObj(app) + + # Tree interceptor. + ti = obj.treeInterceptor + if ti: + self.addObj(ti) + + # NVDAObject. + self.addObj(obj) + for anc in reversed(ancestors): + self.addObj(anc, isAncestor=True) + + # Global commands. + import globalCommands + self.addObj(globalCommands.commands) + + def addResult(self, scriptInfo): + self.scriptInfo[scriptInfo.cls, scriptInfo.scriptName] = scriptInfo + try: + cat = self.results[scriptInfo.category] + except KeyError: + cat = self.results[scriptInfo.category] = {} + cat[scriptInfo.displayName] = scriptInfo + + def addGlobalMap(self, gmap): + for cls, gesture, scriptName in gmap.getScriptsForAllGestures(): + key = (cls, gesture) + if key in self.handledGestures: + continue + self.handledGestures.add(key) + if scriptName is None: + # The global map specified that no script should execute for this gesture and object. + continue + try: + scriptInfo = self.scriptInfo[cls, scriptName] + except KeyError: + if scriptName.startswith("kb:"): + scriptInfo = self.makeKbEmuScriptInfo(cls, scriptName) + else: + try: + script = getattr(cls, "script_%s" % scriptName) + except AttributeError: + continue + scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + if not scriptInfo: + continue + self.addResult(scriptInfo) + scriptInfo.gestures.append(gesture) + + def makeKbEmuScriptInfo(self, cls, scriptName): + info = AllGesturesScriptInfo(cls, scriptName) + info.category = SCRCAT_KBEMU + info.displayName = scriptName[3:] + return info + + def makeNormalScriptInfo(self, cls, scriptName, script): + info = AllGesturesScriptInfo(cls, scriptName) + info.category = self.getScriptCategory(cls, script) + info.displayName = script.__doc__ + if not info.displayName: + return None + return info + + def getScriptCategory(self, cls, script): + try: + return script.category + except AttributeError: + pass + try: + return cls.scriptCategory + except AttributeError: + pass + return SCRCAT_MISC + + def addObj(self, obj, isAncestor=False): + scripts = {} + for cls in obj.__class__.__mro__: + for scriptName, script in cls.__dict__.iteritems(): + if not scriptName.startswith("script_"): + continue + if isAncestor and not getattr(script, "canPropagate", False): + continue + scriptName = scriptName[7:] + try: + scriptInfo = self.scriptInfo[cls, scriptName] + except KeyError: + scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script) + if not scriptInfo: + continue + self.addResult(scriptInfo) + scripts[script] = scriptInfo + for gesture, script in obj._gestureMap.iteritems(): + try: + scriptInfo = scripts[script.__func__] + except KeyError: + continue + key = (scriptInfo.cls, gesture) + if key in self.handledGestures: + continue + self.handledGestures.add(key) + scriptInfo.gestures.append(gesture) + +class AllGesturesScriptInfo(object): + __slots__ = ("cls", "scriptName", "category", "displayName", "gestures") + + def __init__(self, cls, scriptName): + self.cls = cls + self.scriptName = scriptName + self.gestures = [] + + @property + def moduleName(self): + return self.cls.__module__ + + @property + def className(self): + return self.cls.__name__ + def normalizeGestureIdentifier(identifier): """Normalize a gesture identifier so that it matches other identifiers for the same gesture. Any items separated by a + sign after the source are considered to be of indeterminate order @@ -393,6 +653,59 @@ def normalizeGestureIdentifier(identifier): main = "+".join(frozenset(main)) return u"{0}:{1}".format(prefix, main).lower() +#: Maps registered source prefix strings to L{InputGesture} classes. +gestureSources = weakref.WeakValueDictionary() + +def registerGestureSource(source, gestureCls): + """Register an input gesture class for a source prefix string. + The specified gesture class will be used for queries regarding all gesture identifiers with the given source prefix. + For example, if "kb" is registered with the C{KeyboardInputGesture} class, + any queries for "kb:tab" or "kb(desktop):tab" will be directed to the C{KeyboardInputGesture} class. + If there is no exact match for the source, any parenthesised portion is stripped. + For example, for "br(baum):d1", if "br(baum)" isn't registered, + "br" will be used if it is registered. + This registration is used, for example, to get the display text for a gesture identifier. + @param source: The source prefix for associated gesture identifiers. + @type source: basestring + @param gestureCls: The input gesture class. + @type gestureCls: L{InputGesture} + """ + gestureSources[source] = gestureCls + +def _getGestureClsForIdentifier(identifier): + """Get the registered gesture class for an identifier. + """ + source = identifier.split(":", 1)[0] + try: + return gestureSources[source] + except KeyError: + pass + genSource = source.split("(", 1)[0] + if genSource: + try: + return gestureSources[genSource] + except KeyError: + pass + raise LookupError("Gesture source not registered: %s" % source) + +def getDisplayTextForGestureIdentifier(identifier): + """Get the text to be presented to the user describing a given gesture identifier. + The display text consists of two strings: + the gesture's source (e.g. "laptop keyboard") + and the specific gesture (e.g. "alt+tab"). + @param identifier: The normalized gesture identifier in question. + @type identifier: basestring + @return: A tuple of (source, specificGesture). + @rtype: tuple of (basestring, basestring) + @raise LookupError: If no display text can be determined. + """ + gcls = _getGestureClsForIdentifier(identifier) + try: + return gcls.getDisplayTextForIdentifier(identifier) + except: + raise + raise LookupError("Couldn't get display text for identifier: %s" % identifier) + #: The singleton input manager instance. #: @type: L{InputManager} manager = None diff --git a/source/keyLabels.py b/source/keyLabels.py index 082c35b..3675259 100644 --- a/source/keyLabels.py +++ b/source/keyLabels.py @@ -6,45 +6,45 @@ localizedKeyLabels = { # Translators: This is the name of the back key found on multimedia keyboards for controlling the web-browser. - 'browserBack': _("back"), + 'browserback': _("back"), # Translators: This is the name of the forward key found on multimedia keyboards for controlling the web-browser. - 'browserForward': _("forward"), + 'browserforward': _("forward"), # Translators: This is the name of the refresh key found on multimedia keyboards for controlling the web-browser. - 'browserRefresh': _("refresh"), + 'browserrefresh': _("refresh"), # Translators: This is the name of the stop key found on multimedia keyboards for controlling the web-browser. - 'browserStop': _("browser stop"), + 'browserstop': _("browser stop"), # Translators: This is the name of the back key found on multimedia keyboards to goto the search page of the web-browser. - 'browserSearch': _("search page"), + 'browsersearch': _("search page"), # Translators: This is the name of the favorites key found on multimedia keyboards to open favorites in the web-browser. - 'browserFavorites': _("favorites"), + 'browserfavorites': _("favorites"), # Translators: This is the name of the home key found on multimedia keyboards to goto the home page in the web-browser. - 'browserHome': _("home page"), + 'browserhome': _("home page"), # Translators: This is the name of the mute key found on multimedia keyboards to control playback volume. - 'volumeMute': _("mute"), + 'volumemute': _("mute"), # Translators: This is the name of the volume down key found on multimedia keyboards to reduce playback volume. - 'volumeDown': _("volume down"), + 'volumedown': _("volume down"), # Translators: This is the name of the volume up key found on multimedia keyboards to increase playback volume. - 'volumeUp': _("volume up"), + 'volumeup': _("volume up"), # Translators: This is the name of the next track key found on multimedia keyboards to skip to next track in the mediaplayer. - 'mediaNextTrack': _("next track"), + 'medianexttrack': _("next track"), # Translators: This is the name of the next track key found on multimedia keyboards to skip to next track in the mediaplayer. - 'mediaPrevTrack': _("previous track"), + 'mediaprevtrack': _("previous track"), # Translators: This is the name of the stop key found on multimedia keyboards to stop the current playing track in the mediaplayer. - 'mediaStop': _("stop"), + 'mediastop': _("stop"), # Translators: This is the name of the play/pause key found on multimedia keyboards to play/pause the current playing track in the mediaplayer. - 'mediaPlayPause': _("play pause"), + 'mediaplaypause': _("play pause"), # Translators: This is the name of the launch email key found on multimedia keyboards to open an email client. - 'launchMail': _("email"), + 'launchmail': _("email"), # Translators: This is the name of the launch mediaplayer key found on multimedia keyboards to launch the mediaplayer. - 'launchMediaPlayer': _("media player"), + 'launchmediaplayer': _("media player"), # Translators: This is the name of the launch custom application 1 key found on multimedia keyboards to launch a user-defined application. - 'launchApp1': _("custom application 1"), + 'launchapp1': _("custom application 1"), # Translators: This is the name of the launch custom application 2 key found on multimedia keyboards to launch a user-defined application. - 'launchApp2': _("custom application 2"), + 'launchapp2': _("custom application 2"), # Translators: This is the name of a key on the keyboard. 'backspace': _("backspace"), # Translators: This is the name of a key on the keyboard. - 'capsLock': _("caps lock"), + 'capslock': _("caps lock"), # Translators: This is the name of a key on the keyboard. 'control': _("ctrl"), # Translators: This is the name of a key on the keyboard. @@ -56,15 +56,15 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'enter': _("enter"), # Translators: This is the name of a key on the keyboard. - 'numpadEnter': _("numpad enter"), + 'numpadenter': _("numpad enter"), # Translators: This is the name of a key on the keyboard. 'escape': _("escape"), # Translators: This is the name of a key on the keyboard. 'space': _("space"), # Translators: This is the name of a key on the keyboard. - 'pageUp': _("page up"), + 'pageup': _("page up"), # Translators: This is the name of a key on the keyboard. - 'pageDown': _("page down"), + 'pagedown': _("page down"), # Translators: This is the name of a key on the keyboard. 'end': _("end"), # Translators: This is the name of a key on the keyboard. @@ -72,23 +72,23 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'delete': _("delete"), # Translators: This is the name of a key on the keyboard. - 'numpadDelete': _("numpad delete"), + 'numpaddelete': _("numpad delete"), # Translators: This is the name of a key on the keyboard. - 'leftArrow': _("left arrow"), + 'leftarrow': _("left arrow"), # Translators: This is the name of a key on the keyboard. - 'rightArrow': _("right arrow"), + 'rightarrow': _("right arrow"), # Translators: This is the name of a key on the keyboard. - 'upArrow': _("up arrow"), + 'uparrow': _("up arrow"), # Translators: This is the name of a key on the keyboard. - 'downArrow': _("down arrow"), + 'downarrow': _("down arrow"), # Translators: This is the name of a key on the keyboard. 'applications': _("applications"), # Translators: This is the name of a key on the keyboard. - 'numLock': _("num lock"), + 'numlock': _("num lock"), # Translators: This is the name of a key on the keyboard. - 'printScreen': _("print screen"), + 'printscreen': _("print screen"), # Translators: This is the name of a key on the keyboard. - 'scrollLock': _("scroll lock"), + 'scrolllock': _("scroll lock"), # Translators: This is the name of a key on the keyboard. 'numpad4': _("numpad 4"), # Translators: This is the name of a key on the keyboard. @@ -108,29 +108,29 @@ localizedKeyLabels = { # Translators: This is the name of a key on the keyboard. 'numpad5': _("numpad 5"), # Translators: This is the name of a key on the keyboard. - 'numpadDivide': _("numpad divide"), + 'numpaddivide': _("numpad divide"), # Translators: This is the name of a key on the keyboard. - 'numpadMultiply': _("numpad multiply"), + 'numpadmultiply': _("numpad multiply"), # Translators: This is the name of a key on the keyboard. - 'numpadMinus': _("numpad minus"), + 'numpadminus': _("numpad minus"), # Translators: This is the name of a key on the keyboard. - 'numpadPlus': _("numpad plus"), + 'numpadplus': _("numpad plus"), # Translators: This is the name of a key on the keyboard. - 'leftControl': _("left control"), + 'leftcontrol': _("left control"), # Translators: This is the name of a key on the keyboard. - 'rightControl': _("right control"), + 'rightcontrol': _("right control"), # Translators: This is the name of a key on the keyboard. - 'leftWindows': _("left windows"), + 'leftwindows': _("left windows"), # Translators: This is the name of a key on the keyboard. - 'leftShift': _("left shift"), + 'leftshift': _("left shift"), # Translators: This is the name of a key on the keyboard. - 'rightShift': _("right shift"), + 'rightshift': _("right shift"), # Translators: This is the name of a key on the keyboard. - 'leftAlt': _("left alt"), + 'leftalt': _("left alt"), # Translators: This is the name of a key on the keyboard. - 'rightAlt': _("right alt"), + 'rightalt': _("right alt"), # Translators: This is the name of a key on the keyboard. - 'rightWindows': _("right windows"), + 'rightwindows': _("right windows"), # Translators: This is the name of a key on the keyboard. 'break': _("break"), # Translators: This is the name of a key on the keyboard. diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index ed7090b..139eed2 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -8,6 +8,7 @@ """Keyboard support""" import time +import re import wx import winUser import vkCodes @@ -252,12 +253,16 @@ class KeyboardInputGesture(inputCore.InputGesture): NORMAL_MODIFIER_KEYS = { winUser.VK_LCONTROL: winUser.VK_CONTROL, winUser.VK_RCONTROL: winUser.VK_CONTROL, + winUser.VK_CONTROL: None, winUser.VK_LSHIFT: winUser.VK_SHIFT, winUser.VK_RSHIFT: winUser.VK_SHIFT, + winUser.VK_SHIFT: None, winUser.VK_LMENU: winUser.VK_MENU, winUser.VK_RMENU: winUser.VK_MENU, + winUser.VK_MENU: None, winUser.VK_LWIN: VK_WIN, winUser.VK_RWIN: VK_WIN, + VK_WIN: None, } #: All possible toggle key vk codes. @@ -352,7 +357,7 @@ class KeyboardInputGesture(inputCore.InputGesture): # Translators: Reported for an unknown key press. # %s will be replaced with the key code. _("unknown %s") % key[8:] if key.startswith("unknown_") - else localizedKeyLabels.get(key, key) for key in self._keyNamesInDisplayOrder) + else localizedKeyLabels.get(key.lower(), key) for key in self._keyNamesInDisplayOrder) def _get_identifiers(self): keyNames = set(self.modifierNames) @@ -407,7 +412,7 @@ class KeyboardInputGesture(inputCore.InputGesture): toggleState = winUser.getKeyState(self.vkCode) & 1 key = self.mainKeyName ui.message(u"{key} {state}".format( - key=localizedKeyLabels.get(key, key), + key=localizedKeyLabels.get(key.lower(), key), state=_("on") if toggleState else _("off"))) def send(self): @@ -481,3 +486,45 @@ class KeyboardInputGesture(inputCore.InputGesture): raise ValueError return cls(keys[:-1], vk, 0, ext) + + RE_IDENTIFIER = re.compile(r"^kb(?:\((.+?)\))?:(.*)$") + @classmethod + def getDisplayTextForIdentifier(cls, identifier): + layout, keys = cls.RE_IDENTIFIER.match(identifier).groups() + dispSource = None + if layout: + try: + # Translators: Used when describing keys on the system keyboard with a particular layout. + # %s is replaced with the layout name. + # For example, in English, this might produce "laptop keyboard". + dispSource = _("%s keyboard") % cls.LAYOUTS[layout] + except KeyError: + pass + if not dispSource: + # Translators: Used when describing keys on the system keyboard applying to all layouts. + dispSource = _("keyboard, all layouts") + + keys = set(keys.split("+")) + names = [] + main = None + try: + # If present, the NVDA key should appear first. + keys.remove("nvda") + names.append("NVDA") + except KeyError: + pass + for key in keys: + label = localizedKeyLabels.get(key, key) + vk = vkCodes.byName.get(key) + # vkCodes.byName values are (vk, ext) + if vk: + vk = vk[0] + if vk in cls.NORMAL_MODIFIER_KEYS: + names.append(label) + else: + # The main key must be last, so handle that outside the loop. + main = label + names.append(main) + return dispSource, "+".join(names) + +inputCore.registerGestureSource("kb", KeyboardInputGesture) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 73fdc5f..d07d9ec 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1057,6 +1057,26 @@ Using the Level field, you can adjust the lowest symbol level at which this symb When you are finished, press the OK button to save your changes or the Cancel button to discard them. ++++ Input Gestures (NVDA+control+i) +++ +In this dialog, you can customize the input gestures (keys on the keyboard, buttons on a braille display, etc.) for NVDA commands. + +Only commands that are applicable immediately before the dialog is opened are shown. +Therefore, you should generally open this dialog using its key command so that all commands applicable to what you were just doing are shown. +For example, if you want to customize commands related to browse mode, you should press the key command to open the Input Gestures dialog while you are in browse mode. + +The tree in this dialog lists all of the applicable NVDA commands grouped by category. +Any gestures associated with a command are listed beneath the command. + +To add an input gesture to a command, select the command and press the Add button. +Then, perform the input gesture you wish to associate; e.g. press a key on the keyboard or a button on a braille display. +Often, a gesture can be interpreted in more than one way. +For example, if you pressed a key on the keyboard, you may wish it to be specific to the current keyboard layout (e.g. desktop or laptop) or you may wish it to apply for all layouts. +In this case, a menu will appear allowing you to select the desired option. + +To remove a gesture from a command, select the gesture and press the Remove button. + +When you are finished making changes, press the OK button to save them or the Cancel button to discard them. + ++ Saving and Reloading the configuration ++ By default NVDA will automatically save your settings on exit. Note, however, that this option can be changed under the general options in the preferences menu. @@ -1814,79 +1834,6 @@ Please see the [BRLTTY key tables documentation http://mielke.cc/brltty/doc/driv + Advanced Topics + -++ Remapping Key Assignments and Other Input Gestures ++ -Users are able to provide or override mappings of input gestures (such as key presses) to scripts in a special file in the user's NVDA configuration directory. -This file is called gestures.ini. - -This file uses standard ini syntax. -The file may contain multiple sections and each section may have one or more entries. - -Each section provides mappings for scripts in a particular Python module and class inside NVDA. -- The section name should be the Python module and class separated by a dot (.). -- The key of each entry is the name of the script to which input gestures should be bound. -Alternatively, you can use None to unbind input gestures from a script to which they were previously bound. -Each entry key can only be listed once per section, including None. -- The entry value is a comma (,) separated list of gesture identifiers for the input gestures that should be bound. -Gesture identifiers ending in a comma must be enclosed in quotes (" or '). -- - -Gesture identifiers consist of a two letter device code; a sub-device, layout or mode in brackets; a colon; and then a type-specific string identifying the actual input, such as key or touch gesture names. -- For keyboard gestures, the device code is kb. -The part in brackets is the keyboard layout and is optional. -If not specified, the gesture will apply to all keyboard layouts. -The string after the colon is one or more key names separated by a plus (+) sign. -- For braille display gestures, the device code is br. -The part in brackets identifies the specific braille display and is mandatory. -The string after the colon is one or more key names separated by a plus (+) sign. -- For touch gestures, the device code is ts. -The part in brackets is the touch mode and is optional. -The string after the colon is a touch gesture; e.g. double_tap, 2fingerflickUp or 3finger_tap. -- - -In order to discover gesture identifiers, script names and the class and module in which they are contained, you can: -+ Turn on Input Help. -+ Activate the gesture (press the key, touch the screen, etc.). -+ Turn off input help. -+ Activate View log in the NVDA Tools menu. -+ Examine the recent log entries. -One of these should provide information about the input gesture you sent, including the module.class and script if it is bound to one. -+ - -Following is an example of how you could bind NVDA+shift+t to the date time script. - -To find out the correct script name and module.class for date time, you would turn on Input Help and press NVDA+f12 (as this is the current gesture for the date time script). -You would then turn off Input Help and examine the log viewer. - -Towards the bottom, you would see: - -``` -INFO - inputCore.InputManager._handleInputHelp (13:17:22): -Input help: gesture kb(desktop):NVDA+f12, bound to script dateTime on globalCommands.GlobalCommands -``` - -From this, you can see that the script name is dateTime and the module.class is globalCommands.GlobalCommands. - -If the file does not yet exist, you would create a text file called gestures.ini in the user configuration directory and add the following content: - -``` -[globalCommands.GlobalCommands] - dateTime = kb:NVDA+shift+t -``` - -This would bind the key press NVDA+shift+t (in any keyboard layout) to the dateTime script. - -Note that the original NVDA+f12 binding would still work. -If you wanted to remove this binding, you would add the following line: - -``` - None = kb:NVDA+f12 -``` - -Although you are free to have scripts bound to any available key, it may be problematic to use the alt key on the keyboard. -NVDA still sends modifier keys (such as shift, control and alt) to the Operating System, even if they eventuate in a script. -Thus, if you do use alt in a gesture, pressing this key combination may activate the menu bar, as well as executing the script. -Therefore, it is probably best to just use Shift, control and the NVDA modifier key as modifiers. - ++ Advanced Customization of Symbol Pronunciation ++ It is possible to customize the pronunciation of punctuation and other symbols beyond what can be done using the [Punctuation/symbol pronunciation #SymbolPronunciation] dialog. For example, you can specify whether the raw symbol should be sent to the synthesizer (e.g. to cause a pause or change in inflection) and you can add custom symbols. https://bitbucket.org/nvdaaddonteam/nvda/commits/3e2585ace020/ Changeset: 3e2585ace020 Branch: None User: jteh Date: 2013-09-09 11:39:04 Summary: ProfilesDialog: Rename (none) to (normal configuration) for clarity. Also fix wording concerning this in the User Guide. Affected #: 2 files diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index cec5aa2..e23ebc9 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -33,9 +33,9 @@ class ProfilesDialog(wx.Dialog): sizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: The label of the profile list in the Configuration Profiles dialog. sizer.Add(wx.StaticText(self, label=_("&Profile"))) - # Translators: Indicates that no configuration profile is selected. - # In this case, the user's normal configuration will be used. - profiles = [_("(none)")] + # Translators: The item to select the user's normal configuration + # in the profile list in the Configuration Profiles dialog. + profiles = [_("(normal configuration)")] profiles.extend(config.conf.listProfiles()) item = self.profileList = wx.Choice(self, choices=profiles) item.Bind(wx.EVT_CHOICE, self.onProfileListChoice) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 9f70b08..276e262 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1092,7 +1092,7 @@ You can manually activate the selected profile by pressing the Activate button. Once a profile is manually activated, any settings in that profile override the settings in your normal configuration. Also, any settings you change will be saved in that profile. -To return to your normal configuration, select "(none)" in the profile list and activate it. +To return to your normal configuration, select "(normal configuration)" in the profile list and press the Activate button. You can create a new profile by pressing the New button. To rename or delete a profile, press the Rename or Delete buttons, respectively. https://bitbucket.org/nvdaaddonteam/nvda/commits/22da85a3c9a3/ Changeset: 22da85a3c9a3 Branch: None User: jteh Date: 2013-09-09 12:11:02 Summary: ProfilesDialog: Rename Activate button to Activate/edit to make it clearer that this means you will also be editing the profile. Affected #: 2 files diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index e23ebc9..41c888b 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -48,8 +48,8 @@ class ProfilesDialog(wx.Dialog): mainSizer.Add(sizer) sizer = wx.BoxSizer(wx.HORIZONTAL) - # Translators: The label of a button to activate the selected profile. - item = wx.Button(self, label=_("&Activate")) + # Translators: The label of a button to activate (and therefore also edit) the selected profile. + item = wx.Button(self, label=_("&Activate/edit")) item.Bind(wx.EVT_BUTTON, self.onActivate) sizer.Add(item) self.AffirmativeId = item.Id diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 276e262..5d6eb14 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1088,11 +1088,11 @@ You can also do this using a key command: The first control in this dialog is the profile list from which you can select one of the available profiles. When you open the dialog, the last manually activated profile is selected. -You can manually activate the selected profile by pressing the Activate button. +You can manually activate the selected profile by pressing the Activate/edit button. Once a profile is manually activated, any settings in that profile override the settings in your normal configuration. Also, any settings you change will be saved in that profile. -To return to your normal configuration, select "(normal configuration)" in the profile list and press the Activate button. +To return to your normal configuration, select "(normal configuration)" in the profile list and press the Activate/edit button. You can create a new profile by pressing the New button. To rename or delete a profile, press the Rename or Delete buttons, respectively. https://bitbucket.org/nvdaaddonteam/nvda/commits/4ea8af1cfb6a/ Changeset: 4ea8af1cfb6a Branch: None User: jteh Date: 2013-09-09 15:33:02 Summary: ProfilesDialog: When a new profile is created and thus selected, make sure the Triggers, Rename and Delete buttons always get enabled. Previously, they weren't enabled if (normal configuration) was selected before creating the new profile. Affected #: 1 file diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index 41c888b..3006ec1 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -129,6 +129,7 @@ class ProfilesDialog(wx.Dialog): return self.profileList.Append(name) self.profileList.Selection = self.profileList.Count - 1 + self.onProfileListChoice(None) self.profileList.SetFocus() def onDelete(self, evt): https://bitbucket.org/nvdaaddonteam/nvda/commits/fd48169ab214/ Changeset: fd48169ab214 Branch: None User: jteh Date: 2013-09-10 04:48:08 Summary: TriggersDialog: Don't disable the say all option if it is already associated with another profile. Instead, warn the user if they enable it that it will be removed from the other profile. Affected #: 2 files diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index 3006ec1..4f000ac 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -232,9 +232,6 @@ class TriggersDialog(wx.Dialog): item = self.sayAllToggle = wx.CheckBox(self, label=_("&Say all")) if "sayAll" in triggers: item.Value = True - elif "sayAll" in config.conf["profileTriggers"]: - # This trigger is associated with another profile already. - item.Disable() mainSizer.Add(item) item = wx.Button(self, wx.ID_CLOSE, label=_("&Close")) @@ -248,13 +245,23 @@ class TriggersDialog(wx.Dialog): def onClose(self, evt): triggers = config.conf["profileTriggers"] + try: + trigOnOther = triggers["sayAll"] != self.profile + except KeyError: + trigOnOther = False if self.sayAllToggle.Value: + if trigOnOther and gui.messageBox( + # Translators: A confirmation prompt that might be displayed when closing the configuration profile triggers dialog. + _("Say all is already associated with another profile.\n" + "If you continue, it will be removed from the other profile and associated with this one.\n" + "Are you sure you want to continue?"), + # Translators: The title of a confirmation prompt. + _("Confirm"), wx.YES | wx.NO | wx.ICON_WARNING, self + ) == wx.NO: + return triggers["sayAll"] = self.profile - elif self.sayAllToggle.Enabled: - try: - del triggers["sayAll"] - except KeyError: - pass + elif not trigOnOther: + del triggers["sayAll"] self.Parent.Enable() self.Destroy() diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 5d6eb14..78d8130 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1104,7 +1104,6 @@ Pressing the Triggers button in the Configuration Profiles dialog allows you to If there is a manually activated profile, other profiles can still be activated due to triggers, but any settings in the manually activated profile will override them. A specific trigger can only be associated with a single profile, but a single profile can be associated with several triggers. -If a trigger is already associated with another profile, its option won't be available. The Applications grouping allows you to specify applications that should trigger the profile when you switch to them. Applications are specified by the name of their executable file (without the extension), which might be different to the actual name of the application. https://bitbucket.org/nvdaaddonteam/nvda/commits/a930d33f8f14/ Changeset: a930d33f8f14 Branch: None User: jteh Date: 2013-09-10 07:42:49 Summary: Refactor app module lose/gain focus behaviour. Previously, app module lose/gainFocus events and triggers occurred in an indeterminate order. Now, they occur ordered from the outermost app module for gain focus and the innermost app module for lose focus. Also, all trigger exits and enters occur between the loseFocus and gainFocus events. This means that they can be optimised so that the profile switch is only handled once, which may be a lot faster because it can eliminate redundant synth/braille display switching. ConfigManager.atomicProfileSwitch was added to facilitate the switching optimisation. Affected #: 3 files diff --git a/source/api.py b/source/api.py index 31ea63d..1c4285d 100644 --- a/source/api.py +++ b/source/api.py @@ -66,7 +66,7 @@ Before overriding the last object, this function calls event_loseFocus on the ob #add the old focus to the old focus ancestors, but only if its not None (is none at NVDA initialization) if globalVars.focusObject: oldFocusLine.append(globalVars.focusObject) - oldAppModuleSet=set(o.appModule for o in oldFocusLine if o and o.appModule) + oldAppModules=[o.appModule for o in oldFocusLine if o and o.appModule] ancestors=[] tempObj=obj matchedOld=False @@ -106,13 +106,10 @@ Before overriding the last object, this function calls event_loseFocus on the ob container=tempObj.container tempObj.container=container # Cache the parent. tempObj=container + newAppModules=[o.appModule for o in ancestors if o and o.appModule] #Remove the final new ancestor as this will be the new focus object del ancestors[-1] - newAppModuleSet=set(o.appModule for o in ancestors+[obj] if o and o.appModule) - for removedMod in oldAppModuleSet-newAppModuleSet: - appModuleHandler.handleLoseFocus(removedMod) - for addedMod in newAppModuleSet-oldAppModuleSet: - appModuleHandler.handleGainFocus(addedMod) + appModuleHandler.handleAppSwitch(oldAppModules,newAppModules) try: treeInterceptorHandler.cleanup() except watchdog.CallCancelled: diff --git a/source/appModuleHandler.py b/source/appModuleHandler.py index 1efaeae..ed73446 100644 --- a/source/appModuleHandler.py +++ b/source/appModuleHandler.py @@ -177,20 +177,51 @@ def terminate(): log.exception("Error terminating app module %r" % app) runningTable.clear() -def handleLoseFocus(mod): - if not mod.sleepMode and hasattr(mod,'event_appModule_loseFocus'): - try: - mod.event_appModule_loseFocus() - except watchdog.CallCancelled: - pass - mod._configProfileTrigger.exit() - mod._configProfileTrigger = None +def handleAppSwitch(oldMods, newMods): + newModsSet = set(newMods) + processed = set() + nextStage = [] + + # Determine all apps that are losing focus and fire appropriate events. + for mod in reversed(oldMods): + if mod in processed: + # This app has already been handled. + continue + processed.add(mod) + if mod in newModsSet: + # This app isn't losing focus. + continue + processed.add(mod) + # This app is losing focus. + nextStage.append(mod) + if not mod.sleepMode and hasattr(mod,'event_appModule_loseFocus'): + try: + mod.event_appModule_loseFocus() + except watchdog.CallCancelled: + pass + + with config.conf.atomicProfileSwitch(): + # Exit triggers for apps that lost focus. + for mod in nextStage: + mod._configProfileTrigger.exit() + mod._configProfileTrigger = None + + nextStage = [] + # Determine all apps that are gaining focus and enter triggers. + for mod in newMods: + if mod in processed: + # This app isn't gaining focus or it has already been handled. + continue + processed.add(mod) + # This app is gaining focus. + nextStage.append(mod) + trigger = mod._configProfileTrigger = AppProfileTrigger(mod.appName) + trigger.enter() -def handleGainFocus(mod): - trigger = mod._configProfileTrigger = AppProfileTrigger(mod.appName) - trigger.enter() - if not mod.sleepMode and hasattr(mod,'event_appModule_gainFocus'): - mod.event_appModule_gainFocus() + # Fire appropriate events for apps gaining focus. + for mod in nextStage: + if not mod.sleepMode and hasattr(mod,'event_appModule_gainFocus'): + mod.event_appModule_gainFocus() #base class for appModules class AppModule(baseObject.ScriptableObject): diff --git a/source/config/__init__.py b/source/config/__init__.py index b78f135..d4e335e 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -9,6 +9,7 @@ import os import sys from cStringIO import StringIO import itertools +import contextlib from configobj import ConfigObj, ConfigObjError from validate import Validator from logHandler import log @@ -408,11 +409,14 @@ class ConfigManager(object): self.editProfileIndex = None self.validator = Validator() self.rootSection = None + self._shouldHandleProfileSwitch = True self._initBaseConf() #: The names of all profiles that have been modified since they were last saved. self._dirtyProfiles = set() def _handleProfileSwitch(self): + if not self._shouldHandleProfileSwitch: + return init = self.rootSection is None # Reset the cache. self.rootSection = AggregatedSection(self, (), self.spec, self.profiles) @@ -675,6 +679,22 @@ class ConfigManager(object): self.profiles.remove(profile) self._handleProfileSwitch() + @contextlib.contextmanager + def atomicProfileSwitch(self): + """Indicate that multiple profile switches should be treated as one. + This is useful when multiple triggers may be exited/entered at once; + e.g. when switching applications. + While multiple switches aren't harmful, they might take longer; + e.g. unnecessarily switching speech synthesizers or braille displays. + This is a context manager to be used with the C{with} statement. + """ + self._shouldHandleProfileSwitch = False + try: + yield + finally: + self._shouldHandleProfileSwitch = True + self._handleProfileSwitch() + class AggregatedSection(object): """A view of a section of configuration which aggregates settings from all active profiles. """ https://bitbucket.org/nvdaaddonteam/nvda/commits/1abb8b9365b1/ Changeset: 1abb8b9365b1 Branch: None User: jteh Date: 2013-09-11 08:01:58 Summary: Fix severe truncation of speech (even up to several lines) with say all when a profile using a different synth is triggered. Say all needs to wait for the synth to actually finish speaking before it exits the trigger. Affected #: 1 file diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py index f26321e..1a3043f 100644 --- a/source/sayAllHandler.py +++ b/source/sayAllHandler.py @@ -146,7 +146,7 @@ def readTextHelper_generator(cursor): if cursor!=CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]: api.setReviewPosition(updater) elif not keepReading and lastReceivedIndex==lastSentIndex: - # All text has been spoken. + # All text has been sent to the synth. # Turn the page and start again if the object supports it. if isinstance(reader.obj,textInfos.DocumentWithPageTurns): try: @@ -163,6 +163,20 @@ def readTextHelper_generator(cursor): yield yield + # Wait until the synth has actually finished speaking. + # Otherwise, if there is a triggered profile with a different synth, + # we will switch too early and truncate speech (even up to several lines). + # Send another index and wait for it. + index=lastSentIndex+1 + speech.speak([speech.IndexCommand(index)]) + while speech.getLastSpeechIndex()<index: + yield + yield + # Some synths say they've handled the index slightly sooner than they actually have, + # so wait a bit longer. + for i in xrange(30): + yield + class SayAllProfileTrigger(config.ProfileTrigger): """A configuration profile trigger for when say all is in progress. """ https://bitbucket.org/nvdaaddonteam/nvda/commits/eeadf3ca9de0/ Changeset: eeadf3ca9de0 Branch: None User: jteh Date: 2013-09-12 03:21:12 Summary: Fix an issue where nothing was spoken if a single key (e.g. an arrow key) was pressed during say all and say all used a different speech synth. This occurred because the say all trigger was being exited after the text in response to the key was spoken instead of before. The reason was that a reference to the say all generator was being held longer than it should in queueHandler.pumpall, so Python couldn't destroy the generator until after the queue had been pumped. Now, pumpAll only holds the reference as long as it needs to. Affected #: 1 file diff --git a/source/queueHandler.py b/source/queueHandler.py index 4005245..0db66da 100644 --- a/source/queueHandler.py +++ b/source/queueHandler.py @@ -73,4 +73,6 @@ def pumpAll(): except: log.exception("error in generator %d"%ID) del generators[ID] + # Lose our reference so Python can destroy the generator if appropriate. + del gen flushQueue(eventQueue) https://bitbucket.org/nvdaaddonteam/nvda/commits/c9cdfbb81a34/ Changeset: c9cdfbb81a34 Branch: None User: mdcurran Date: 2013-09-12 03:21:58 Summary: Merge branch 'master' into next Affected #: 1 file diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index b2e7147..43fa530 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -11,6 +11,7 @@ - They can also be activated automatically due to triggers such as switching to a particular application. - See the User Guide for details. - Form fields are now reported in Microsoft word documents. (#2295) +- NVDA can now announce revision information in Microsoft Word when Track Changes is enabled. Note that Report editor revisions in NVDA's document settings dialog (off by default) must be enabled also for them to be announced. (#1670) == Bug Fixes == https://bitbucket.org/nvdaaddonteam/nvda/commits/201a4ecf5a20/ Changeset: 201a4ecf5a20 Branch: None User: jteh Date: 2013-09-12 03:42:42 Summary: Merge branch 'master' into t1532 Affected #: 9 files diff --git a/nvdaHelper/remote/winword.cpp b/nvdaHelper/remote/winword.cpp index f4ddbba..07ac388 100644 --- a/nvdaHelper/remote/winword.cpp +++ b/nvdaHelper/remote/winword.cpp @@ -36,6 +36,10 @@ using namespace std; #define wdDISPID_SELECTION_SETRANGE 100 #define wdDISPID_SELECTION_STARTISACTIVE 404 #define wdDISPID_RANGE_INRANGE 126 +#define wdDISPID_RANGE_DUPLICATE 6 +#define wdDISPID_RANGE_REVISIONS 150 +#define wdDISPID_REVISIONS_ITEM 0 +#define wdDISPID_REVISION_TYPE 4 #define wdDISPID_RANGE_STORYTYPE 7 #define wdDISPID_RANGE_MOVEEND 111 #define wdDISPID_RANGE_COLLAPSE 101 @@ -154,6 +158,7 @@ using namespace std; #define formatConfig_reportComments 4096 #define formatConfig_reportHeadings 8192 #define formatConfig_reportLanguage 16384 +#define formatConfig_reportRevisions 32768 #define formatConfig_fontFlags (formatConfig_reportFontName|formatConfig_reportFontSize|formatConfig_reportFontAttributes|formatConfig_reportColor) #define formatConfig_initialFormatFlags (formatConfig_reportPage|formatConfig_reportLineNumber|formatConfig_reportTables|formatConfig_reportHeadings) @@ -288,6 +293,25 @@ int generateHeadingXML(IDispatch* pDispatchRange, wostringstream& XMLStream) { return 1; } +int getRevisionType(IDispatch* pDispatchOrigRange) { + IDispatchPtr pDispatchRange=NULL; + //If range is not duplicated here, revisions collection represents revisions at the start of the range when it was first created + if(_com_dispatch_raw_propget(pDispatchOrigRange,wdDISPID_RANGE_DUPLICATE,VT_DISPATCH,&pDispatchRange)!=S_OK||!pDispatchRange) { + return 0; + } + IDispatchPtr pDispatchRevisions=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_REVISIONS,VT_DISPATCH,&pDispatchRevisions)!=S_OK||!pDispatchRevisions) { + return 0; + } + IDispatchPtr pDispatchRevision=NULL; + if(_com_dispatch_raw_method(pDispatchRevisions,wdDISPID_REVISIONS_ITEM,DISPATCH_METHOD,VT_DISPATCH,&pDispatchRevision,L"\x0003",1)!=S_OK||!pDispatchRevision) { + return 0; + } + long revisionType=0; + _com_dispatch_raw_propget(pDispatchRevision,wdDISPID_REVISION_TYPE,VT_I4,&revisionType); + return revisionType; +} + int getHyperlinkCount(IDispatch* pDispatchRange) { IDispatchPtr pDispatchHyperlinks=NULL; int count=0; @@ -441,6 +465,10 @@ void generateXMLAttribsForFormatting(IDispatch* pDispatchRange, int startOffset, if((formatConfig&formatConfig_reportLinks)&&getHyperlinkCount(pDispatchRange)>0) { formatAttribsStream<<L"link=\"1\" "; } + if(formatConfig&formatConfig_reportRevisions) { + long revisionType=getRevisionType(pDispatchRange); + formatAttribsStream<<L"wdRevisionType=\""<<revisionType<<L"\" "; + } if(formatConfig&formatConfig_reportStyle) { IDispatchPtr pDispatchStyle=NULL; if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_STYLE,VT_DISPATCH,&pDispatchStyle)==S_OK&&pDispatchStyle) { @@ -634,6 +662,7 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) //Walk the range from the given start to end by characterFormatting or word units //And grab any text and formatting and generate appropriate xml do { + int curDisabledFormatConfig=0; //generated form field xml if in a form field //Also automatically extends the range and chunkEndOffset to the end of the field BOOL isFormField=generateFormFieldXML(pDispatchRange,XMLStream,chunkEndOffset); @@ -666,6 +695,10 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) noteCharOffset=i; if(i==0) text[i]=L' '; break; + } else if(text[i]==L'\x0007'&&(chunkEndOffset-chunkStartOffset)==1) { + text[i]=L'\0'; + //Collecting revision info does not work on cell delimiters + curDisabledFormatConfig|=formatConfig_reportRevisions; } } isNoteChar=(noteCharOffset==0); @@ -693,9 +726,9 @@ void winword_getTextInRange_helper(HWND hwnd, winword_getTextInRange_args* args) } _com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_END,VT_I4,&chunkEndOffset); } - XMLStream<<L"<text "; + XMLStream<<L"<text _startOffset=\""<<chunkStartOffset<<L"\" _endOffset=\""<<chunkEndOffset<<L"\" "; XMLStream<<initialFormatAttribsStream.str(); - generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,formatConfig,XMLStream); + generateXMLAttribsForFormatting(pDispatchRange,chunkStartOffset,chunkEndOffset,formatConfig&(~curDisabledFormatConfig),XMLStream); XMLStream<<L">"; if(firstLoop) { formatConfig&=(~formatConfig_reportLists); diff --git a/source/NVDAObjects/window/winword.py b/source/NVDAObjects/window/winword.py index 38ee650..9106ae6 100755 --- a/source/NVDAObjects/window/winword.py +++ b/source/NVDAObjects/window/winword.py @@ -95,6 +95,47 @@ wdContentControlDate=6 wdContentControlGroup=7 wdContentControlCheckBox=8 +wdNoRevision=0 +wdRevisionInsert=1 +wdRevisionDelete=2 +wdRevisionProperty=3 +wdRevisionParagraphNumber=4 +wdRevisionDisplayField=5 +wdRevisionReconcile=6 +wdRevisionConflict=7 +wdRevisionStyle=8 +wdRevisionReplace=9 +wdRevisionParagraphProperty=10 +wdRevisionTableProperty=11 +wdRevisionSectionProperty=12 +wdRevisionStyleDefinition=13 +wdRevisionMovedFrom=14 +wdRevisionMovedTo=15 +wdRevisionCellInsertion=16 +wdRevisionCellDeletion=17 +wdRevisionCellMerge=18 + +wdRevisionTypeLabels={ + wdRevisionInsert:_("insertion"), + wdRevisionDelete:_("deletion"), + wdRevisionProperty:_("property"), + wdRevisionParagraphNumber:_("paragraph number"), + wdRevisionDisplayField:_("display field"), + wdRevisionReconcile:_("reconcile"), + wdRevisionConflict:_("conflict"), + wdRevisionStyle:_("style"), + wdRevisionReplace:_("replace"), + wdRevisionParagraphProperty:_("paragraph property"), + wdRevisionTableProperty:_("table property"), + wdRevisionSectionProperty:_("section property"), + wdRevisionStyleDefinition:_("style definition"), + wdRevisionMovedFrom:_("moved from"), + wdRevisionMovedTo:_("moved to"), + wdRevisionCellInsertion:_("cell insertion"), + wdRevisionCellDeletion:_("cell deletion"), + wdRevisionCellMerge:_("cell merge"), +} + storyTypeLocalizedLabels={ wdCommentsStory:_("Comments"), wdEndnotesStory:_("Endnotes"), @@ -159,6 +200,7 @@ formatConfigFlagsMap={ "reportComments":4096, "reportHeadings":8192, "autoLanguageSwitching":16384, + "reportRevisions":32768, } class WordDocumentTextInfo(textInfos.TextInfo): @@ -205,6 +247,7 @@ class WordDocumentTextInfo(textInfos.TextInfo): raise NotImplementedError("position: %s"%position) def getTextWithFields(self,formatConfig=None): + extraDetail=formatConfig.get('extraDetail',False) if formatConfig else False if not formatConfig: formatConfig=config.conf['documentFormatting'] formatConfig['autoLanguageSwitching']=config.conf['speech'].get('autoLanguageSwitching',False) @@ -223,7 +266,7 @@ class WordDocumentTextInfo(textInfos.TextInfo): if isinstance(field,textInfos.ControlField): item.field=self._normalizeControlField(field) elif isinstance(field,textInfos.FormatField): - item.field=self._normalizeFormatField(field) + item.field=self._normalizeFormatField(field,extraDetail=extraDetail) elif index>0 and isinstance(item,basestring) and item.isspace(): #2047: don't expose language for whitespace as its incorrect for east-asian languages lastItem=commandList[index-1] @@ -288,7 +331,25 @@ class WordDocumentTextInfo(textInfos.TextInfo): field['role']=controlTypes.ROLE_FRAME return field - def _normalizeFormatField(self,field): + def _normalizeFormatField(self,field,extraDetail=False): + _startOffset=int(field.pop('_startOffset')) + _endOffset=int(field.pop('_endOffset')) + revisionType=int(field.pop('wdRevisionType',0)) + revisionLabel=wdRevisionTypeLabels.get(revisionType,None) + if revisionLabel: + if extraDetail: + try: + r=self.obj.WinwordSelectionObject.range + r.setRange(_startOffset,_endOffset) + rev=r.revisions.item(1) + author=rev.author + date=rev.date + except COMError: + author=_("unknown author") + date=_("unknown date") + field['revision']=_("{revisionType} by {revisionAuthor} on {revisionDate}").format(revisionType=revisionLabel,revisionAuthor=author,revisionDate=date) + else: + field['revision']=revisionLabel color=field.pop('color',None) if color is not None: field['color']=colors.RGB.fromCOLORREF(int(color)) diff --git a/source/config/__init__.py b/source/config/__init__.py index 6bc5d65..5e0a4ee 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -144,6 +144,7 @@ confspec = ConfigObj(StringIO( reportFontName = boolean(default=false) reportFontSize = boolean(default=false) reportFontAttributes = boolean(default=false) + reportRevisions = boolean(default=false) reportColor = boolean(default=False) reportAlignment = boolean(default=false) reportStyle = boolean(default=false) diff --git a/source/globalCommands.py b/source/globalCommands.py index 4221545..eb5f3c2 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -833,7 +833,7 @@ class GlobalCommands(ScriptableObject): def script_reportFormatting(self,gesture): formatConfig={ "detectFormatAfterCursor":False, - "reportFontName":True,"reportFontSize":True,"reportFontAttributes":True,"reportColor":True, + "reportFontName":True,"reportFontSize":True,"reportFontAttributes":True,"reportColor":True,"reportRevisions":False, "reportStyle":True,"reportAlignment":True,"reportSpellingErrors":True, "reportPage":False,"reportLineNumber":False,"reportTables":False, "reportLinks":False,"reportHeadings":False,"reportLists":False, diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index d96fd29..3dadf3e 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1011,6 +1011,11 @@ class DocumentFormattingDialog(SettingsDialog): settingsSizer.Add(self.colorCheckBox,border=10,flag=wx.BOTTOM) # Translators: This is the label for a checkbox in the # document formatting settings dialog. + self.revisionsCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Report &editor revisions")) + self.revisionsCheckBox.SetValue(config.conf["documentFormatting"]["reportRevisions"]) + settingsSizer.Add(self.revisionsCheckBox,border=10,flag=wx.BOTTOM) + # Translators: This is the label for a checkbox in the + # document formatting settings dialog. self.styleCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Report st&yle")) self.styleCheckBox.SetValue(config.conf["documentFormatting"]["reportStyle"]) settingsSizer.Add(self.styleCheckBox,border=10,flag=wx.BOTTOM) @@ -1090,6 +1095,7 @@ class DocumentFormattingDialog(SettingsDialog): config.conf["documentFormatting"]["reportFontSize"]=self.fontSizeCheckBox.IsChecked() config.conf["documentFormatting"]["reportFontAttributes"]=self.fontAttrsCheckBox.IsChecked() config.conf["documentFormatting"]["reportColor"]=self.colorCheckBox.IsChecked() + config.conf["documentFormatting"]["reportRevisions"]=self.revisionsCheckBox.IsChecked() config.conf["documentFormatting"]["reportAlignment"]=self.alignmentCheckBox.IsChecked() config.conf["documentFormatting"]["reportStyle"]=self.styleCheckBox.IsChecked() config.conf["documentFormatting"]["reportSpellingErrors"]=self.spellingErrorsCheckBox.IsChecked() diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index 20709ad..9780548 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -146,6 +146,13 @@ def internal_keyDownEvent(vkCode,scanCode,extended,injected): try: inputCore.manager.executeGesture(gesture) trappedKeys.add(keyCode) + if canModifiersPerformAction(gesture.generalizedModifiers): + # #3472: These modifiers can perform an action if pressed alone + # and we've just consumed the main key. + # Send special reserved vkcode (0xff) to at least notify the app's key state that something happendd. + # This allows alt and windows to be bound to scripts and + # stops control+shift from switching keyboard layouts in cursorManager selection scripts. + KeyboardInputGesture((),0xff,0,False).send() return False except inputCore.NoInputGestureAction: if gesture.isNVDAModifierKey: @@ -213,6 +220,30 @@ def getInputHkl(): thread = 0 return winUser.user32.GetKeyboardLayout(thread) +def canModifiersPerformAction(modifiers): + """Determine whether given generalized modifiers can perform an action if pressed alone. + For example, alt activates the menu bar if it isn't modifying another key. + """ + if inputCore.manager.isInputHelpActive: + return False + control = shift = other = False + for vk, ext in modifiers: + if vk in (winUser.VK_MENU, VK_WIN): + # Alt activates the menu bar. + # Windows activates the Start Menu. + return True + elif vk == winUser.VK_CONTROL: + control = True + elif vk == winUser.VK_SHIFT: + shift = True + elif (vk, ext) not in trappedKeys : + # Trapped modifiers aren't relevant. + other = True + if control and shift and not other: + # Shift+control switches keyboard layouts. + return True + return False + class KeyboardInputGesture(inputCore.InputGesture): """A key pressed on the traditional system keyboard. """ diff --git a/source/speech.py b/source/speech.py index 9d8cf9f..8278fba 100755 --- a/source/speech.py +++ b/source/speech.py @@ -559,6 +559,9 @@ def speakTextInfo(info,useCache=True,formatConfig=None,unit=None,reason=controlT extraDetail=unit in (textInfos.UNIT_CHARACTER,textInfos.UNIT_WORD) if not formatConfig: formatConfig=config.conf["documentFormatting"] + if extraDetail: + formatConfig=formatConfig.copy() + formatConfig['extraDetail']=True reportIndentation=unit==textInfos.UNIT_LINE and formatConfig["reportLineIndentation"] speechSequence=[] @@ -1060,6 +1063,15 @@ def getFormatFieldSpeech(attrs,attrsCache=None,formatConfig=None,unit=None,extra # %s will be replaced with the line number. text=_("line %s")%lineNumber textList.append(text) + if formatConfig["reportRevisions"]: + revision=attrs.get("revision") + oldRevision=attrsCache.get("revision") if attrsCache is not None else None + if (revision or oldRevision is not None) and revision!=oldRevision: + # Translators: Reported when text is revised. + text=(_("revised %s"%revision) if revision + # Translators: Reported when text is not revised. + else _("unrevised")) + textList.append(text) if formatConfig["reportFontAttributes"]: bold=attrs.get("bold") oldBold=attrsCache.get("bold") if attrsCache is not None else None diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index ce9ebdf..3188ffb 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -7,12 +7,15 @@ == New Features == - Form fields are now reported in Microsoft word documents. (#2295) +- NVDA can now announce revision information in Microsoft Word when Track Changes is enabled. Note that Report editor revisions in NVDA's document settings dialog (off by default) must be enabled also for them to be announced. (#1670) == Bug Fixes == - Zend Studio now functions the same as Eclipse. (#3420) - The changed state of certain checkboxes in the Microsoft Outlook 2010 message rules dialog are now reported automatically. (#3063) - NVDA will now report the pinned state for pinned controls such as tabs in Mozilla Firefox. (#3372) +- It is now possible to bind scripts to keyboard gestures containing Alt and/or Windows keys as modifiers. Previously, if this was done, performing the script would cause the Start Menu or menu bar to be activated. (#3472) +- Selecting text in browse mode documents (e.g. using control+shift+end) no longer causes the keyboard layout to be switched on systems with multiple keyboard layouts installed. (#3472) = 2013.2 = diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 94c5221..1ae185a 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -996,6 +996,7 @@ You can configure reporting of: - Font attributes - Text alignment - Colors +- editor revisions - Text style - Spelling errors - Page numbers https://bitbucket.org/nvdaaddonteam/nvda/commits/acbd6d40e551/ Changeset: acbd6d40e551 Branch: None User: jteh Date: 2013-09-12 03:43:20 Summary: Merge branch 't1532' into next. No changes to tt1532 specific code, just merged with master. Affected #: 1 file diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 43fa530..0bcb632 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -18,6 +18,8 @@ - Zend Studio now functions the same as Eclipse. (#3420) - The changed state of certain checkboxes in the Microsoft Outlook 2010 message rules dialog are now reported automatically. (#3063) - NVDA will now report the pinned state for pinned controls such as tabs in Mozilla Firefox. (#3372) +- It is now possible to bind scripts to keyboard gestures containing Alt and/or Windows keys as modifiers. Previously, if this was done, performing the script would cause the Start Menu or menu bar to be activated. (#3472) +- Selecting text in browse mode documents (e.g. using control+shift+end) no longer causes the keyboard layout to be switched on systems with multiple keyboard layouts installed. (#3472) = 2013.2 = https://bitbucket.org/nvdaaddonteam/nvda/commits/84ba1652e329/ Changeset: 84ba1652e329 Branch: None User: mdcurran Date: 2013-09-12 03:47:17 Summary: Merge branch 'master' into next Affected #: 1 file diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 0bcb632..1d56024 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -12,6 +12,7 @@ - See the User Guide for details. - Form fields are now reported in Microsoft word documents. (#2295) - NVDA can now announce revision information in Microsoft Word when Track Changes is enabled. Note that Report editor revisions in NVDA's document settings dialog (off by default) must be enabled also for them to be announced. (#1670) +- Dropdown lists in Microsoft Excel 2003 through 2010 are now announced when opened and navigated around. (#3382) == Bug Fixes == https://bitbucket.org/nvdaaddonteam/nvda/commits/b120c93442d5/ Changeset: b120c93442d5 Branch: None User: jteh Date: 2013-09-12 03:48:49 Summary: Merge branch 't667' into next Incubates #667. Affected #: 7 files diff --git a/source/api.py b/source/api.py index 6a27064..9dd0d98 100644 --- a/source/api.py +++ b/source/api.py @@ -66,7 +66,7 @@ Before overriding the last object, this function calls event_loseFocus on the ob #add the old focus to the old focus ancestors, but only if its not None (is none at NVDA initialization) if globalVars.focusObject: oldFocusLine.append(globalVars.focusObject) - oldAppModuleSet=set(o.appModule for o in oldFocusLine if o and o.appModule) + oldAppModules=[o.appModule for o in oldFocusLine if o and o.appModule] ancestors=[] tempObj=obj matchedOld=False @@ -106,13 +106,10 @@ Before overriding the last object, this function calls event_loseFocus on the ob container=tempObj.container tempObj.container=container # Cache the parent. tempObj=container + newAppModules=[o.appModule for o in ancestors if o and o.appModule] #Remove the final new ancestor as this will be the new focus object del ancestors[-1] - newAppModuleSet=set(o.appModule for o in ancestors+[obj] if o and o.appModule) - for removedMod in oldAppModuleSet-newAppModuleSet: - appModuleHandler.handleLoseFocus(removedMod) - for addedMod in newAppModuleSet-oldAppModuleSet: - appModuleHandler.handleGainFocus(addedMod) + appModuleHandler.handleAppSwitch(oldAppModules,newAppModules) try: treeInterceptorHandler.cleanup() except watchdog.CallCancelled: diff --git a/source/appModuleHandler.py b/source/appModuleHandler.py index 1efaeae..ed73446 100644 --- a/source/appModuleHandler.py +++ b/source/appModuleHandler.py @@ -177,20 +177,51 @@ def terminate(): log.exception("Error terminating app module %r" % app) runningTable.clear() -def handleLoseFocus(mod): - if not mod.sleepMode and hasattr(mod,'event_appModule_loseFocus'): - try: - mod.event_appModule_loseFocus() - except watchdog.CallCancelled: - pass - mod._configProfileTrigger.exit() - mod._configProfileTrigger = None +def handleAppSwitch(oldMods, newMods): + newModsSet = set(newMods) + processed = set() + nextStage = [] + + # Determine all apps that are losing focus and fire appropriate events. + for mod in reversed(oldMods): + if mod in processed: + # This app has already been handled. + continue + processed.add(mod) + if mod in newModsSet: + # This app isn't losing focus. + continue + processed.add(mod) + # This app is losing focus. + nextStage.append(mod) + if not mod.sleepMode and hasattr(mod,'event_appModule_loseFocus'): + try: + mod.event_appModule_loseFocus() + except watchdog.CallCancelled: + pass + + with config.conf.atomicProfileSwitch(): + # Exit triggers for apps that lost focus. + for mod in nextStage: + mod._configProfileTrigger.exit() + mod._configProfileTrigger = None + + nextStage = [] + # Determine all apps that are gaining focus and enter triggers. + for mod in newMods: + if mod in processed: + # This app isn't gaining focus or it has already been handled. + continue + processed.add(mod) + # This app is gaining focus. + nextStage.append(mod) + trigger = mod._configProfileTrigger = AppProfileTrigger(mod.appName) + trigger.enter() -def handleGainFocus(mod): - trigger = mod._configProfileTrigger = AppProfileTrigger(mod.appName) - trigger.enter() - if not mod.sleepMode and hasattr(mod,'event_appModule_gainFocus'): - mod.event_appModule_gainFocus() + # Fire appropriate events for apps gaining focus. + for mod in nextStage: + if not mod.sleepMode and hasattr(mod,'event_appModule_gainFocus'): + mod.event_appModule_gainFocus() #base class for appModules class AppModule(baseObject.ScriptableObject): diff --git a/source/config/__init__.py b/source/config/__init__.py index 570f2b4..86b73dc 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -9,6 +9,7 @@ import os import sys from cStringIO import StringIO import itertools +import contextlib from configobj import ConfigObj, ConfigObjError from validate import Validator from logHandler import log @@ -409,11 +410,14 @@ class ConfigManager(object): self.editProfileIndex = None self.validator = Validator() self.rootSection = None + self._shouldHandleProfileSwitch = True self._initBaseConf() #: The names of all profiles that have been modified since they were last saved. self._dirtyProfiles = set() def _handleProfileSwitch(self): + if not self._shouldHandleProfileSwitch: + return init = self.rootSection is None # Reset the cache. self.rootSection = AggregatedSection(self, (), self.spec, self.profiles) @@ -676,6 +680,22 @@ class ConfigManager(object): self.profiles.remove(profile) self._handleProfileSwitch() + @contextlib.contextmanager + def atomicProfileSwitch(self): + """Indicate that multiple profile switches should be treated as one. + This is useful when multiple triggers may be exited/entered at once; + e.g. when switching applications. + While multiple switches aren't harmful, they might take longer; + e.g. unnecessarily switching speech synthesizers or braille displays. + This is a context manager to be used with the C{with} statement. + """ + self._shouldHandleProfileSwitch = False + try: + yield + finally: + self._shouldHandleProfileSwitch = True + self._handleProfileSwitch() + class AggregatedSection(object): """A view of a section of configuration which aggregates settings from all active profiles. """ diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index cec5aa2..4f000ac 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -33,9 +33,9 @@ class ProfilesDialog(wx.Dialog): sizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: The label of the profile list in the Configuration Profiles dialog. sizer.Add(wx.StaticText(self, label=_("&Profile"))) - # Translators: Indicates that no configuration profile is selected. - # In this case, the user's normal configuration will be used. - profiles = [_("(none)")] + # Translators: The item to select the user's normal configuration + # in the profile list in the Configuration Profiles dialog. + profiles = [_("(normal configuration)")] profiles.extend(config.conf.listProfiles()) item = self.profileList = wx.Choice(self, choices=profiles) item.Bind(wx.EVT_CHOICE, self.onProfileListChoice) @@ -48,8 +48,8 @@ class ProfilesDialog(wx.Dialog): mainSizer.Add(sizer) sizer = wx.BoxSizer(wx.HORIZONTAL) - # Translators: The label of a button to activate the selected profile. - item = wx.Button(self, label=_("&Activate")) + # Translators: The label of a button to activate (and therefore also edit) the selected profile. + item = wx.Button(self, label=_("&Activate/edit")) item.Bind(wx.EVT_BUTTON, self.onActivate) sizer.Add(item) self.AffirmativeId = item.Id @@ -129,6 +129,7 @@ class ProfilesDialog(wx.Dialog): return self.profileList.Append(name) self.profileList.Selection = self.profileList.Count - 1 + self.onProfileListChoice(None) self.profileList.SetFocus() def onDelete(self, evt): @@ -231,9 +232,6 @@ class TriggersDialog(wx.Dialog): item = self.sayAllToggle = wx.CheckBox(self, label=_("&Say all")) if "sayAll" in triggers: item.Value = True - elif "sayAll" in config.conf["profileTriggers"]: - # This trigger is associated with another profile already. - item.Disable() mainSizer.Add(item) item = wx.Button(self, wx.ID_CLOSE, label=_("&Close")) @@ -247,13 +245,23 @@ class TriggersDialog(wx.Dialog): def onClose(self, evt): triggers = config.conf["profileTriggers"] + try: + trigOnOther = triggers["sayAll"] != self.profile + except KeyError: + trigOnOther = False if self.sayAllToggle.Value: + if trigOnOther and gui.messageBox( + # Translators: A confirmation prompt that might be displayed when closing the configuration profile triggers dialog. + _("Say all is already associated with another profile.\n" + "If you continue, it will be removed from the other profile and associated with this one.\n" + "Are you sure you want to continue?"), + # Translators: The title of a confirmation prompt. + _("Confirm"), wx.YES | wx.NO | wx.ICON_WARNING, self + ) == wx.NO: + return triggers["sayAll"] = self.profile - elif self.sayAllToggle.Enabled: - try: - del triggers["sayAll"] - except KeyError: - pass + elif not trigOnOther: + del triggers["sayAll"] self.Parent.Enable() self.Destroy() diff --git a/source/queueHandler.py b/source/queueHandler.py index 4005245..0db66da 100644 --- a/source/queueHandler.py +++ b/source/queueHandler.py @@ -73,4 +73,6 @@ def pumpAll(): except: log.exception("error in generator %d"%ID) del generators[ID] + # Lose our reference so Python can destroy the generator if appropriate. + del gen flushQueue(eventQueue) diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py index f26321e..1a3043f 100644 --- a/source/sayAllHandler.py +++ b/source/sayAllHandler.py @@ -146,7 +146,7 @@ def readTextHelper_generator(cursor): if cursor!=CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]: api.setReviewPosition(updater) elif not keepReading and lastReceivedIndex==lastSentIndex: - # All text has been spoken. + # All text has been sent to the synth. # Turn the page and start again if the object supports it. if isinstance(reader.obj,textInfos.DocumentWithPageTurns): try: @@ -163,6 +163,20 @@ def readTextHelper_generator(cursor): yield yield + # Wait until the synth has actually finished speaking. + # Otherwise, if there is a triggered profile with a different synth, + # we will switch too early and truncate speech (even up to several lines). + # Send another index and wait for it. + index=lastSentIndex+1 + speech.speak([speech.IndexCommand(index)]) + while speech.getLastSpeechIndex()<index: + yield + yield + # Some synths say they've handled the index slightly sooner than they actually have, + # so wait a bit longer. + for i in xrange(30): + yield + class SayAllProfileTrigger(config.ProfileTrigger): """A configuration profile trigger for when say all is in progress. """ diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index d07d9ec..7667d21 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1109,11 +1109,11 @@ You can also do this using a key command: The first control in this dialog is the profile list from which you can select one of the available profiles. When you open the dialog, the last manually activated profile is selected. -You can manually activate the selected profile by pressing the Activate button. +You can manually activate the selected profile by pressing the Activate/edit button. Once a profile is manually activated, any settings in that profile override the settings in your normal configuration. Also, any settings you change will be saved in that profile. -To return to your normal configuration, select "(none)" in the profile list and activate it. +To return to your normal configuration, select "(normal configuration)" in the profile list and press the Activate/edit button. You can create a new profile by pressing the New button. To rename or delete a profile, press the Rename or Delete buttons, respectively. @@ -1125,7 +1125,6 @@ Pressing the Triggers button in the Configuration Profiles dialog allows you to If there is a manually activated profile, other profiles can still be activated due to triggers, but any settings in the manually activated profile will override them. A specific trigger can only be associated with a single profile, but a single profile can be associated with several triggers. -If a trigger is already associated with another profile, its option won't be available. The Applications grouping allows you to specify applications that should trigger the profile when you switch to them. Applications are specified by the name of their executable file (without the extension), which might be different to the actual name of the application. https://bitbucket.org/nvdaaddonteam/nvda/commits/1909092e051f/ Changeset: 1909092e051f Branch: None User: mdcurran Date: 2013-09-12 03:53:36 Summary: Merge branch 'master' into next Affected #: 0 files https://bitbucket.org/nvdaaddonteam/nvda/commits/2dac7393334e/ Changeset: 2dac7393334e Branch: None User: mdcurran Date: 2013-09-12 03:58:06 Summary: Merge branch 'master' into next Affected #: 0 files https://bitbucket.org/nvdaaddonteam/nvda/commits/d9f65203d29d/ Changeset: d9f65203d29d Branch: None User: jteh Date: 2013-09-12 08:16:34 Summary: gui.MainFrame: Make the focus and focus ancestors before the last popup available via prevFocus and prevFocusAncestors attributes. This makes it possible for dialogs brought up with the NVDA menu to be context sensitive. Affected #: 1 file diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 6428d32..5e1e0ec 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -87,6 +87,14 @@ class MainFrame(wx.Frame): super(MainFrame, self).__init__(None, wx.ID_ANY, versionInfo.name, size=(1,1), style=style) self.Bind(wx.EVT_CLOSE, self.onExitCommand) self.sysTrayIcon = SysTrayIcon(self) + #: The focus before the last popup or C{None} if unknown. + #: This is only valid before L{prePopup} is called, + #: so it should be used as early as possible in any popup that needs it. + #: @type: L{NVDAObject} + self.prevFocus = None + #: The focus ancestors before the last popup or C{None} if unknown. + #: @type: list of L{NVDAObject} + self.prevFocusAncestors = None # This makes Windows return to the previous foreground window and also seems to allow NVDA to be brought to the foreground. self.Show() self.Hide() @@ -107,7 +115,12 @@ class MainFrame(wx.Frame): L{postPopup} should be called after the dialog or menu has been shown. @postcondition: A dialog or menu may be shown. """ - if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != os.getpid(): + nvdaPid = os.getpid() + focus = api.getFocusObject() + if focus.processID != nvdaPid: + self.prevFocus = focus + self.prevFocusAncestors = api.getFocusAncestors() + if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != nvdaPid: # This process is not the foreground process, so bring it to the foreground. self.Raise() @@ -115,6 +128,8 @@ class MainFrame(wx.Frame): """Clean up after a popup dialog or menu. This should be called after a dialog or menu was popped up for the user. """ + self.prevFocus = None + self.prevFocusAncestors = None if not winUser.isWindowVisible(winUser.getForegroundWindow()): # The current foreground window is invisible, so we want to return to the previous foreground window. # Showing and hiding our main window seems to achieve this. https://bitbucket.org/nvdaaddonteam/nvda/commits/6f613f43b3e9/ Changeset: 6f613f43b3e9 Branch: None User: jteh Date: 2013-09-12 08:17:18 Summary: Merge branch 'nvdaMenuSaveState' into t1532 Affected #: 6 files diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index f2d78b2..4cd8451 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -84,6 +84,9 @@ An NVDAObject for a window if windowClassName=="EXCEL7" and (relation=='focus' or isinstance(relation,tuple)): from . import excel yield excel.ExcelCell + if windowClassName=="EXCEL:": + from .excel import ExcelDropdown as newCls + yield newCls import JABHandler if JABHandler.isJavaWindow(windowHandle): import NVDAObjects.JAB diff --git a/source/NVDAObjects/window/excel.py b/source/NVDAObjects/window/excel.py index c68ff94..a87bae6 100755 --- a/source/NVDAObjects/window/excel.py +++ b/source/NVDAObjects/window/excel.py @@ -16,6 +16,7 @@ import colors import eventHandler import gui import winUser +from displayModel import DisplayModelTextInfo import controlTypes from . import Window from .. import NVDAObjectTextInfo @@ -168,6 +169,22 @@ class ExcelCell(ExcelBase): if rowHeaderColumn and columnNumber>rowHeaderColumn: return self.excelCellObject.parent.cells(rowNumber,rowHeaderColumn).text + def _get_dropdown(self): + w=winUser.getAncestor(self.windowHandle,winUser.GA_ROOT) + if not w: return + obj=Window(windowHandle=w,chooseBestAPI=False) + if not obj: return + prev=obj.previous + if not prev or prev.windowClassName!='EXCEL:': return + return prev + + def script_openDropdown(self,gesture): + gesture.send() + d=self.dropdown + if d: + d.parent=self + eventHandler.queueEvent("gainFocus",d) + def script_setColumnHeaderRow(self,gesture): scriptCount=scriptHandler.getLastScriptRepeatCount() tableID=self.tableID @@ -290,6 +307,7 @@ class ExcelCell(ExcelBase): __gestures = { "kb:NVDA+shift+c": "setColumnHeaderRow", "kb:NVDA+shift+r": "setRowHeaderColumn", + "kb:alt+downArrow":"openDropdown", } class ExcelSelection(ExcelBase): @@ -321,3 +339,107 @@ class ExcelSelection(ExcelBase): if position==textInfos.POSITION_SELECTION: position=textInfos.POSITION_ALL return super(ExcelSelection,self).makeTextInfo(position) + +class ExcelDropdownItem(Window): + + firstChild=None + lastChild=None + children=[] + role=controlTypes.ROLE_LISTITEM + + def __init__(self,parent=None,name=None,states=None,index=None): + self.name=name + self.states=states + self.parent=parent + self.index=index + super(ExcelDropdownItem,self).__init__(windowHandle=parent.windowHandle) + + def _get_previous(self): + newIndex=self.index-1 + if newIndex>=0: + return self.parent.children[newIndex] + + def _get_next(self): + newIndex=self.index+1 + if newIndex<self.parent.childCount: + return self.parent.children[newIndex] + + def _get_positionInfo(self): + return {'indexInGroup':self.index+1,'similarItemsInGroup':self.parent.childCount,} + +class ExcelDropdown(Window): + + @classmethod + def kwargsFromSuper(cls,kwargs,relation=None): + return kwargs + + role=controlTypes.ROLE_LIST + excelCell=None + + def _get__highlightColors(self): + background=colors.RGB.fromCOLORREF(winUser.user32.GetSysColor(13)) + foreground=colors.RGB.fromCOLORREF(winUser.user32.GetSysColor(14)) + self._highlightColors=(background,foreground) + return self._highlightColors + + def _get_children(self): + children=[] + index=0 + states=set() + for item in DisplayModelTextInfo(self,textInfos.POSITION_ALL).getTextWithFields(): + if isinstance(item,textInfos.FieldCommand) and item.command=="formatChange": + states=set([controlTypes.STATE_SELECTABLE]) + foreground=item.field.get('color',None) + background=item.field.get('background-color',None) + if (background,foreground)==self._highlightColors: + states.add(controlTypes.STATE_SELECTED) + if isinstance(item,basestring): + obj=ExcelDropdownItem(parent=self,name=item,states=states,index=index) + children.append(obj) + index+=1 + return children + + def _get_childCount(self): + return len(self.children) + + def _get_firstChild(self): + return self.children[0] + def _get_selection(self): + for child in self.children: + if controlTypes.STATE_SELECTED in child.states: + return child + + def script_selectionChange(self,gesture): + gesture.send() + newFocus=self.selection or self + if eventHandler.lastQueuedFocusObject is newFocus: return + eventHandler.queueEvent("gainFocus",newFocus) + script_selectionChange.canPropagate=True + + def script_closeDropdown(self,gesture): + gesture.send() + eventHandler.queueEvent("gainFocus",self.parent) + script_closeDropdown.canPropagate=True + + __gestures={ + "kb:downArrow":"selectionChange", + "kb:upArrow":"selectionChange", + "kb:leftArrow":"selectionChange", + "kb:rightArrow":"selectionChange", + "kb:home":"selectionChange", + "kb:end":"selectionChange", + "kb:escape":"closeDropdown", + "kb:enter":"closeDropdown", + "kb:space":"closeDropdown", + } + + def event_gainFocus(self): + child=self.selection + if not child and self.childCount>0: + child=self.children[0] + if child: + eventHandler.queueEvent("focusEntered",self) + eventHandler.queueEvent("gainFocus",child) + else: + super(ExcelDropdown,self).event_gainFocus() + diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py index 96bad4f..989ccd1 100644 --- a/source/_UIAHandler.py +++ b/source/_UIAHandler.py @@ -130,11 +130,11 @@ class UIAHandler(COMObject): raise self.MTAThreadInitException def terminate(self): - MTAThreadHandle=HANDLE(windll.kernel32.OpenThread(self.MTAThread.ident,False,winKernel.SYNCHRONIZE)) + MTAThreadHandle=HANDLE(windll.kernel32.OpenThread(winKernel.SYNCHRONIZE,False,self.MTAThread.ident)) self.MTAThreadStopEvent.set() - index=c_int() - #Wait for the MTAA thread to die (while still message pumping) - windll.user32.MsgWaitForMultipleObjects(1,byref(MTAThreadHandle),False,5000,0) + #Wait for the MTA thread to die (while still message pumping) + if windll.user32.MsgWaitForMultipleObjects(1,byref(MTAThreadHandle),False,200,0)!=0: + log.debugWarning("Timeout or error while waiting for UIAHandler MTA thread") windll.kernel32.CloseHandle(MTAThreadHandle) del self.MTAThread diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 55edcde..fa206bc 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -87,6 +87,14 @@ class MainFrame(wx.Frame): super(MainFrame, self).__init__(None, wx.ID_ANY, versionInfo.name, size=(1,1), style=style) self.Bind(wx.EVT_CLOSE, self.onExitCommand) self.sysTrayIcon = SysTrayIcon(self) + #: The focus before the last popup or C{None} if unknown. + #: This is only valid before L{prePopup} is called, + #: so it should be used as early as possible in any popup that needs it. + #: @type: L{NVDAObject} + self.prevFocus = None + #: The focus ancestors before the last popup or C{None} if unknown. + #: @type: list of L{NVDAObject} + self.prevFocusAncestors = None # This makes Windows return to the previous foreground window and also seems to allow NVDA to be brought to the foreground. self.Show() self.Hide() @@ -107,7 +115,12 @@ class MainFrame(wx.Frame): L{postPopup} should be called after the dialog or menu has been shown. @postcondition: A dialog or menu may be shown. """ - if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != os.getpid(): + nvdaPid = os.getpid() + focus = api.getFocusObject() + if focus.processID != nvdaPid: + self.prevFocus = focus + self.prevFocusAncestors = api.getFocusAncestors() + if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != nvdaPid: # This process is not the foreground process, so bring it to the foreground. self.Raise() @@ -115,6 +128,8 @@ class MainFrame(wx.Frame): """Clean up after a popup dialog or menu. This should be called after a dialog or menu was popped up for the user. """ + self.prevFocus = None + self.prevFocusAncestors = None if not winUser.isWindowVisible(winUser.getForegroundWindow()): # The current foreground window is invisible, so we want to return to the previous foreground window. # Showing and hiding our main window seems to achieve this. diff --git a/source/logHandler.py b/source/logHandler.py index 2f6c7e6..0a380ec 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -24,36 +24,6 @@ EVENT_E_ALL_SUBSCRIBERS_FAILED = -2147220991 RPC_E_CALL_REJECTED = -2147418111 RPC_E_DISCONNECTED = -2147417848 -moduleCache={} - -def makeModulePathFromFilePath(path): - """calculates the pythonic dotted module path from a file path of a python module. - @param path: the relative or absolute path to the module - @type path: string - @returns: the Pythonic dotted module path - @rtype: string - """ - if path in moduleCache: - return moduleCache[path] - modPathList = [] - # Work through the path components from right to left. - curPath = path - while curPath: - curPath, curPathCom = os.path.split(curPath) - if not curPathCom: - break - curPathCom = os.path.splitext(curPathCom)[0] - # __init__ is the root module of a package, so skip it. - if curPathCom != "__init__": - modPathList.insert(0, curPathCom) - if curPath in sys.path: - # curPath is in the Python search path, so the Pythonic module path is relative to curPath. - break - modulePath = ".".join(modPathList) - if modulePath: - moduleCache[path] = modulePath - return modulePath - def getCodePath(f): """Using a frame object, gets its module path (relative to the current directory).[className.[funcName]] @param f: the frame object to use @@ -61,7 +31,19 @@ def getCodePath(f): @returns: the dotted module.class.attribute path @rtype: string """ - path=makeModulePathFromFilePath(os.path.relpath(f.f_code.co_filename)) + fn=f.f_code.co_filename + if fn[0] != "<" and os.path.isabs(fn) and not fn.startswith(sys.path[0] + "\\"): + # This module is external because: + # the code comes from a file (fn doesn't begin with "<"); + # it has an absolute file path (code bundled in binary builds reports relative paths); and + # it is not part of NVDA's Python code (not beneath sys.path[0]). + path="external:" + else: + path="" + try: + path+=f.f_globals["__name__"] + except KeyError: + path+=fn funcName=f.f_code.co_name if funcName.startswith('<'): funcName="" diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 3188ffb..b16889d 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -8,6 +8,7 @@ == New Features == - Form fields are now reported in Microsoft word documents. (#2295) - NVDA can now announce revision information in Microsoft Word when Track Changes is enabled. Note that Report editor revisions in NVDA's document settings dialog (off by default) must be enabled also for them to be announced. (#1670) +- Dropdown lists in Microsoft Excel 2003 through 2010 are now announced when opened and navigated around. (#3382) == Bug Fixes == https://bitbucket.org/nvdaaddonteam/nvda/commits/329e27302a89/ Changeset: 329e27302a89 Branch: None User: jteh Date: 2013-09-12 08:59:35 Summary: If the Input Gestures dialog is opened from the NVDA menu, it will now use the focus before the NVDA menu was opened. Previously, you always had to use NVDA+control+i if you wanted to access context specific gestures. Affected #: 2 files diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 3dadf3e..84aab0c 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1528,7 +1528,7 @@ class InputGesturesDialog(SettingsDialog): tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect) settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) - gestures = inputCore.manager.getAllGestureMappings() + gestures = inputCore.manager.getAllGestureMappings(obj=gui.mainFrame.prevFocus, ancestors=gui.mainFrame.prevFocusAncestors) for category in sorted(gestures): treeCat = tree.AppendItem(self.treeRoot, category) commands = gestures[category] diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 1ae185a..485c0ea 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1061,8 +1061,7 @@ When you are finished, press the OK button to save your changes or the Cancel bu In this dialog, you can customize the input gestures (keys on the keyboard, buttons on a braille display, etc.) for NVDA commands. Only commands that are applicable immediately before the dialog is opened are shown. -Therefore, you should generally open this dialog using its key command so that all commands applicable to what you were just doing are shown. -For example, if you want to customize commands related to browse mode, you should press the key command to open the Input Gestures dialog while you are in browse mode. +For example, if you want to customize commands related to browse mode, you should open the Input Gestures dialog while you are in browse mode. The tree in this dialog lists all of the applicable NVDA commands grouped by category. Any gestures associated with a command are listed beneath the command. https://bitbucket.org/nvdaaddonteam/nvda/commits/2633f7e19366/ Changeset: 2633f7e19366 Branch: None User: jteh Date: 2013-09-12 09:04:15 Summary: Merge branch 't1532' into next Incubates #1532. Affected #: 3 files diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 0324263..bb64610 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -87,6 +87,14 @@ class MainFrame(wx.Frame): super(MainFrame, self).__init__(None, wx.ID_ANY, versionInfo.name, size=(1,1), style=style) self.Bind(wx.EVT_CLOSE, self.onExitCommand) self.sysTrayIcon = SysTrayIcon(self) + #: The focus before the last popup or C{None} if unknown. + #: This is only valid before L{prePopup} is called, + #: so it should be used as early as possible in any popup that needs it. + #: @type: L{NVDAObject} + self.prevFocus = None + #: The focus ancestors before the last popup or C{None} if unknown. + #: @type: list of L{NVDAObject} + self.prevFocusAncestors = None # This makes Windows return to the previous foreground window and also seems to allow NVDA to be brought to the foreground. self.Show() self.Hide() @@ -107,7 +115,12 @@ class MainFrame(wx.Frame): L{postPopup} should be called after the dialog or menu has been shown. @postcondition: A dialog or menu may be shown. """ - if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != os.getpid(): + nvdaPid = os.getpid() + focus = api.getFocusObject() + if focus.processID != nvdaPid: + self.prevFocus = focus + self.prevFocusAncestors = api.getFocusAncestors() + if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != nvdaPid: # This process is not the foreground process, so bring it to the foreground. self.Raise() @@ -115,6 +128,8 @@ class MainFrame(wx.Frame): """Clean up after a popup dialog or menu. This should be called after a dialog or menu was popped up for the user. """ + self.prevFocus = None + self.prevFocusAncestors = None if not winUser.isWindowVisible(winUser.getForegroundWindow()): # The current foreground window is invisible, so we want to return to the previous foreground window. # Showing and hiding our main window seems to achieve this. diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index bd10732..e6b8ce3 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1528,7 +1528,7 @@ class InputGesturesDialog(SettingsDialog): tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelect) settingsSizer.Add(tree, proportion=7, flag=wx.EXPAND) - gestures = inputCore.manager.getAllGestureMappings() + gestures = inputCore.manager.getAllGestureMappings(obj=gui.mainFrame.prevFocus, ancestors=gui.mainFrame.prevFocusAncestors) for category in sorted(gestures): treeCat = tree.AppendItem(self.treeRoot, category) commands = gestures[category] diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 7667d21..adb3262 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1061,8 +1061,7 @@ When you are finished, press the OK button to save your changes or the Cancel bu In this dialog, you can customize the input gestures (keys on the keyboard, buttons on a braille display, etc.) for NVDA commands. Only commands that are applicable immediately before the dialog is opened are shown. -Therefore, you should generally open this dialog using its key command so that all commands applicable to what you were just doing are shown. -For example, if you want to customize commands related to browse mode, you should press the key command to open the Input Gestures dialog while you are in browse mode. +For example, if you want to customize commands related to browse mode, you should open the Input Gestures dialog while you are in browse mode. The tree in this dialog lists all of the applicable NVDA commands grouped by category. Any gestures associated with a command are listed beneath the command. https://bitbucket.org/nvdaaddonteam/nvda/commits/685c52188c98/ Changeset: 685c52188c98 Branch: None User: jteh Date: 2013-09-13 06:05:51 Summary: Fix undesirable speech interruption with some synths whenever the focus changed. atomicProfileSwitch was calling _handleProfileSwitch at the end even if there was no profile switch. appModuleHandler.handleAppSwitch (which is called for every focus change) uses atomicProfileSwitch even if the app doesn't change, so this meant _handleProfileSwitch was being called for every focus change. _handleProfileSwitch resets synth settings and some synths cancel speech if the voice is reset. Re #3524. Affected #: 1 file diff --git a/source/config/__init__.py b/source/config/__init__.py index d4e335e..3a0087b 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -410,12 +410,14 @@ class ConfigManager(object): self.validator = Validator() self.rootSection = None self._shouldHandleProfileSwitch = True + self._pendingHandleProfileSwitch = False self._initBaseConf() #: The names of all profiles that have been modified since they were last saved. self._dirtyProfiles = set() def _handleProfileSwitch(self): if not self._shouldHandleProfileSwitch: + self._pendingHandleProfileSwitch = True return init = self.rootSection is None # Reset the cache. @@ -693,7 +695,9 @@ class ConfigManager(object): yield finally: self._shouldHandleProfileSwitch = True - self._handleProfileSwitch() + if self._pendingHandleProfileSwitch: + self._handleProfileSwitch() + self._pendingHandleProfileSwitch = False class AggregatedSection(object): """A view of a section of configuration which aggregates settings from all active profiles. https://bitbucket.org/nvdaaddonteam/nvda/commits/349ff4ee78ce/ Changeset: 349ff4ee78ce Branch: None User: jteh Date: 2013-09-13 08:09:45 Summary: When switching config profiles, don't apply voice settings that haven't actually changed. Some synths may interrupt speech or perform slowly when some settings are applied, so it's best to apply only those that are necessary. Affected #: 1 file diff --git a/source/synthDriverHandler.py b/source/synthDriverHandler.py index f0ab46a..4a78623 100644 --- a/source/synthDriverHandler.py +++ b/source/synthDriverHandler.py @@ -107,7 +107,7 @@ def handleConfigProfileSwitch(): if conf["synth"] != _curSynth.name: setSynth(conf["synth"]) return - _curSynth.loadSettings() + _curSynth.loadSettings(onlyChanged=True) class SynthSetting(object): """Represents a synthesizer setting such as voice or variant. @@ -452,24 +452,28 @@ class SynthDriver(baseObject.AutoPropertyObject): for setting in self.supportedSettings: conf[setting.name]=getattr(self,setting.name) - def loadSettings(self): + def loadSettings(self, onlyChanged=False): c=config.conf["speech"][self.name] if self.isSupported("voice"): voice=c.get("voice",None) - try: - changeVoice(self,voice) - except: - log.warning("Invalid voice: %s" % voice) - # Update the configuration with the correct voice. - c["voice"]=self.voice - # We need to call changeVoice here so that required initialisation can be performed. - changeVoice(self,self.voice) - else: + if not onlyChanged or self.voice!=voice: + try: + changeVoice(self,voice) + except: + log.warning("Invalid voice: %s" % voice) + # Update the configuration with the correct voice. + c["voice"]=self.voice + # We need to call changeVoice here so that required initialisation can be performed. + changeVoice(self,self.voice) + elif not onlyChanged: changeVoice(self,None) for s in self.supportedSettings: if s.name=="voice" or c[s.name] is None: continue - setattr(self,s.name,c[s.name]) + val=c[s.name] + if onlyChanged and getattr(self,s.name)==val: + continue + setattr(self,s.name,val) def _get_initialSettingsRingSetting (self): if not self.isSupported("rate") and len(self.supportedSettings)>0: https://bitbucket.org/nvdaaddonteam/nvda/commits/64c198882fd3/ Changeset: 64c198882fd3 Branch: None User: jteh Date: 2013-09-13 08:51:56 Summary: Merge branch 't667' into next Incubates #667. Fixes #3524. Affected #: 2 files diff --git a/source/config/__init__.py b/source/config/__init__.py index 86b73dc..1bf36dc 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -411,12 +411,14 @@ class ConfigManager(object): self.validator = Validator() self.rootSection = None self._shouldHandleProfileSwitch = True + self._pendingHandleProfileSwitch = False self._initBaseConf() #: The names of all profiles that have been modified since they were last saved. self._dirtyProfiles = set() def _handleProfileSwitch(self): if not self._shouldHandleProfileSwitch: + self._pendingHandleProfileSwitch = True return init = self.rootSection is None # Reset the cache. @@ -694,7 +696,9 @@ class ConfigManager(object): yield finally: self._shouldHandleProfileSwitch = True - self._handleProfileSwitch() + if self._pendingHandleProfileSwitch: + self._handleProfileSwitch() + self._pendingHandleProfileSwitch = False class AggregatedSection(object): """A view of a section of configuration which aggregates settings from all active profiles. diff --git a/source/synthDriverHandler.py b/source/synthDriverHandler.py index f0ab46a..4a78623 100644 --- a/source/synthDriverHandler.py +++ b/source/synthDriverHandler.py @@ -107,7 +107,7 @@ def handleConfigProfileSwitch(): if conf["synth"] != _curSynth.name: setSynth(conf["synth"]) return - _curSynth.loadSettings() + _curSynth.loadSettings(onlyChanged=True) class SynthSetting(object): """Represents a synthesizer setting such as voice or variant. @@ -452,24 +452,28 @@ class SynthDriver(baseObject.AutoPropertyObject): for setting in self.supportedSettings: conf[setting.name]=getattr(self,setting.name) - def loadSettings(self): + def loadSettings(self, onlyChanged=False): c=config.conf["speech"][self.name] if self.isSupported("voice"): voice=c.get("voice",None) - try: - changeVoice(self,voice) - except: - log.warning("Invalid voice: %s" % voice) - # Update the configuration with the correct voice. - c["voice"]=self.voice - # We need to call changeVoice here so that required initialisation can be performed. - changeVoice(self,self.voice) - else: + if not onlyChanged or self.voice!=voice: + try: + changeVoice(self,voice) + except: + log.warning("Invalid voice: %s" % voice) + # Update the configuration with the correct voice. + c["voice"]=self.voice + # We need to call changeVoice here so that required initialisation can be performed. + changeVoice(self,self.voice) + elif not onlyChanged: changeVoice(self,None) for s in self.supportedSettings: if s.name=="voice" or c[s.name] is None: continue - setattr(self,s.name,c[s.name]) + val=c[s.name] + if onlyChanged and getattr(self,s.name)==val: + continue + setattr(self,s.name,val) def _get_initialSettingsRingSetting (self): if not self.isSupported("rate") and len(self.supportedSettings)>0: https://bitbucket.org/nvdaaddonteam/nvda/commits/23b20c22fbcf/ Changeset: 23b20c22fbcf Branch: None User: jteh Date: 2013-09-14 00:43:13 Summary: TriggersDialog: Don't barf when say all is unchecked and the say all trigger isn't associated with any other profile. Re #667. Affected #: 1 file diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index 4f000ac..f8cf839 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -261,7 +261,10 @@ class TriggersDialog(wx.Dialog): return triggers["sayAll"] = self.profile elif not trigOnOther: - del triggers["sayAll"] + try: + del triggers["sayAll"] + except KeyError: + pass self.Parent.Enable() self.Destroy() https://bitbucket.org/nvdaaddonteam/nvda/commits/9f660291af00/ Changeset: 9f660291af00 Branch: None User: jteh Date: 2013-09-14 00:43:37 Summary: Merge branch 't667' into next Incubates #667. Affected #: 1 file diff --git a/source/gui/configProfiles.py b/source/gui/configProfiles.py index 4f000ac..f8cf839 100644 --- a/source/gui/configProfiles.py +++ b/source/gui/configProfiles.py @@ -261,7 +261,10 @@ class TriggersDialog(wx.Dialog): return triggers["sayAll"] = self.profile elif not trigOnOther: - del triggers["sayAll"] + try: + del triggers["sayAll"] + except KeyError: + pass self.Parent.Enable() self.Destroy() https://bitbucket.org/nvdaaddonteam/nvda/commits/c9b1b235d492/ Changeset: c9b1b235d492 Branch: None User: jteh Date: 2013-09-15 16:15:18 Summary: Fix problems when attempting to move to columns in list views in NVDA's GUI. The nvda app module was trying to use IAccessible properties without first checking whether it was dealing with an IAccessible object. Fixes #3527. Affected #: 1 file diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 0fadb65..b1edb76 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -8,6 +8,7 @@ import appModuleHandler import api import controlTypes import versionInfo +from NVDAObjects.IAccessible import IAccessible nvdaMenuIaIdentity = None @@ -15,6 +16,8 @@ class AppModule(appModuleHandler.AppModule): def isNvdaMenu(self, obj): global nvdaMenuIaIdentity + if not isinstance(obj, IAccessible): + return False if obj.IAccessibleIdentity == nvdaMenuIaIdentity: return True if nvdaMenuIaIdentity is not True: https://bitbucket.org/nvdaaddonteam/nvda/commits/f717c9b01783/ Changeset: f717c9b01783 Branch: None User: jteh Date: 2013-09-15 16:16:50 Summary: Merge branch 't3503' into next Incubates #3503. Re #3527. Affected #: 1 file diff --git a/source/appModules/nvda.py b/source/appModules/nvda.py index 0fadb65..b1edb76 100755 --- a/source/appModules/nvda.py +++ b/source/appModules/nvda.py @@ -8,6 +8,7 @@ import appModuleHandler import api import controlTypes import versionInfo +from NVDAObjects.IAccessible import IAccessible nvdaMenuIaIdentity = None @@ -15,6 +16,8 @@ class AppModule(appModuleHandler.AppModule): def isNvdaMenu(self, obj): global nvdaMenuIaIdentity + if not isinstance(obj, IAccessible): + return False if obj.IAccessibleIdentity == nvdaMenuIaIdentity: return True if nvdaMenuIaIdentity is not True: https://bitbucket.org/nvdaaddonteam/nvda/commits/a66853216d95/ Changeset: a66853216d95 Branch: None User: jteh Date: 2013-09-15 17:30:43 Summary: If an unknow command line argument is passed to the NVDA distribution package, NVDA no longer displays error message dialogs endlessly. This occurred because command line errors are signalled with exit code 2, but the same exit code was also being used to signal NVDA restarts to the launcher. Now, restarts are signalled to the launcher using exit code 3. Authors: Patrick ZAJDA <patrick@xxxxxxxx>, James Teh <jamie@xxxxxxxxxxxx> Re #3463. Affected #: 2 files diff --git a/launcher/nvdaLauncher.nsi b/launcher/nvdaLauncher.nsi index bea1fe3..4b7de09 100644 --- a/launcher/nvdaLauncher.nsi +++ b/launcher/nvdaLauncher.nsi @@ -82,8 +82,8 @@ ${GetParameters} $0 Banner::destroy exec: execWait "$PLUGINSDIR\app\nvda_noUIAccess.exe $0 -r --launcher" $1 -;If exit code is 2 then execute again (restart) -intcmp $1 2 exec +1 +;If exit code is 3 then execute again (restart) +intcmp $1 3 exec +1 SectionEnd var hmci diff --git a/source/core.py b/source/core.py index c179cc1..80b6b9e 100644 --- a/source/core.py +++ b/source/core.py @@ -46,7 +46,7 @@ def restart(): """Restarts NVDA by starting a new copy with -r.""" if globalVars.appArgs.launcher: import wx - globalVars.exitCode=2 + globalVars.exitCode=3 wx.GetApp().ExitMainLoop() return import subprocess https://bitbucket.org/nvdaaddonteam/nvda/commits/c41367a2cc5a/ Changeset: c41367a2cc5a Branch: None User: jteh Date: 2013-09-15 17:34:57 Summary: Merge branch 't3463' into next Incubates #3463. Affected #: 2 files diff --git a/launcher/nvdaLauncher.nsi b/launcher/nvdaLauncher.nsi index bea1fe3..4b7de09 100644 --- a/launcher/nvdaLauncher.nsi +++ b/launcher/nvdaLauncher.nsi @@ -82,8 +82,8 @@ ${GetParameters} $0 Banner::destroy exec: execWait "$PLUGINSDIR\app\nvda_noUIAccess.exe $0 -r --launcher" $1 -;If exit code is 2 then execute again (restart) -intcmp $1 2 exec +1 +;If exit code is 3 then execute again (restart) +intcmp $1 3 exec +1 SectionEnd var hmci diff --git a/source/core.py b/source/core.py index 937225c..6377487 100644 --- a/source/core.py +++ b/source/core.py @@ -55,7 +55,7 @@ def restart(): """Restarts NVDA by starting a new copy with -r.""" if globalVars.appArgs.launcher: import wx - globalVars.exitCode=2 + globalVars.exitCode=3 wx.GetApp().ExitMainLoop() return import subprocess https://bitbucket.org/nvdaaddonteam/nvda/commits/07826b32b69e/ Changeset: 07826b32b69e Branch: None User: mdcurran Date: 2013-09-16 01:26:33 Summary: added translator comments for MS word revision types. Re #1670 Affected #: 1 file diff --git a/source/NVDAObjects/window/winword.py b/source/NVDAObjects/window/winword.py index 9106ae6..a2e7830 100755 --- a/source/NVDAObjects/window/winword.py +++ b/source/NVDAObjects/window/winword.py @@ -116,23 +116,41 @@ wdRevisionCellDeletion=17 wdRevisionCellMerge=18 wdRevisionTypeLabels={ + # Translators: a Microsoft Word revision type (inserted content) wdRevisionInsert:_("insertion"), + # Translators: a Microsoft Word revision type (deleted content) wdRevisionDelete:_("deletion"), + # Translators: a Microsoft Word revision type (changed content property, e.g. font, color) wdRevisionProperty:_("property"), + # Translators: a Microsoft Word revision type (changed paragraph number) wdRevisionParagraphNumber:_("paragraph number"), + # Translators: a Microsoft Word revision type (display field) wdRevisionDisplayField:_("display field"), + # Translators: a Microsoft Word revision type (reconcile) wdRevisionReconcile:_("reconcile"), + # Translators: a Microsoft Word revision type (conflicting revision) wdRevisionConflict:_("conflict"), + # Translators: a Microsoft Word revision type (style change) wdRevisionStyle:_("style"), + # Translators: a Microsoft Word revision type (replaced content) wdRevisionReplace:_("replace"), + # Translators: a Microsoft Word revision type (changed paragraph property, e.g. alignment) wdRevisionParagraphProperty:_("paragraph property"), + # Translators: a Microsoft Word revision type (table) wdRevisionTableProperty:_("table property"), + # Translators: a Microsoft Word revision type (section property) wdRevisionSectionProperty:_("section property"), + # Translators: a Microsoft Word revision type (style definition) wdRevisionStyleDefinition:_("style definition"), + # Translators: a Microsoft Word revision type (moved from) wdRevisionMovedFrom:_("moved from"), + # Translators: a Microsoft Word revision type (moved to) wdRevisionMovedTo:_("moved to"), + # Translators: a Microsoft Word revision type (inserted table cell) wdRevisionCellInsertion:_("cell insertion"), + # Translators: a Microsoft Word revision type (deleted table cell) wdRevisionCellDeletion:_("cell deletion"), + # Translators: a Microsoft Word revision type (merged table cells) wdRevisionCellMerge:_("cell merge"), } https://bitbucket.org/nvdaaddonteam/nvda/commits/107b6c9e4ba9/ Changeset: 107b6c9e4ba9 Branch: None User: mhameed Date: 2013-09-16 01:34:49 Summary: l10n From translation svn rev:11460. Affected #: 43 files This diff is so big that we needed to truncate the remainder. https://bitbucket.org/nvdaaddonteam/nvda/commits/b911f6553fef/ Changeset: b911f6553fef Branch: None User: mhameed Date: 2013-09-16 01:34:49 Summary: Add new Polish translator to contributors. Affected #: 1 file diff --git a/contributors.txt b/contributors.txt index d18fdd4..83bf595 100644 --- a/contributors.txt +++ b/contributors.txt @@ -134,3 +134,4 @@ Kevin Scannell Hamid Rezaey Bue Vester-Andersen Yogesh Kumar +Grzegorz Zlotowicz https://bitbucket.org/nvdaaddonteam/nvda/commits/5b906832d7d5/ Changeset: 5b906832d7d5 Branch: None User: mhameed Date: 2013-09-16 01:34:50 Summary: add Farsi translator to contributors. Affected #: 1 file diff --git a/contributors.txt b/contributors.txt index 83bf595..8e64bbc 100644 --- a/contributors.txt +++ b/contributors.txt @@ -131,6 +131,7 @@ Juan Pablo Martínez Cortés Marat Suleymanov Rui Fontes (Tiflotecnia, lda.) Kevin Scannell +Ali Aslani Hamid Rezaey Bue Vester-Andersen Yogesh Kumar https://bitbucket.org/nvdaaddonteam/nvda/commits/d4d3744bf628/ Changeset: d4d3744bf628 Branch: next User: mhameed Date: 2013-09-16 01:59:46 Summary: Merge branch 'master' into next Affected #: 46 files diff --git a/contributors.txt b/contributors.txt index d18fdd4..8e64bbc 100644 --- a/contributors.txt +++ b/contributors.txt @@ -131,6 +131,8 @@ Juan Pablo Martínez Cortés Marat Suleymanov Rui Fontes (Tiflotecnia, lda.) Kevin Scannell +Ali Aslani Hamid Rezaey Bue Vester-Andersen Yogesh Kumar +Grzegorz Zlotowicz diff --git a/source/NVDAObjects/window/winword.py b/source/NVDAObjects/window/winword.py index 4f66617..b99c0b8 100755 --- a/source/NVDAObjects/window/winword.py +++ b/source/NVDAObjects/window/winword.py @@ -115,23 +115,41 @@ wdRevisionCellDeletion=17 wdRevisionCellMerge=18 wdRevisionTypeLabels={ + # Translators: a Microsoft Word revision type (inserted content) wdRevisionInsert:_("insertion"), + # Translators: a Microsoft Word revision type (deleted content) wdRevisionDelete:_("deletion"), + # Translators: a Microsoft Word revision type (changed content property, e.g. font, color) wdRevisionProperty:_("property"), + # Translators: a Microsoft Word revision type (changed paragraph number) wdRevisionParagraphNumber:_("paragraph number"), + # Translators: a Microsoft Word revision type (display field) wdRevisionDisplayField:_("display field"), + # Translators: a Microsoft Word revision type (reconcile) wdRevisionReconcile:_("reconcile"), + # Translators: a Microsoft Word revision type (conflicting revision) wdRevisionConflict:_("conflict"), + # Translators: a Microsoft Word revision type (style change) wdRevisionStyle:_("style"), + # Translators: a Microsoft Word revision type (replaced content) wdRevisionReplace:_("replace"), + # Translators: a Microsoft Word revision type (changed paragraph property, e.g. alignment) wdRevisionParagraphProperty:_("paragraph property"), + # Translators: a Microsoft Word revision type (table) wdRevisionTableProperty:_("table property"), + # Translators: a Microsoft Word revision type (section property) wdRevisionSectionProperty:_("section property"), + # Translators: a Microsoft Word revision type (style definition) wdRevisionStyleDefinition:_("style definition"), + # Translators: a Microsoft Word revision type (moved from) wdRevisionMovedFrom:_("moved from"), + # Translators: a Microsoft Word revision type (moved to) wdRevisionMovedTo:_("moved to"), + # Translators: a Microsoft Word revision type (inserted table cell) wdRevisionCellInsertion:_("cell insertion"), + # Translators: a Microsoft Word revision type (deleted table cell) wdRevisionCellDeletion:_("cell deletion"), + # Translators: a Microsoft Word revision type (merged table cells) wdRevisionCellMerge:_("cell merge"), } This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/nvdaaddonteam/nvda/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.