14 new commits in nvda: https://bitbucket.org/nvdaaddonteam/nvda/commits/b43f7b02c78a/ Changeset: b43f7b02c78a Branch: None User: manish_agrawal Date: 2013-08-23 01:49:04 Summary: Initial try at supporting MS word 2010 protected view. Fixes a crash, but focus on some systems is still not right. Affected #: 3 files diff --git a/nvdaHelper/remote/winword.cpp b/nvdaHelper/remote/winword.cpp index 9c2aaf5..6535727 100644 --- a/nvdaHelper/remote/winword.cpp +++ b/nvdaHelper/remote/winword.cpp @@ -50,6 +50,9 @@ using namespace std; #define wdDISPID_STYLE_NAMELOCAL 0 #define wdDISPID_RANGE_SPELLINGERRORS 316 #define wdDISPID_SPELLINGERRORS_COUNT 1 +#define wdDISPID_RANGE_APPLICATION 1000 +#define wdDISPID_APPLICATION_ISSANDBOX 492 + #define wdDISPID_RANGE_FONT 5 #define wdDISPID_FONT_COLOR 159 #define wdDISPID_FONT_BOLD 130 @@ -409,11 +412,20 @@ void generateXMLAttribsForFormatting(IDispatch* pDispatchRange, int startOffset, } } if(formatConfig&formatConfig_reportSpellingErrors) { - IDispatchPtr pDispatchSpellingErrors=NULL; - if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_SPELLINGERRORS,VT_DISPATCH,&pDispatchSpellingErrors)==S_OK&&pDispatchSpellingErrors) { - _com_dispatch_raw_propget(pDispatchSpellingErrors,wdDISPID_SPELLINGERRORS_COUNT,VT_I4,&iVal); - if(iVal>0) { - formatAttribsStream<<L"invalid-spelling=\""<<iVal<<L"\" "; + IDispatchPtr pDispatchApplication=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_APPLICATION ,VT_DISPATCH,&pDispatchApplication)==S_OK && pDispatchApplication) { + bool isSandbox = true; + // We need to ironically enter the if block if the call to get IsSandbox property fails + // for backward compatibility because IsSandbox was introduced with word 2010 and earlier versions will return a failure for this property access. + // This however, means that if this property access fails for some reason in word 2010, then we will incorrectly enter this section. + if(_com_dispatch_raw_propget(pDispatchApplication,wdDISPID_APPLICATION_ISSANDBOX ,VT_BOOL,&isSandbox)!=S_OK || !isSandbox ) { + IDispatchPtr pDispatchSpellingErrors=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_SPELLINGERRORS,VT_DISPATCH,&pDispatchSpellingErrors)==S_OK&&pDispatchSpellingErrors) { + _com_dispatch_raw_propget(pDispatchSpellingErrors,wdDISPID_SPELLINGERRORS_COUNT,VT_I4,&iVal); + if(iVal>0) { + formatAttribsStream<<L"invalid-spelling=\""<<iVal<<L"\" "; + } + } } } } diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 40f2151..08d9e46 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -1797,4 +1797,5 @@ _staticMap={ ("NUIDialog",oleacc.ROLE_SYSTEM_CLIENT):"NUIDialogClient", ("_WwN",oleacc.ROLE_SYSTEM_TEXT):"winword.SpellCheckErrorField", ("_WwO",oleacc.ROLE_SYSTEM_TEXT):"winword.SpellCheckErrorField", + ("_WwB",oleacc.ROLE_SYSTEM_CLIENT):"winword.ProtectedDocumentPane", } diff --git a/source/NVDAObjects/IAccessible/winword.py b/source/NVDAObjects/IAccessible/winword.py index c7080285..f89fc19 100644 --- a/source/NVDAObjects/IAccessible/winword.py +++ b/source/NVDAObjects/IAccessible/winword.py @@ -13,6 +13,8 @@ import winUser import speech import controlTypes import textInfos +import eventHandler +import wx from . import IAccessible from NVDAObjects.window.winword import WordDocument @@ -66,3 +68,20 @@ class SpellCheckErrorField(IAccessible,WordDocument): if errorText: speech.speakText(errorText) speech.speakSpelling(errorText) + + +class ProtectedDocumentPane(IAccessible): + """The pane that gets focus in case a document opens in protected mode in word + This is mapped to the window class _WWB and role oleacc.ROLE_SYSTEM_CLIENT + """ + + def event_gainFocus(self): + """On gaining focus, simply set the focus on a child of type word document. + This is just a container window. + """ + if eventHandler.isPendingEvents("gainFocus"): + return + document=next((x for x in self.children if isinstance(x,WordDocument)), None) + if document: + wx.CallLater(50, document.setFocus) + https://bitbucket.org/nvdaaddonteam/nvda/commits/67071d5d87f6/ Changeset: 67071d5d87f6 Branch: None User: mdcurran Date: 2013-08-23 01:58:37 Summary: MS word 2010 protected view support: When focus lands on the _WwB outer window, bounce focus to the document by instructing Windows to set focus to the document window using user32's SetFocus, and then also make sure that the document has been activated if not already. Re #1686 Affected #: 1 file diff --git a/source/NVDAObjects/IAccessible/winword.py b/source/NVDAObjects/IAccessible/winword.py index f89fc19..5bbd315 100644 --- a/source/NVDAObjects/IAccessible/winword.py +++ b/source/NVDAObjects/IAccessible/winword.py @@ -6,6 +6,7 @@ from comtypes import COMError import comtypes.automation import comtypes.client +import ctypes import NVDAHelper from logHandler import log import oleacc @@ -14,7 +15,6 @@ import speech import controlTypes import textInfos import eventHandler -import wx from . import IAccessible from NVDAObjects.window.winword import WordDocument @@ -83,5 +83,10 @@ class ProtectedDocumentPane(IAccessible): return document=next((x for x in self.children if isinstance(x,WordDocument)), None) if document: - wx.CallLater(50, document.setFocus) - + curThreadID=ctypes.windll.kernel32.GetCurrentThreadId() + ctypes.windll.user32.AttachThreadInput(curThreadID,document.windowThreadID,True) + ctypes.windll.user32.SetFocus(document.windowHandle) + ctypes.windll.user32.AttachThreadInput(curThreadID,document.windowThreadID,False) + if not document.WinwordWindowObject.active: + document.WinwordWindowObject.activate() + https://bitbucket.org/nvdaaddonteam/nvda/commits/25cd26b0c208/ Changeset: 25cd26b0c208 Branch: None User: mdcurran Date: 2013-09-13 05:11:48 Summary: expose the last sayAll mode used (CURSOR_CARET or CURSOR_REVIEW) via sayAllHandler.lastSayAllMode. Affected #: 1 file diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py index e2cb085..1d9d531 100644 --- a/source/sayAllHandler.py +++ b/source/sayAllHandler.py @@ -19,6 +19,7 @@ CURSOR_CARET=0 CURSOR_REVIEW=1 _generatorID = None +lastSayAllMode=None def _startGenerator(generator): global _generatorID @@ -90,6 +91,8 @@ def readObjectsHelper_generator(obj): yield def readText(cursor): + global lastSayAllMode + lastSayAllMode=cursor _startGenerator(readTextHelper_generator(cursor)) def readTextHelper_generator(cursor): https://bitbucket.org/nvdaaddonteam/nvda/commits/fd2b84e52960/ Changeset: fd2b84e52960 Branch: None User: mdcurran Date: 2013-09-13 05:23:50 Summary: Allow scripts to request that sayAll should be restarted after they execute, via an optional script property: resumeSayAllMode, which should be set to one of the sayAll modes (CURSOR_CARET or CURSOR_REVIEW). * inputCore: Gestures now contain a 'wasInSayAll' property which is set to true if this gesture should be considered as having been executed while in sayAll. All gestures still cause sayAll to stop, but this allows communication that sayAll was running, back to scriptHandler. Modifiers are handled specifically so that sayAll is tracked via modifiers all the way up to a regular (non-modifier) gesture. * keyboardHandler keyUp event function: ensure that inputCore forgets about sayAll if there are no modifier keys held down at all. This is currently needed as inputCore does not have any concept of gesture releases. * config: add an 'allowSkimReadingInSayAll' boolean setting to the keyboard section of the config. * scriptHandler.executeScript: if the gesture was in sayAll, and the current script's resumeSayall mode agrees with sayAllhandlers last sayAll mode, and the user has confiered NVDA to handle skim reading in sayAll, then temporarily turn off speech while the script executes, and then restart the last sayAll mode. Affected #: 4 files diff --git a/source/config/__init__.py b/source/config/__init__.py index 5e0a4ee..0e0fb20 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -127,6 +127,7 @@ confspec = ConfigObj(StringIO( speakCommandKeys = boolean(default=false) speechInterruptForCharacters = boolean(default=true) speechInterruptForEnter = boolean(default=true) + allowSkimReadingInSayAll = boolean(default=False) [virtualBuffers] maxLineLength = integer(default=100) diff --git a/source/inputCore.py b/source/inputCore.py index 9feaf8f..977f9ea 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -14,6 +14,7 @@ import sys import os import itertools import configobj +import sayAllHandler import baseObject import scriptHandler import queueHandler @@ -38,6 +39,10 @@ class InputGesture(baseObject.AutoPropertyObject): """ cachePropertiesByDefault = True + #: indicates that sayAll was running before this gesture + #: @type: bool + wasInSayAll=False + def _get_identifiers(self): """The identifier(s) which will be used in input gesture maps to represent this gesture. These identifiers will be looked up in order until a match is found. @@ -254,6 +259,10 @@ class InputManager(baseObject.AutoPropertyObject): Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. """ + #: a modifier gesture was just executed while sayAll was running + #: @type: bool + lastModifierWasInSayAll=False + 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 @@ -284,6 +293,18 @@ class InputManager(baseObject.AutoPropertyObject): if focus.sleepMode is focus.SLEEP_FULL or (focus.sleepMode and not getattr(script, 'allowInSleepMode', False)): raise NoInputGestureAction + wasInSayAll=False + if gesture.isModifier: + if not self.lastModifierWasInSayAll: + wasInSayAll=self.lastModifierWasInSayAll=sayAllHandler.isRunning() + elif self.lastModifierWasInSayAll: + wasInSayAll=True + self.lastModifierWasInSayAll=False + else: + wasInSayAll=sayAllHandler.isRunning() + if wasInSayAll: + gesture.wasInSayAll=True + speechEffect = gesture.speechEffectWhenExecuted if speechEffect == gesture.SPEECHEFFECT_CANCEL: queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech) diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index e934d75..b7d68ce 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -191,6 +191,11 @@ def internal_keyUpEvent(vkCode,scanCode,extended,injected): if keyCode != stickyNVDAModifier: currentModifiers.discard(keyCode) + # help inputCore manage its sayAll state for keyboard modifiers -- inputCore itself has no concept of key releases + if not currentModifiers: + inputCore.manager.lastModifierWasInSayAll=False + + if keyCode in trappedKeys: trappedKeys.remove(keyCode) return False diff --git a/source/scriptHandler.py b/source/scriptHandler.py index 6e107ee..fa8dabc 100644 --- a/source/scriptHandler.py +++ b/source/scriptHandler.py @@ -7,6 +7,9 @@ import time import weakref import inspect +import config +import speech +import sayAllHandler import appModuleHandler import api import queueHandler @@ -153,6 +156,12 @@ def executeScript(script,gesture): if _isScriptRunning and lastScriptRef==scriptFunc: return gesture.send() _isScriptRunning=True + resumeSayAllMode=None + if config.conf['keyboard']['allowSkimReadingInSayAll']and gesture.wasInSayAll and getattr(script,'resumeSayAllMode',None)==sayAllHandler.lastSayAllMode: + resumeSayAllMode=sayAllHandler.lastSayAllMode + if resumeSayAllMode is not None: + oldSpeechMode=speech.speechMode + speech.speechMode=speech.speechMode_off try: scriptTime=time.time() scriptRef=weakref.ref(scriptFunc) @@ -167,6 +176,9 @@ def executeScript(script,gesture): log.exception("error executing script: %s with gesture %r"%(script,gesture.displayName)) finally: _isScriptRunning=False + if resumeSayAllMode is not None: + speech.speechMode=oldSpeechMode + sayAllHandler.readText(resumeSayAllMode) def getLastScriptRepeatCount(): """The count of how many times the most recent script has been executed. @@ -206,3 +218,4 @@ def isCurrentScript(scriptFunc): log.debugWarning("Could not get unbound method from parent frame instance",exc_info=True) return False return givenFunc==realFunc + https://bitbucket.org/nvdaaddonteam/nvda/commits/35cf14d1c4d6/ Changeset: 35cf14d1c4d6 Branch: None User: mdcurran Date: 2013-09-13 05:24:33 Summary: Mark various scripts with the appropriate resumeSayAllMode property. These include browse mode quick nav, cursor manager line, paragraph and page movement, all marked as caret. Editable text line and paragraph marked as caret. And review previous line / next line marked as review. Affected #: 4 files diff --git a/source/cursorManager.py b/source/cursorManager.py index 9ae3b89..4ad30dd 100644 --- a/source/cursorManager.py +++ b/source/cursorManager.py @@ -12,6 +12,7 @@ A cursor manager provides caret navigation and selection commands for a virtual import wx import baseObject import gui +import sayAllHandler import textInfos import api import speech @@ -127,9 +128,11 @@ class CursorManager(baseObject.ScriptableObject): def script_moveByPage_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,-config.conf["virtualBuffers"]["linesPerPage"],extraDetail=False) + script_moveByPage_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByPage_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,config.conf["virtualBuffers"]["linesPerPage"],extraDetail=False) + script_moveByPage_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByCharacter_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_CHARACTER,-1,extraDetail=True,handleSymbols=True) @@ -145,15 +148,19 @@ class CursorManager(baseObject.ScriptableObject): def script_moveByLine_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,-1) + script_moveByLine_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByLine_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,1) + script_moveByLine_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByParagraph_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_PARAGRAPH,-1) + script_moveByParagraph_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByParagraph_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_PARAGRAPH,1) + script_moveByParagraph_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_startOfLine(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_CHARACTER,posUnit=textInfos.UNIT_LINE,extraDetail=True,handleSymbols=True) diff --git a/source/editableText.py b/source/editableText.py index 4301556..57a8704 100755 --- a/source/editableText.py +++ b/source/editableText.py @@ -10,6 +10,7 @@ """ import time +import sayAllHandler import api import review from baseObject import ScriptableObject @@ -99,6 +100,7 @@ class EditableText(ScriptableObject): def script_caret_moveByLine(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_LINE) + script_caret_moveByLine.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_caret_moveByCharacter(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_CHARACTER) @@ -108,6 +110,8 @@ class EditableText(ScriptableObject): def script_caret_moveByParagraph(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_PARAGRAPH) + script_caret_moveByParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET + def _backspaceScriptHelper(self,unit,gesture): try: diff --git a/source/globalCommands.py b/source/globalCommands.py index 7283e14..614204e 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -521,6 +521,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.resumeSayAllMode=sayAllHandler.CURSOR_REVIEW def script_review_currentLine(self,gesture): info=api.getReviewPosition().copy() @@ -546,6 +547,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.resumeSayAllMode=sayAllHandler.CURSOR_REVIEW def script_review_bottom(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 0cdccd6..9468894 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -929,6 +929,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "next", nextError, readUnit) script.__doc__ = nextDoc script.__name__ = funcName + script.resumeSayAllMode=sayAllHandler.CURSOR_CARET setattr(cls, funcName, script) cls.__gestures["kb:%s" % key] = scriptName scriptName = "previous%s" % scriptSuffix @@ -936,6 +937,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "previous", prevError, readUnit) script.__doc__ = prevDoc script.__name__ = funcName + script.resumeSayAllMode=sayAllHandler.CURSOR_CARET setattr(cls, funcName, script) cls.__gestures["kb:shift+%s" % key] = scriptName @@ -1339,6 +1341,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte # Translators: the description for the next table row script on virtualBuffers. script_nextRow.__doc__ = _("moves to the next table row") + def script_previousRow(self, gesture): self._tableMovementScriptHelper(axis="row", movement="previous") # Translators: the description for the previous table row script on virtualBuffers. https://bitbucket.org/nvdaaddonteam/nvda/commits/61f17c11034f/ Changeset: 61f17c11034f Branch: None User: mdcurran Date: 2013-09-13 05:27:47 Summary: GUI: add an 'allow skim reading in say all' option to the keyboard settings dialog. Affected #: 1 file diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 534532d..8635c77 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -639,6 +639,11 @@ class KeyboardSettingsDialog(SettingsDialog): settingsSizer.Add(self.speechInterruptForEnterCheckBox,border=10,flag=wx.BOTTOM) # Translators: This is the label for a checkbox in the # keyboard settings dialog. + self.skimReadingInSayAllCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Allow skim &reading in Say All")) + self.skimReadingInSayAllCheckBox.SetValue(config.conf["keyboard"]["allowSkimReadingInSayAll"]) + settingsSizer.Add(self.skimReadingInSayAllCheckBox,border=10,flag=wx.BOTTOM) + # Translators: This is the label for a checkbox in the + # keyboard settings dialog. self.beepLowercaseCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Beep if typing lowercase letters when caps lock is on")) self.beepLowercaseCheckBox.SetValue(config.conf["keyboard"]["beepForLowercaseWithCapslock"]) settingsSizer.Add(self.beepLowercaseCheckBox,border=10,flag=wx.BOTTOM) @@ -661,6 +666,7 @@ class KeyboardSettingsDialog(SettingsDialog): config.conf["keyboard"]["speakTypedWords"]=self.wordsCheckBox.IsChecked() config.conf["keyboard"]["speechInterruptForCharacters"]=self.speechInterruptForCharsCheckBox.IsChecked() config.conf["keyboard"]["speechInterruptForEnter"]=self.speechInterruptForEnterCheckBox.IsChecked() + config.conf["keyboard"]["allowSkimReadingInSayAll"]=self.skimReadingInSayAllCheckBox.IsChecked() config.conf["keyboard"]["beepForLowercaseWithCapslock"]=self.beepLowercaseCheckBox.IsChecked() config.conf["keyboard"]["speakCommandKeys"]=self.commandKeysCheckBox.IsChecked() super(KeyboardSettingsDialog, self).onOk(evt) https://bitbucket.org/nvdaaddonteam/nvda/commits/9ad7d6ae94f9/ Changeset: 9ad7d6ae94f9 Branch: None User: mdcurran Date: 2013-09-13 05:28:37 Summary: Update the user guide to mention the 'allow skim reading in say all' setting. Affected #: 1 file diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 7d42534..fe20e7f 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -800,6 +800,9 @@ If on, this option will cause speech to be interrupted each time a character is ==== Speech interrupt for Enter key ==== If on, this option will cause speech to be interrupted each time the Enter key is pressed. This is on by default. +==== Allow skim reading in Say All ==== +If on, certain navigation commands (such as quick navigation in browse mode or moving by line or paragraph) do not stop Say All, rather Say All jumps to the new position and continues reading. + ==== Beep if Typing Lowercase Letters when Caps Lock is On ==== When enabled, a warning beep will be heard if a letter is typed with the shift key while caps lock is on. Generally, typing shifted letters with caps lock is unintentional and is usually due to not realising that caps lock is enabled. https://bitbucket.org/nvdaaddonteam/nvda/commits/17c8f977a349/ Changeset: 17c8f977a349 Branch: None User: mdcurran Date: 2013-09-16 03:13:02 Summary: Merge branch 'master' into next Affected #: 1 file diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 1d56024..512f347 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -21,6 +21,7 @@ - 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) +- Internet Explorer should no longer crash or become unusable when closing NVDA. (#3397) = 2013.2 = https://bitbucket.org/nvdaaddonteam/nvda/commits/04dff7b9aec1/ Changeset: 04dff7b9aec1 Branch: None User: mdcurran Date: 2013-09-16 03:39:59 Summary: Merge branch 't2766' into next. Incubates #2766 Affected #: 11 files diff --git a/source/config/__init__.py b/source/config/__init__.py index 1bf36dc..01cad05 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -103,6 +103,7 @@ confspec = ConfigObj(StringIO( speakCommandKeys = boolean(default=false) speechInterruptForCharacters = boolean(default=true) speechInterruptForEnter = boolean(default=true) + allowSkimReadingInSayAll = boolean(default=False) [virtualBuffers] maxLineLength = integer(default=100) diff --git a/source/cursorManager.py b/source/cursorManager.py index 74c2f51..7433508 100644 --- a/source/cursorManager.py +++ b/source/cursorManager.py @@ -12,6 +12,7 @@ A cursor manager provides caret navigation and selection commands for a virtual import wx import baseObject import gui +import sayAllHandler import textInfos import api import speech @@ -131,9 +132,11 @@ class CursorManager(baseObject.ScriptableObject): def script_moveByPage_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,-config.conf["virtualBuffers"]["linesPerPage"],extraDetail=False) + script_moveByPage_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByPage_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,config.conf["virtualBuffers"]["linesPerPage"],extraDetail=False) + script_moveByPage_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByCharacter_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_CHARACTER,-1,extraDetail=True,handleSymbols=True) @@ -149,15 +152,19 @@ class CursorManager(baseObject.ScriptableObject): def script_moveByLine_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,-1) + script_moveByLine_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByLine_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_LINE,1) + script_moveByLine_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByParagraph_back(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_PARAGRAPH,-1) + script_moveByParagraph_back.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_moveByParagraph_forward(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_PARAGRAPH,1) + script_moveByParagraph_forward.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_startOfLine(self,gesture): self._caretMovementScriptHelper(textInfos.UNIT_CHARACTER,posUnit=textInfos.UNIT_LINE,extraDetail=True,handleSymbols=True) diff --git a/source/editableText.py b/source/editableText.py index 4301556..57a8704 100755 --- a/source/editableText.py +++ b/source/editableText.py @@ -10,6 +10,7 @@ """ import time +import sayAllHandler import api import review from baseObject import ScriptableObject @@ -99,6 +100,7 @@ class EditableText(ScriptableObject): def script_caret_moveByLine(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_LINE) + script_caret_moveByLine.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_caret_moveByCharacter(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_CHARACTER) @@ -108,6 +110,8 @@ class EditableText(ScriptableObject): def script_caret_moveByParagraph(self,gesture): self._caretMovementScriptHelper(gesture, textInfos.UNIT_PARAGRAPH) + script_caret_moveByParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET + def _backspaceScriptHelper(self,unit,gesture): try: diff --git a/source/globalCommands.py b/source/globalCommands.py index 3b9b975..18ea815 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -590,6 +590,7 @@ class GlobalCommands(ScriptableObject): # 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 + script_review_previousLine.resumeSayAllMode=sayAllHandler.CURSOR_REVIEW def script_review_currentLine(self,gesture): info=api.getReviewPosition().copy() @@ -617,6 +618,7 @@ class GlobalCommands(ScriptableObject): # 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 + script_review_nextLine.resumeSayAllMode=sayAllHandler.CURSOR_REVIEW def script_review_bottom(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index e6b8ce3..0fdf86a 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -640,6 +640,11 @@ class KeyboardSettingsDialog(SettingsDialog): settingsSizer.Add(self.speechInterruptForEnterCheckBox,border=10,flag=wx.BOTTOM) # Translators: This is the label for a checkbox in the # keyboard settings dialog. + self.skimReadingInSayAllCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Allow skim &reading in Say All")) + self.skimReadingInSayAllCheckBox.SetValue(config.conf["keyboard"]["allowSkimReadingInSayAll"]) + settingsSizer.Add(self.skimReadingInSayAllCheckBox,border=10,flag=wx.BOTTOM) + # Translators: This is the label for a checkbox in the + # keyboard settings dialog. self.beepLowercaseCheckBox=wx.CheckBox(self,wx.NewId(),label=_("Beep if typing lowercase letters when caps lock is on")) self.beepLowercaseCheckBox.SetValue(config.conf["keyboard"]["beepForLowercaseWithCapslock"]) settingsSizer.Add(self.beepLowercaseCheckBox,border=10,flag=wx.BOTTOM) @@ -662,6 +667,7 @@ class KeyboardSettingsDialog(SettingsDialog): config.conf["keyboard"]["speakTypedWords"]=self.wordsCheckBox.IsChecked() config.conf["keyboard"]["speechInterruptForCharacters"]=self.speechInterruptForCharsCheckBox.IsChecked() config.conf["keyboard"]["speechInterruptForEnter"]=self.speechInterruptForEnterCheckBox.IsChecked() + config.conf["keyboard"]["allowSkimReadingInSayAll"]=self.skimReadingInSayAllCheckBox.IsChecked() config.conf["keyboard"]["beepForLowercaseWithCapslock"]=self.beepLowercaseCheckBox.IsChecked() config.conf["keyboard"]["speakCommandKeys"]=self.commandKeysCheckBox.IsChecked() super(KeyboardSettingsDialog, self).onOk(evt) diff --git a/source/inputCore.py b/source/inputCore.py index 67292d3..30f6c4b 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -15,6 +15,7 @@ import os import itertools import weakref import configobj +import sayAllHandler import baseObject import scriptHandler import queueHandler @@ -49,6 +50,10 @@ class InputGesture(baseObject.AutoPropertyObject): """ cachePropertiesByDefault = True + #: indicates that sayAll was running before this gesture + #: @type: bool + wasInSayAll=False + def _get_identifiers(self): """The identifier(s) which will be used in input gesture maps to represent this gesture. These identifiers will be looked up in order until a match is found. @@ -348,6 +353,10 @@ class InputManager(baseObject.AutoPropertyObject): Input includes key presses on the keyboard, as well as key presses on Braille displays, etc. """ + #: a modifier gesture was just executed while sayAll was running + #: @type: bool + lastModifierWasInSayAll=False + def __init__(self): #: The function to call when capturing gestures. #: If it returns C{False}, normal execution will be prevented. @@ -379,6 +388,18 @@ class InputManager(baseObject.AutoPropertyObject): if focus.sleepMode is focus.SLEEP_FULL or (focus.sleepMode and not getattr(script, 'allowInSleepMode', False)): raise NoInputGestureAction + wasInSayAll=False + if gesture.isModifier: + if not self.lastModifierWasInSayAll: + wasInSayAll=self.lastModifierWasInSayAll=sayAllHandler.isRunning() + elif self.lastModifierWasInSayAll: + wasInSayAll=True + self.lastModifierWasInSayAll=False + else: + wasInSayAll=sayAllHandler.isRunning() + if wasInSayAll: + gesture.wasInSayAll=True + speechEffect = gesture.speechEffectWhenExecuted if speechEffect == gesture.SPEECHEFFECT_CANCEL: queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech) diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index 139eed2..63d34c7 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -192,6 +192,11 @@ def internal_keyUpEvent(vkCode,scanCode,extended,injected): if keyCode != stickyNVDAModifier: currentModifiers.discard(keyCode) + # help inputCore manage its sayAll state for keyboard modifiers -- inputCore itself has no concept of key releases + if not currentModifiers: + inputCore.manager.lastModifierWasInSayAll=False + + if keyCode in trappedKeys: trappedKeys.remove(keyCode) return False diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py index 1a3043f..d62e46d 100644 --- a/source/sayAllHandler.py +++ b/source/sayAllHandler.py @@ -19,6 +19,7 @@ CURSOR_CARET=0 CURSOR_REVIEW=1 _generatorID = None +lastSayAllMode=None def _startGenerator(generator): global _generatorID @@ -90,6 +91,8 @@ def readObjectsHelper_generator(obj): yield def readText(cursor): + global lastSayAllMode + lastSayAllMode=cursor _startGenerator(readTextHelper_generator(cursor)) def readTextHelper_generator(cursor): diff --git a/source/scriptHandler.py b/source/scriptHandler.py index 6e107ee..fa8dabc 100644 --- a/source/scriptHandler.py +++ b/source/scriptHandler.py @@ -7,6 +7,9 @@ import time import weakref import inspect +import config +import speech +import sayAllHandler import appModuleHandler import api import queueHandler @@ -153,6 +156,12 @@ def executeScript(script,gesture): if _isScriptRunning and lastScriptRef==scriptFunc: return gesture.send() _isScriptRunning=True + resumeSayAllMode=None + if config.conf['keyboard']['allowSkimReadingInSayAll']and gesture.wasInSayAll and getattr(script,'resumeSayAllMode',None)==sayAllHandler.lastSayAllMode: + resumeSayAllMode=sayAllHandler.lastSayAllMode + if resumeSayAllMode is not None: + oldSpeechMode=speech.speechMode + speech.speechMode=speech.speechMode_off try: scriptTime=time.time() scriptRef=weakref.ref(scriptFunc) @@ -167,6 +176,9 @@ def executeScript(script,gesture): log.exception("error executing script: %s with gesture %r"%(script,gesture.displayName)) finally: _isScriptRunning=False + if resumeSayAllMode is not None: + speech.speechMode=oldSpeechMode + sayAllHandler.readText(resumeSayAllMode) def getLastScriptRepeatCount(): """The count of how many times the most recent script has been executed. @@ -206,3 +218,4 @@ def isCurrentScript(scriptFunc): log.debugWarning("Could not get unbound method from parent frame instance",exc_info=True) return False return givenFunc==realFunc + diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 0cdccd6..9468894 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -929,6 +929,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "next", nextError, readUnit) script.__doc__ = nextDoc script.__name__ = funcName + script.resumeSayAllMode=sayAllHandler.CURSOR_CARET setattr(cls, funcName, script) cls.__gestures["kb:%s" % key] = scriptName scriptName = "previous%s" % scriptSuffix @@ -936,6 +937,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "previous", prevError, readUnit) script.__doc__ = prevDoc script.__name__ = funcName + script.resumeSayAllMode=sayAllHandler.CURSOR_CARET setattr(cls, funcName, script) cls.__gestures["kb:shift+%s" % key] = scriptName @@ -1339,6 +1341,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte # Translators: the description for the next table row script on virtualBuffers. script_nextRow.__doc__ = _("moves to the next table row") + def script_previousRow(self, gesture): self._tableMovementScriptHelper(axis="row", movement="previous") # Translators: the description for the previous table row script on virtualBuffers. diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 4967ef7..df6f381 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -800,6 +800,9 @@ If on, this option will cause speech to be interrupted each time a character is ==== Speech interrupt for Enter key ==== If on, this option will cause speech to be interrupted each time the Enter key is pressed. This is on by default. +==== Allow skim reading in Say All ==== +If on, certain navigation commands (such as quick navigation in browse mode or moving by line or paragraph) do not stop Say All, rather Say All jumps to the new position and continues reading. + ==== Beep if Typing Lowercase Letters when Caps Lock is On ==== When enabled, a warning beep will be heard if a letter is typed with the shift key while caps lock is on. Generally, typing shifted letters with caps lock is unintentional and is usually due to not realising that caps lock is enabled. https://bitbucket.org/nvdaaddonteam/nvda/commits/603494add2b7/ Changeset: 603494add2b7 Branch: None User: mdcurran Date: 2013-09-16 04:01:39 Summary: Merge branch 'master' into t1686 Affected #: 83 files diff --git a/contributors.txt b/contributors.txt index ed1df41..8e64bbc 100644 --- a/contributors.txt +++ b/contributors.txt @@ -131,5 +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/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 6535727..7743ad5 100644 --- a/nvdaHelper/remote/winword.cpp +++ b/nvdaHelper/remote/winword.cpp @@ -35,6 +35,11 @@ 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_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 @@ -47,6 +52,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 @@ -105,6 +123,7 @@ using namespace std; #define wdCharacter 1 #define wdWord 2 +#define wdParagraph 4 #define wdLine 5 #define wdCharacterFormatting 13 @@ -142,6 +161,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) @@ -195,6 +215,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; @@ -212,6 +296,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; @@ -365,6 +468,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) { @@ -556,15 +663,28 @@ 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; + 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); + 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); @@ -576,34 +696,33 @@ 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; + } 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; + } } - 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); @@ -619,9 +738,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); @@ -637,6 +756,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/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; 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/__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/NVDAObjects/window/winword.py b/source/NVDAObjects/window/winword.py index d43cdbf..a2e7830 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,78 @@ 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 + +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={ + # 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"), +} + storyTypeLocalizedLabels={ wdCommentsStory:_("Comments"), wdEndnotesStory:_("Endnotes"), @@ -94,6 +167,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") @@ -128,6 +218,7 @@ formatConfigFlagsMap={ "reportComments":4096, "reportHeadings":8192, "autoLanguageSwitching":16384, + "reportRevisions":32768, } class WordDocumentTextInfo(textInfos.TextInfo): @@ -136,7 +227,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 @@ -174,13 +265,12 @@ 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) 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)) @@ -194,7 +284,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] @@ -226,8 +316,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) @@ -237,7 +349,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)) @@ -413,10 +543,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..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 @@ -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/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/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={ diff --git a/source/globalCommands.py b/source/globalCommands.py index d0a3258..7283e14 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -748,7 +748,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 28d89ea..534532d 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1010,6 +1010,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) @@ -1089,6 +1094,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 7ae3e9c..e934d75 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -145,6 +145,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: @@ -212,6 +219,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. """ This diff is so big that we needed to truncate the remainder. https://bitbucket.org/nvdaaddonteam/nvda/commits/00d104a9840b/ Changeset: 00d104a9840b Branch: None User: mdcurran Date: 2013-09-16 04:08:06 Summary: Merge branch 't1686' into next. Incubates #1686 Affected #: 3 files diff --git a/nvdaHelper/remote/winword.cpp b/nvdaHelper/remote/winword.cpp index 07ac388..7743ad5 100644 --- a/nvdaHelper/remote/winword.cpp +++ b/nvdaHelper/remote/winword.cpp @@ -68,6 +68,9 @@ using namespace std; #define wdDISPID_STYLE_NAMELOCAL 0 #define wdDISPID_RANGE_SPELLINGERRORS 316 #define wdDISPID_SPELLINGERRORS_COUNT 1 +#define wdDISPID_RANGE_APPLICATION 1000 +#define wdDISPID_APPLICATION_ISSANDBOX 492 + #define wdDISPID_RANGE_FONT 5 #define wdDISPID_FONT_COLOR 159 #define wdDISPID_FONT_BOLD 130 @@ -516,11 +519,20 @@ void generateXMLAttribsForFormatting(IDispatch* pDispatchRange, int startOffset, } } if(formatConfig&formatConfig_reportSpellingErrors) { - IDispatchPtr pDispatchSpellingErrors=NULL; - if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_SPELLINGERRORS,VT_DISPATCH,&pDispatchSpellingErrors)==S_OK&&pDispatchSpellingErrors) { - _com_dispatch_raw_propget(pDispatchSpellingErrors,wdDISPID_SPELLINGERRORS_COUNT,VT_I4,&iVal); - if(iVal>0) { - formatAttribsStream<<L"invalid-spelling=\""<<iVal<<L"\" "; + IDispatchPtr pDispatchApplication=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_APPLICATION ,VT_DISPATCH,&pDispatchApplication)==S_OK && pDispatchApplication) { + bool isSandbox = true; + // We need to ironically enter the if block if the call to get IsSandbox property fails + // for backward compatibility because IsSandbox was introduced with word 2010 and earlier versions will return a failure for this property access. + // This however, means that if this property access fails for some reason in word 2010, then we will incorrectly enter this section. + if(_com_dispatch_raw_propget(pDispatchApplication,wdDISPID_APPLICATION_ISSANDBOX ,VT_BOOL,&isSandbox)!=S_OK || !isSandbox ) { + IDispatchPtr pDispatchSpellingErrors=NULL; + if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_SPELLINGERRORS,VT_DISPATCH,&pDispatchSpellingErrors)==S_OK&&pDispatchSpellingErrors) { + _com_dispatch_raw_propget(pDispatchSpellingErrors,wdDISPID_SPELLINGERRORS_COUNT,VT_I4,&iVal); + if(iVal>0) { + formatAttribsStream<<L"invalid-spelling=\""<<iVal<<L"\" "; + } + } } } } diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 40f2151..08d9e46 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -1797,4 +1797,5 @@ _staticMap={ ("NUIDialog",oleacc.ROLE_SYSTEM_CLIENT):"NUIDialogClient", ("_WwN",oleacc.ROLE_SYSTEM_TEXT):"winword.SpellCheckErrorField", ("_WwO",oleacc.ROLE_SYSTEM_TEXT):"winword.SpellCheckErrorField", + ("_WwB",oleacc.ROLE_SYSTEM_CLIENT):"winword.ProtectedDocumentPane", } diff --git a/source/NVDAObjects/IAccessible/winword.py b/source/NVDAObjects/IAccessible/winword.py index c7080285..5bbd315 100644 --- a/source/NVDAObjects/IAccessible/winword.py +++ b/source/NVDAObjects/IAccessible/winword.py @@ -6,6 +6,7 @@ from comtypes import COMError import comtypes.automation import comtypes.client +import ctypes import NVDAHelper from logHandler import log import oleacc @@ -13,6 +14,7 @@ import winUser import speech import controlTypes import textInfos +import eventHandler from . import IAccessible from NVDAObjects.window.winword import WordDocument @@ -66,3 +68,25 @@ class SpellCheckErrorField(IAccessible,WordDocument): if errorText: speech.speakText(errorText) speech.speakSpelling(errorText) + + +class ProtectedDocumentPane(IAccessible): + """The pane that gets focus in case a document opens in protected mode in word + This is mapped to the window class _WWB and role oleacc.ROLE_SYSTEM_CLIENT + """ + + def event_gainFocus(self): + """On gaining focus, simply set the focus on a child of type word document. + This is just a container window. + """ + if eventHandler.isPendingEvents("gainFocus"): + return + document=next((x for x in self.children if isinstance(x,WordDocument)), None) + if document: + curThreadID=ctypes.windll.kernel32.GetCurrentThreadId() + ctypes.windll.user32.AttachThreadInput(curThreadID,document.windowThreadID,True) + ctypes.windll.user32.SetFocus(document.windowHandle) + ctypes.windll.user32.AttachThreadInput(curThreadID,document.windowThreadID,False) + if not document.WinwordWindowObject.active: + document.WinwordWindowObject.activate() + https://bitbucket.org/nvdaaddonteam/nvda/commits/ca1d151113ff/ Changeset: ca1d151113ff Branch: None User: mdcurran Date: 2013-09-16 08:45:39 Summary: Lists in browse mode: report the number of controls (items) in the list, rather than also including text nodes (spaces). E.g. navbar on www.nvaccess.org now reports as 8 items, rather than 14. Note that a new _childControlCount attribute has been exposed on controlField nodes coming from virtualBuffers. Affected #: 2 files diff --git a/nvdaHelper/vbufBase/storage.cpp b/nvdaHelper/vbufBase/storage.cpp index 7a9854a..a5eb6d9 100644 --- a/nvdaHelper/vbufBase/storage.cpp +++ b/nvdaHelper/vbufBase/storage.cpp @@ -207,8 +207,10 @@ void VBufStorage_fieldNode_t::generateAttributesForMarkupOpeningTag(std::wstring s<<L"isBlock=\""<<this->isBlock<<L"\" "; s<<L"isHidden=\""<<this->isHidden<<L"\" "; int childCount=0; + int childControlCount=0; for(VBufStorage_fieldNode_t* child=this->firstChild;child!=NULL;child=child->next) { ++childCount; + if((child->length)>0&&child->firstChild) ++childControlCount; } int parentChildCount=1; int indexInParent=0; @@ -219,7 +221,7 @@ void VBufStorage_fieldNode_t::generateAttributesForMarkupOpeningTag(std::wstring for(VBufStorage_fieldNode_t* next=this->next;next!=NULL;next=next->next) { ++parentChildCount; } - s<<L"_childcount=\""<<childCount<<L"\" _indexInParent=\""<<indexInParent<<L"\" _parentChildCount=\""<<parentChildCount<<L"\" "; + s<<L"_childcount=\""<<childCount<<L"\" _childcontrolcount=\""<<childControlCount<<L"\" _indexInParent=\""<<indexInParent<<L"\" _parentChildCount=\""<<parentChildCount<<L"\" "; text+=s.str(); for(VBufStorage_attributeMap_t::iterator i=this->attributes.begin();i!=this->attributes.end();++i) { text+=i->first; diff --git a/source/speech.py b/source/speech.py index 8278fba..24e2a66 100755 --- a/source/speech.py +++ b/source/speech.py @@ -888,7 +888,7 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD formatConfig=config.conf["documentFormatting"] presCat=attrs.getPresentationCategory(ancestorAttrs,formatConfig, reason=reason) - childCount=int(attrs.get('_childcount',"0")) + childControlCount=int(attrs.get('_childcontrolcount',"0")) if reason==controlTypes.REASON_FOCUS or attrs.get('alwaysReportName',False): name=attrs.get('name',"") else: @@ -945,7 +945,7 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD if speakEntry and fieldType=="start_addedToControlFieldStack" and role==controlTypes.ROLE_LIST and controlTypes.STATE_READONLY in states: # List. # Translators: Speaks number of items in a list (example output: list with 5 items). - return roleText+" "+_("with %s items")%childCount + return roleText+" "+_("with %s items")%childControlCount elif fieldType=="start_addedToControlFieldStack" and role==controlTypes.ROLE_TABLE and tableID: # Table. return " ".join((roleText, getSpeechTextForProperties(_tableID=tableID, rowCount=attrs.get("table-rowcount"), columnCount=attrs.get("table-columncount")),levelText)) https://bitbucket.org/nvdaaddonteam/nvda/commits/051bc82be1bf/ Changeset: 051bc82be1bf Branch: None User: mdcurran Date: 2013-09-16 08:49:09 Summary: Merge branch 't2151' into next. Incubates #2151 Affected #: 2 files diff --git a/nvdaHelper/vbufBase/storage.cpp b/nvdaHelper/vbufBase/storage.cpp index 7a9854a..a5eb6d9 100644 --- a/nvdaHelper/vbufBase/storage.cpp +++ b/nvdaHelper/vbufBase/storage.cpp @@ -207,8 +207,10 @@ void VBufStorage_fieldNode_t::generateAttributesForMarkupOpeningTag(std::wstring s<<L"isBlock=\""<<this->isBlock<<L"\" "; s<<L"isHidden=\""<<this->isHidden<<L"\" "; int childCount=0; + int childControlCount=0; for(VBufStorage_fieldNode_t* child=this->firstChild;child!=NULL;child=child->next) { ++childCount; + if((child->length)>0&&child->firstChild) ++childControlCount; } int parentChildCount=1; int indexInParent=0; @@ -219,7 +221,7 @@ void VBufStorage_fieldNode_t::generateAttributesForMarkupOpeningTag(std::wstring for(VBufStorage_fieldNode_t* next=this->next;next!=NULL;next=next->next) { ++parentChildCount; } - s<<L"_childcount=\""<<childCount<<L"\" _indexInParent=\""<<indexInParent<<L"\" _parentChildCount=\""<<parentChildCount<<L"\" "; + s<<L"_childcount=\""<<childCount<<L"\" _childcontrolcount=\""<<childControlCount<<L"\" _indexInParent=\""<<indexInParent<<L"\" _parentChildCount=\""<<parentChildCount<<L"\" "; text+=s.str(); for(VBufStorage_attributeMap_t::iterator i=this->attributes.begin();i!=this->attributes.end();++i) { text+=i->first; diff --git a/source/speech.py b/source/speech.py index 8278fba..24e2a66 100755 --- a/source/speech.py +++ b/source/speech.py @@ -888,7 +888,7 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD formatConfig=config.conf["documentFormatting"] presCat=attrs.getPresentationCategory(ancestorAttrs,formatConfig, reason=reason) - childCount=int(attrs.get('_childcount',"0")) + childControlCount=int(attrs.get('_childcontrolcount',"0")) if reason==controlTypes.REASON_FOCUS or attrs.get('alwaysReportName',False): name=attrs.get('name',"") else: @@ -945,7 +945,7 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD if speakEntry and fieldType=="start_addedToControlFieldStack" and role==controlTypes.ROLE_LIST and controlTypes.STATE_READONLY in states: # List. # Translators: Speaks number of items in a list (example output: list with 5 items). - return roleText+" "+_("with %s items")%childCount + return roleText+" "+_("with %s items")%childControlCount elif fieldType=="start_addedToControlFieldStack" and role==controlTypes.ROLE_TABLE and tableID: # Table. return " ".join((roleText, getSpeechTextForProperties(_tableID=tableID, rowCount=attrs.get("table-rowcount"), columnCount=attrs.get("table-columncount")),levelText)) https://bitbucket.org/nvdaaddonteam/nvda/commits/d604742d48f4/ Changeset: d604742d48f4 Branch: next User: mdcurran Date: 2013-09-17 01:50:42 Summary: Merge branch 'master' into next Affected #: 2 files diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 512f347..33f17cd 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -22,6 +22,7 @@ - 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) - Internet Explorer should no longer crash or become unusable when closing NVDA. (#3397) +- Physical movement and other events on some newer computers are no longer treated as inappropriate key presses. Previously, this silenced speech and sometimes triggered NVDA commands. (#3468) = 2013.2 = diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index df6f381..b440f81 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -633,7 +633,7 @@ The Voice Settings dialog box contains the following options: ==== Voice ==== The first option that you land on in this dialog is a combo box listing all the voices of the current synthesizer that you have installed. You can use the arrow keys to listen to all the various choices. -Left and Up arrow take you up in the list, while right and down arrow moves you down in the list. +Left and Up arrow take you up in the list, while right and down arrow move you down in the list. ==== Variant ==== If you are using the Espeak synthesizer that is packaged with NVDA, this is a combo box that lets you select the Variant the synthesizer should speak with. 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.