11 new commits in nvda: https://bitbucket.org/nvdaaddonteam/nvda/commits/1fe0135823d4/ Changeset: 1fe0135823d4 Branch: None User: jteh Date: 2014-02-20 07:36:35 Summary: In browse mode, labels on landmarks are now reported. They are also included in the Elements List dialog. Affected #: 1 file diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index c49a1c6..1435c46 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -233,6 +233,10 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList = [] landmark = attrs.get("landmark") if formatConfig["reportLandmarks"] and fieldType == "start_addedToControlFieldStack" and landmark: + try: + textList.append(attrs["name"]) + except KeyError: + pass textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) textList.append(super(VirtualBufferTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason)) return " ".join(textList) @@ -241,6 +245,10 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList = [] landmark = field.get("landmark") if formatConfig["reportLandmarks"] and reportStart and landmark and field.get("_startOfNode"): + try: + textList.append(field["name"]) + except KeyError: + pass # Translators: This is spoken and brailled to indicate a landmark (example output: main landmark). textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) text = super(VirtualBufferTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig) @@ -275,7 +283,7 @@ class ElementsListDialog(wx.Dialog): # in the browse mode Elements List dialog. ("landmark", _("Lan&dmarks")), ) - Element = collections.namedtuple("Element", ("textInfo", "text", "parent")) + Element = collections.namedtuple("Element", ("textInfo", "docHandle", "id", "text", "parent")) lastSelectedElementType=0 @@ -354,11 +362,16 @@ class ElementsListDialog(wx.Dialog): parentElements = [] for node, start, end in self.vbuf._iterNodesByType(elType): + docHandle = ctypes.c_int() + id = ctypes.c_int() + NVDAHelper.localLib.VBuf_getIdentifierFromControlFieldNode(self.vbuf.VBufHandle, node, ctypes.byref(docHandle), ctypes.byref(id)) + docHandle = docHandle.value + id = id.value elInfo = self.vbuf.makeTextInfo(textInfos.offsets.Offsets(start, end)) # Find the parent element, if any. for parent in reversed(parentElements): - if self.isChildElement(elType, parent.textInfo, elInfo): + if self.isChildElement(elType, parent, elInfo, docHandle, id): break else: # We're not a child of this parent, so this parent has no more children and can be removed from the stack. @@ -368,7 +381,8 @@ class ElementsListDialog(wx.Dialog): # Note that parentElements will be empty at this point, as all parents are no longer relevant and have thus been removed from the stack. parent = None - element = self.Element(elInfo, self.getElementText(elInfo, elType), parent) + element = self.Element(elInfo, docHandle, id, + self.getElementText(elInfo, docHandle, id, elType), parent) self._elements.append(element) if not self._initialElement and elInfo.compareEndPoints(caret, "startToStart") > 0: @@ -430,36 +444,41 @@ class ElementsListDialog(wx.Dialog): self.activateButton.Enable() self.moveButton.Enable() - def _getControlFieldAttrib(self, info, attrib): + def _getControlFieldAttribs(self, info, docHandle, id): info = info.copy() info.expand(textInfos.UNIT_CHARACTER) for field in reversed(info.getTextWithFields()): if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"): # Not a control field. continue - val = field.field.get(attrib) - if val: - return val - return None + attrs = field.field + if int(attrs["controlIdentifier_docHandle"]) == docHandle and int(attrs["controlIdentifier_ID"]) == id: + return attrs + raise LookupError - def getElementText(self, elInfo, elType): + def getElementText(self, elInfo, docHandle, id, elType): if elType == "landmark": - landmark = self._getControlFieldAttrib(elInfo, "landmark") + attrs = self._getControlFieldAttribs(elInfo, docHandle, id) + landmark = attrs.get("landmark") if landmark: - return aria.landmarkRoles[landmark] + name = attrs.get("name", "") + if name: + name += " " + return name + aria.landmarkRoles[landmark] else: return elInfo.text.strip() - def isChildElement(self, elType, parent, child): - if parent.isOverlapping(child): + def isChildElement(self, elType, parent, childInfo, childDoc, childId): + if parent.textInfo.isOverlapping(childInfo): return True elif elType == "heading": try: - if int(self._getControlFieldAttrib(child, "level")) > int(self._getControlFieldAttrib(parent, "level")): + if (int(self._getControlFieldAttribs(childInfo, childDoc, childId)["level"]) + > int(self._getControlFieldAttribs(parent.textInfo, parent.docHandle, parent.id)["level"])): return True - except (ValueError, TypeError): + except (KeyError, ValueError, TypeError): return False return False https://bitbucket.org/nvdaaddonteam/nvda/commits/90cff713c7f3/ Changeset: 90cff713c7f3 Branch: None User: jteh Date: 2014-02-20 07:48:17 Summary: Initial work on support for the region role in browse mode. Regions are treated as landmarks, but only if they are labelled. Also, the word "landmark" isn't reported, since this seems rather superfluous. At present, reporting works correctly, but quick navigation will jump to unlabelled regions and the Elements List breaks when unlabelled regions are encountered. Affected #: 2 files diff --git a/source/aria.py b/source/aria.py index a23d2e4..8c54bc4 100755 --- a/source/aria.py +++ b/source/aria.py @@ -1,6 +1,6 @@ #aria.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2009-2012 NV Access Limited +#Copyright (C) 2009-2014 NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -79,4 +79,7 @@ landmarkRoles = { "search": _("search"), # Translators: Reported for the form landmark, normally found on web pages. "form": _("form"), + # Strictly speaking, region isn't a landmark, but it is very similar. + # Translators: Reported for a significant region, normally found on web pages. + "region": _("region"), } diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 1435c46..95f4d99 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -201,6 +201,10 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(self.obj.makeTextInfo(textInfos.offsets.Offsets(start, end)).text) attrs["table-%sheadertext" % axis] = "\n".join(textList) + if attrs.get("landmark") == "region" and not attrs.get("name"): + # We only consider region to be a landmark if it has a name. + del attrs["landmark"] + return attrs def _normalizeFormatField(self, attrs): @@ -237,7 +241,11 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(attrs["name"]) except KeyError: pass - textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) + if landmark == "region": + # The word landmark is superfluous for regions. + textList.append(aria.landmarkRoles[landmark]) + else: + textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) textList.append(super(VirtualBufferTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason)) return " ".join(textList) @@ -249,8 +257,12 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(field["name"]) except KeyError: pass - # Translators: This is spoken and brailled to indicate a landmark (example output: main landmark). - textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) + if landmark == "region": + # The word landmark is superfluous for regions. + textList.append(aria.landmarkRoles[landmark]) + else: + # Translators: This is spoken and brailled to indicate a landmark (example output: main landmark). + textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) text = super(VirtualBufferTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig) if text: textList.append(text) @@ -459,12 +471,10 @@ class ElementsListDialog(wx.Dialog): def getElementText(self, elInfo, docHandle, id, elType): if elType == "landmark": attrs = self._getControlFieldAttribs(elInfo, docHandle, id) - landmark = attrs.get("landmark") - if landmark: - name = attrs.get("name", "") - if name: - name += " " - return name + aria.landmarkRoles[landmark] + name = attrs.get("name", "") + if name: + name += " " + return name + aria.landmarkRoles[attrs["landmark"]] else: return elInfo.text.strip() https://bitbucket.org/nvdaaddonteam/nvda/commits/1d07c6da11ad/ Changeset: 1d07c6da11ad Branch: None User: jteh Date: 2014-02-20 07:48:17 Summary: When searching for a node by attributes in a virtual buffer, use regular expressions matching against a string of requested attributes. This allows us to match against multiple, completely separate options without needing to write a complex custom query engine. It also allows for easier enhancement in future. This is necessary to support regions as a landmark because normal landmarks don't need names but regions do. Affected #: 6 files diff --git a/nvdaHelper/interfaces/vbuf/vbuf.idl b/nvdaHelper/interfaces/vbuf/vbuf.idl index b952a0b..47e5877 100755 --- a/nvdaHelper/interfaces/vbuf/vbuf.idl +++ b/nvdaHelper/interfaces/vbuf/vbuf.idl @@ -124,13 +124,14 @@ interface VBuf { * @param buffer the virtual buffer to use * @param offset offset in the buffer to start searching from * @param direction which direction to search - * @param attribsString the attributes the node should contain + * @param attribs the attributes to search + * @param regexp regular expression the requested attributes must match * @param startOffset memory where the start offset of the found node can be placed * @param endOffset memory where the end offset of the found node will be placed * @param foundNode the found field node * @return non-zero if the node is found. */ - int findNodeByAttributes([in] VBufRemote_bufferHandle_t buffer, [in] int offset, [in] int direction, [in,string] const wchar_t* attribsString, [out] int *startOffset, [out] int *endOffset, [out] VBufRemote_nodeHandle_t* foundNode); + int findNodeByAttributes([in] VBufRemote_bufferHandle_t buffer, [in] int offset, [in] int direction, [in,string] const wchar_t* attribs, [in,string] const wchar_t* regexp, [out] int *startOffset, [out] int *endOffset, [out] VBufRemote_nodeHandle_t* foundNode); /** * Retreaves the current selection offsets for the buffer diff --git a/nvdaHelper/remote/vbufRemote.cpp b/nvdaHelper/remote/vbufRemote.cpp index 149a8c8..2905eb2 100755 --- a/nvdaHelper/remote/vbufRemote.cpp +++ b/nvdaHelper/remote/vbufRemote.cpp @@ -109,10 +109,10 @@ int VBufRemote_getIdentifierFromControlFieldNode(VBufRemote_bufferHandle_t buffe return res; } -int VBufRemote_findNodeByAttributes(VBufRemote_bufferHandle_t buffer, int offset, int direction, const wchar_t* attribsString, int *startOffset, int *endOffset, VBufRemote_nodeHandle_t* foundNode) { +int VBufRemote_findNodeByAttributes(VBufRemote_bufferHandle_t buffer, int offset, int direction, const wchar_t* attribs, const wchar_t* regexp, int *startOffset, int *endOffset, VBufRemote_nodeHandle_t* foundNode) { VBufBackend_t* backend=(VBufBackend_t*)buffer; backend->lock.acquire(); - *foundNode=(VBufRemote_nodeHandle_t)(backend->findNodeByAttributes(offset,(VBufStorage_findDirection_t)direction,attribsString,startOffset,endOffset)); + *foundNode=(VBufRemote_nodeHandle_t)(backend->findNodeByAttributes(offset,(VBufStorage_findDirection_t)direction,attribs,regexp,startOffset,endOffset)); backend->lock.release(); return (*foundNode)!=0; } diff --git a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp index 9092b8b..68a232a 100755 --- a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp +++ b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp @@ -281,6 +281,9 @@ bool isLabelVisible(IAccessible2* acc) { return true; } +const vector<wstring>ATTRLIST_ROLES(1, L"IAccessible2::attribute_xml-roles"); +const wregex REGEX_PRESENTATION_ROLE(L"IAccessible2\\\\:\\\\:attribute_xml-roles:.*\\bpresentation\\b.*;"); + VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, IAccessibleTable* paccTable, IAccessibleTable2* paccTable2, long tableID, @@ -428,7 +431,7 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, parentNode->isBlock=isBlockElement; // force isHidden to True if this has an ARIA role of presentation but its focusble -- Gecko does not hide this itself. - if((states&STATE_SYSTEM_FOCUSABLE)&&parentNode->matchAttributes(L"IAccessible2\\:\\:attribute_xml-roles:~wpresentation;")) { + if((states&STATE_SYSTEM_FOCUSABLE)&&parentNode->matchAttributes(ATTRLIST_ROLES, REGEX_PRESENTATION_ROLE)) { parentNode->isHidden=true; } BSTR name=NULL; diff --git a/nvdaHelper/vbufBase/storage.cpp b/nvdaHelper/vbufBase/storage.cpp index 2a91469..ed7fbd3 100644 --- a/nvdaHelper/vbufBase/storage.cpp +++ b/nvdaHelper/vbufBase/storage.cpp @@ -19,6 +19,10 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <list> #include <set> #include <sstream> +#include <regex> +#include <vector> +#include <sstream> +#include <algorithm> #include <common/xml.h> #include <common/log.h> #include "utils.h" @@ -130,42 +134,30 @@ VBufStorage_fieldNode_t* VBufStorage_fieldNode_t::nextNodeInTree(int direction, return tempNode; } -bool VBufStorage_fieldNode_t::matchAttributes(const std::wstring& attribsString) { - LOG_DEBUG(L"using attributes of "<<attribsString); - multiValueAttribsMap attribsMap; - multiValueAttribsStringToMap(attribsString,attribsMap); - bool outerMatch=true; - for(multiValueAttribsMap::iterator i=attribsMap.begin();i!=attribsMap.end();++i) { - LOG_DEBUG(L"Checking for attrib "<<i->first); - VBufStorage_attributeMap_t::iterator foundIterator=attributes.find(i->first); - const std::wstring& foundValue=(foundIterator!=attributes.end())?foundIterator->second:L""; - wstring foundValueTrailingSpaces=L" "+foundValue+L" "; - LOG_DEBUG(L"node's value for this attribute is "<<foundValue); - multiValueAttribsMap::iterator upperBound=attribsMap.upper_bound(i->first); - bool innerMatch=false; - for(multiValueAttribsMap::iterator j=i;j!=upperBound;++j) { - i=j; - if(innerMatch) - continue; - LOG_DEBUG(L"Checking value "<<j->second); - if( - // Word match. - (j->second.compare(0, 2, L"~w")==0&&foundValueTrailingSpaces.find(L" "+j->second.substr(2)+L" ")!=wstring::npos) - // Full match. - ||(j->second==foundValue) - ) { - LOG_DEBUG(L"values match"); - innerMatch=true; - } - } - outerMatch=innerMatch; - if(!outerMatch) { - LOG_DEBUG(L"given attributes do not match node's attributes, returning false"); - return false; +inline void outputEscapedAttribute(wostringstream& out, const wstring& text) { + for (wstring::const_iterator it = text.begin(); it != text.end(); ++it) { + switch (*it) { + case L':': + case L';': + case L'\\': + out << L"\\"; + default: + out << *it; } } - LOG_DEBUG(L"Given attributes match node's attributes, returning true"); - return true; +} + +bool VBufStorage_fieldNode_t::matchAttributes(const std::vector<std::wstring>& attribs, const std::wregex& regexp) { + wostringstream test; + for (vector<wstring>::const_iterator attribName = attribs.begin(); attribName != attribs.end(); ++attribName) { + outputEscapedAttribute(test, *attribName); + test << L":"; + VBufStorage_attributeMap_t::const_iterator foundAttrib = attributes.find(*attribName); + if (foundAttrib != attributes.end()) + outputEscapedAttribute(test, foundAttrib->second); + test << L";"; + } + return regex_match(test.str(), regexp); } int VBufStorage_fieldNode_t::calculateOffsetInTree() const { @@ -896,7 +888,7 @@ VBufStorage_textContainer_t* VBufStorage_buffer_t::getTextInRange(int startOffs return new VBufStorage_textContainer_t(text); } -VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring& attribsString, int *startOffset, int *endOffset) { +VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring& attribs, const std::wstring ®exp, int *startOffset, int *endOffset) { if(this->rootNode==NULL) { LOG_DEBUGWARNING(L"buffer empty, returning NULL"); return NULL; @@ -922,6 +914,18 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, LOG_DEBUGWARNING(L"Could not find node at offset "<<offset<<L", returning NULL"); return NULL; } + // Split attribs at spaces. + vector<wstring> attribsList; + copy(istream_iterator<wstring, wchar_t, std::char_traits<wchar_t>>(wistringstream(attribs)), + istream_iterator<wstring, wchar_t, std::char_traits<wchar_t>>(), + back_inserter<vector<wstring> >(attribsList)); + wregex regexObj; + try { + regexObj=wregex(regexp); + } catch (...) { + LOG_ERROR(L"Error in regular expression"); + return NULL; + } LOG_DEBUG(L"starting from node "<<node->getDebugInfo()); LOG_DEBUG(L"initial start is "<<bufferStart<<L" and initial end is "<<bufferEnd); if(direction==VBufStorage_findDirection_forward) { @@ -931,7 +935,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, bufferEnd=bufferStart+node->length; LOG_DEBUG(L"start is now "<<bufferStart<<L" and end is now "<<bufferEnd); LOG_DEBUG(L"Checking node "<<node->getDebugInfo()); - if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsString)) { + if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsList,regexObj)) { LOG_DEBUG(L"found a match"); break; } @@ -943,7 +947,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, bufferStart+=tempRelativeStart; bufferEnd=bufferStart+node->length; LOG_DEBUG(L"start is now "<<bufferStart<<L" and end is now "<<bufferEnd); - if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsString)) { + if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsList,regexObj)) { //Skip first containing parent match or parent match where offset hasn't changed if((bufferStart==offset)||(!skippedFirstMatch&&bufferStart<offset&&bufferEnd>offset)) { LOG_DEBUG(L"skipping initial parent"); @@ -963,7 +967,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, if(node) { bufferEnd=bufferStart+node->length; } - } while(node!=NULL&&(node->isHidden||!node->matchAttributes(attribsString))); + } while(node!=NULL&&(node->isHidden||!node->matchAttributes(attribsList,regexObj))); LOG_DEBUG(L"end is now "<<bufferEnd); } if(node==NULL) { diff --git a/nvdaHelper/vbufBase/storage.h b/nvdaHelper/vbufBase/storage.h index 2a1e27a..3456584 100644 --- a/nvdaHelper/vbufBase/storage.h +++ b/nvdaHelper/vbufBase/storage.h @@ -19,6 +19,8 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <map> #include <set> #include <list> +#include <vector> +#include <regex> /** * values to indicate a direction for searching @@ -224,7 +226,7 @@ class VBufStorage_fieldNode_t { * @param attribsString the string containing the attributes, each attribute can have multiple values to match on. * @return true if the attributes exist, false otherwize. */ - bool matchAttributes(const std::wstring& attribsString); + bool matchAttributes(const std::vector<std::wstring>& attribs, const std::wregex& regexp); /** * True if this node his hidden - searches will not locate this node. @@ -554,12 +556,13 @@ class VBufStorage_buffer_t { * Finds a field node that contains particular attributes. * @param offset offset in the buffer to start searching from, if -1 then starts at the root of the buffer. * @param direction which direction to search - * @param attribsString the attributes the node should contain + * @param attribs the attributes to search + * @param regexp regular expression the requested attributes must match * @param startOffset memory where the start offset of the found node can be placed * @param endOffset memory where the end offset of the found node will be placed * @return the found field node */ - virtual VBufStorage_fieldNode_t* findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring &attribsString, int *startOffset, int *endOffset); + virtual VBufStorage_fieldNode_t* findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring &attribs, const std::wstring ®exp, int *startOffset, int *endOffset); /** * Retreaves the current selection offsets for the buffer diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 95f4d99..78cb24d 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -42,23 +42,53 @@ VBufStorage_findDirection_up=2 VBufRemote_nodeHandle_t=ctypes.c_ulonglong -def VBufStorage_findMatch_word(word): - return "~w%s" % word - -def dictToMultiValueAttribsString(d): - mainList=[] - for k,v in d.iteritems(): - k=unicode(k).replace(':','\\:').replace(';','\\;').replace(',','\\,') - valList=[] - for i in v: - if i is None: - i="" +class VBufStorage_findMatch_word(unicode): + pass + +FINDBYATTRIBS_ESCAPE_TABLE = { + # Symbols that are escaped in the attributes string. + ord(u":"): ur"\\:", + ord(u";"): ur"\\;", + ord(u"\\"): u"\\\\\\\\", +} +# Symbols that must be escaped for a regular expression. +FINDBYATTRIBS_ESCAPE_TABLE.update({(ord(s), u"\\" + s) for s in u"^$.*+?()[]{}|"}) +def _prepareForFindByAttributes(attribs): + escape = lambda text: unicode(text).translate(FINDBYATTRIBS_ESCAPE_TABLE) + reqAttrs = [] + regexp = [] + if isinstance(attribs, dict): + # Single option. + attribs = (attribs,) + # All options will match against all requested attributes, + # so first build the list of requested attributes. + for option in attribs: + for name in option: + reqAttrs.append(unicode(name)) + # Now build the regular expression. + for option in attribs: + optRegexp = [] + for name in reqAttrs: + optRegexp.append("%s:" % escape(name)) + values = option.get(name) + if not values: + # The value isn't tested for this attribute, so match any value. + optRegexp.append(r"(?:\\;|[^;])+;") + elif values[0] is None: + # There must be no value for this attribute. + optRegexp.append(r";") + elif isinstance(values[0], VBufStorage_findMatch_word): + # Assume all are word matches. + optRegexp.append(r"(?:\\;|[^;])*\b(?:") + optRegexp.append("|".join(escape(val) for val in values)) + optRegexp.append(r")\b(?:\\;|[^;])*;") else: - i=unicode(i).replace(':','\\:').replace(';','\\;').replace(',','\\,') - valList.append(i) - attrib="%s:%s"%(k,",".join(valList)) - mainList.append(attrib) - return "%s;"%";".join(mainList) + # Assume all are exact matches. + optRegexp.append("(?:") + optRegexp.append("|".join(escape(val) for val in values)) + optRegexp.append(");") + regexp.append("".join(optRegexp)) + return u" ".join(reqAttrs), u"|".join(regexp) class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): @@ -908,7 +938,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte return self._iterNodesByAttribs(attribs, direction, offset) def _iterNodesByAttribs(self, attribs, direction="next", offset=-1): - attribs=dictToMultiValueAttribsString(attribs) + reqAttrs, regexp = _prepareForFindByAttributes(attribs) startOffset=ctypes.c_int() endOffset=ctypes.c_int() if direction=="next": @@ -922,7 +952,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte while True: try: node=VBufRemote_nodeHandle_t() - NVDAHelper.localLib.VBuf_findNodeByAttributes(self.VBufHandle,offset,direction,attribs,ctypes.byref(startOffset),ctypes.byref(endOffset),ctypes.byref(node)) + NVDAHelper.localLib.VBuf_findNodeByAttributes(self.VBufHandle,offset,direction,reqAttrs,regexp,ctypes.byref(startOffset),ctypes.byref(endOffset),ctypes.byref(node)) except: return if not node: https://bitbucket.org/nvdaaddonteam/nvda/commits/6d31b774aa3b/ Changeset: 6d31b774aa3b Branch: None User: jteh Date: 2014-02-20 07:48:17 Summary: When navigating by landmark, don't navigate to regions with no label. Affected #: 3 files diff --git a/source/virtualBuffers/MSHTML.py b/source/virtualBuffers/MSHTML.py index 010a7d5..bcb5284 100644 --- a/source/virtualBuffers/MSHTML.py +++ b/source/virtualBuffers/MSHTML.py @@ -6,7 +6,7 @@ from comtypes import COMError import eventHandler -from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word +from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word, VBufStorage_findMatch_notEmpty import controlTypes import NVDAObjects.IAccessible.MSHTML import winUser @@ -261,7 +261,11 @@ class MSHTML(VirtualBuffer): elif nodeType=="focusable": attrs={"IAccessible::state_%s"%oleacc.STATE_SYSTEM_FOCUSABLE:[1]} elif nodeType=="landmark": - attrs={"HTMLAttrib::role":[VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles]} + attrs = [ + {"HTMLAttrib::role": [VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles if lr != "region"]}, + {"HTMLAttrib::role": [VBufStorage_findMatch_word("region")], + "name": [VBufStorage_findMatch_notEmpty]} + ] elif nodeType == "embeddedObject": attrs = {"IHTMLDOMNode::nodeName": ["OBJECT","EMBED","APPLET"]} elif nodeType == "separator": diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 78cb24d..45f94c3 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -44,6 +44,7 @@ VBufRemote_nodeHandle_t=ctypes.c_ulonglong class VBufStorage_findMatch_word(unicode): pass +VBufStorage_findMatch_notEmpty = object() FINDBYATTRIBS_ESCAPE_TABLE = { # Symbols that are escaped in the attributes string. @@ -72,7 +73,10 @@ def _prepareForFindByAttributes(attribs): optRegexp.append("%s:" % escape(name)) values = option.get(name) if not values: - # The value isn't tested for this attribute, so match any value. + # The value isn't tested for this attribute, so match any (or no) value. + optRegexp.append(r"(?:\\;|[^;])*;") + elif values[0] is VBufStorage_findMatch_notEmpty: + # There must be a value for this attribute. optRegexp.append(r"(?:\\;|[^;])+;") elif values[0] is None: # There must be no value for this attribute. diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py index 6d4f8e5..6c34466 100755 --- a/source/virtualBuffers/gecko_ia2.py +++ b/source/virtualBuffers/gecko_ia2.py @@ -4,7 +4,7 @@ #See the file COPYING for more details. #Copyright (C) 2008-2012 NV Access Limited -from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word +from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word, VBufStorage_findMatch_notEmpty import treeInterceptorHandler import controlTypes import NVDAObjects.IAccessible.mozilla @@ -217,7 +217,11 @@ class Gecko_ia2(VirtualBuffer): elif nodeType=="focusable": attrs={"IAccessible::state_%s"%oleacc.STATE_SYSTEM_FOCUSABLE:[1]} elif nodeType=="landmark": - attrs={"IAccessible2::attribute_xml-roles":[VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles]} + attrs = [ + {"IAccessible2::attribute_xml-roles": [VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles if lr != "region"]}, + {"IAccessible2::attribute_xml-roles": [VBufStorage_findMatch_word("region")], + "name": [VBufStorage_findMatch_notEmpty]} + ] elif nodeType=="embeddedObject": attrs={"IAccessible2::attribute_tag":self._searchableTagValues(["embed","object","applet"])} else: https://bitbucket.org/nvdaaddonteam/nvda/commits/db819f3a6f61/ Changeset: db819f3a6f61 Branch: None User: jteh Date: 2014-02-20 07:51:44 Summary: Merge branch 't1195' into next Incubates #1195. Affected #: 0 files https://bitbucket.org/nvdaaddonteam/nvda/commits/41e2ffc5e8b2/ Changeset: 41e2ffc5e8b2 Branch: None User: jteh Date: 2014-02-20 07:51:56 Summary: Merge branch 't3741' into next Incubates #3741. Affected #: 9 files diff --git a/nvdaHelper/interfaces/vbuf/vbuf.idl b/nvdaHelper/interfaces/vbuf/vbuf.idl index b952a0b..47e5877 100755 --- a/nvdaHelper/interfaces/vbuf/vbuf.idl +++ b/nvdaHelper/interfaces/vbuf/vbuf.idl @@ -124,13 +124,14 @@ interface VBuf { * @param buffer the virtual buffer to use * @param offset offset in the buffer to start searching from * @param direction which direction to search - * @param attribsString the attributes the node should contain + * @param attribs the attributes to search + * @param regexp regular expression the requested attributes must match * @param startOffset memory where the start offset of the found node can be placed * @param endOffset memory where the end offset of the found node will be placed * @param foundNode the found field node * @return non-zero if the node is found. */ - int findNodeByAttributes([in] VBufRemote_bufferHandle_t buffer, [in] int offset, [in] int direction, [in,string] const wchar_t* attribsString, [out] int *startOffset, [out] int *endOffset, [out] VBufRemote_nodeHandle_t* foundNode); + int findNodeByAttributes([in] VBufRemote_bufferHandle_t buffer, [in] int offset, [in] int direction, [in,string] const wchar_t* attribs, [in,string] const wchar_t* regexp, [out] int *startOffset, [out] int *endOffset, [out] VBufRemote_nodeHandle_t* foundNode); /** * Retreaves the current selection offsets for the buffer diff --git a/nvdaHelper/remote/vbufRemote.cpp b/nvdaHelper/remote/vbufRemote.cpp index 149a8c8..2905eb2 100755 --- a/nvdaHelper/remote/vbufRemote.cpp +++ b/nvdaHelper/remote/vbufRemote.cpp @@ -109,10 +109,10 @@ int VBufRemote_getIdentifierFromControlFieldNode(VBufRemote_bufferHandle_t buffe return res; } -int VBufRemote_findNodeByAttributes(VBufRemote_bufferHandle_t buffer, int offset, int direction, const wchar_t* attribsString, int *startOffset, int *endOffset, VBufRemote_nodeHandle_t* foundNode) { +int VBufRemote_findNodeByAttributes(VBufRemote_bufferHandle_t buffer, int offset, int direction, const wchar_t* attribs, const wchar_t* regexp, int *startOffset, int *endOffset, VBufRemote_nodeHandle_t* foundNode) { VBufBackend_t* backend=(VBufBackend_t*)buffer; backend->lock.acquire(); - *foundNode=(VBufRemote_nodeHandle_t)(backend->findNodeByAttributes(offset,(VBufStorage_findDirection_t)direction,attribsString,startOffset,endOffset)); + *foundNode=(VBufRemote_nodeHandle_t)(backend->findNodeByAttributes(offset,(VBufStorage_findDirection_t)direction,attribs,regexp,startOffset,endOffset)); backend->lock.release(); return (*foundNode)!=0; } diff --git a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp index 9092b8b..68a232a 100755 --- a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp +++ b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp @@ -281,6 +281,9 @@ bool isLabelVisible(IAccessible2* acc) { return true; } +const vector<wstring>ATTRLIST_ROLES(1, L"IAccessible2::attribute_xml-roles"); +const wregex REGEX_PRESENTATION_ROLE(L"IAccessible2\\\\:\\\\:attribute_xml-roles:.*\\bpresentation\\b.*;"); + VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, IAccessibleTable* paccTable, IAccessibleTable2* paccTable2, long tableID, @@ -428,7 +431,7 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, parentNode->isBlock=isBlockElement; // force isHidden to True if this has an ARIA role of presentation but its focusble -- Gecko does not hide this itself. - if((states&STATE_SYSTEM_FOCUSABLE)&&parentNode->matchAttributes(L"IAccessible2\\:\\:attribute_xml-roles:~wpresentation;")) { + if((states&STATE_SYSTEM_FOCUSABLE)&&parentNode->matchAttributes(ATTRLIST_ROLES, REGEX_PRESENTATION_ROLE)) { parentNode->isHidden=true; } BSTR name=NULL; diff --git a/nvdaHelper/vbufBase/storage.cpp b/nvdaHelper/vbufBase/storage.cpp index 2a91469..ed7fbd3 100644 --- a/nvdaHelper/vbufBase/storage.cpp +++ b/nvdaHelper/vbufBase/storage.cpp @@ -19,6 +19,10 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <list> #include <set> #include <sstream> +#include <regex> +#include <vector> +#include <sstream> +#include <algorithm> #include <common/xml.h> #include <common/log.h> #include "utils.h" @@ -130,42 +134,30 @@ VBufStorage_fieldNode_t* VBufStorage_fieldNode_t::nextNodeInTree(int direction, return tempNode; } -bool VBufStorage_fieldNode_t::matchAttributes(const std::wstring& attribsString) { - LOG_DEBUG(L"using attributes of "<<attribsString); - multiValueAttribsMap attribsMap; - multiValueAttribsStringToMap(attribsString,attribsMap); - bool outerMatch=true; - for(multiValueAttribsMap::iterator i=attribsMap.begin();i!=attribsMap.end();++i) { - LOG_DEBUG(L"Checking for attrib "<<i->first); - VBufStorage_attributeMap_t::iterator foundIterator=attributes.find(i->first); - const std::wstring& foundValue=(foundIterator!=attributes.end())?foundIterator->second:L""; - wstring foundValueTrailingSpaces=L" "+foundValue+L" "; - LOG_DEBUG(L"node's value for this attribute is "<<foundValue); - multiValueAttribsMap::iterator upperBound=attribsMap.upper_bound(i->first); - bool innerMatch=false; - for(multiValueAttribsMap::iterator j=i;j!=upperBound;++j) { - i=j; - if(innerMatch) - continue; - LOG_DEBUG(L"Checking value "<<j->second); - if( - // Word match. - (j->second.compare(0, 2, L"~w")==0&&foundValueTrailingSpaces.find(L" "+j->second.substr(2)+L" ")!=wstring::npos) - // Full match. - ||(j->second==foundValue) - ) { - LOG_DEBUG(L"values match"); - innerMatch=true; - } - } - outerMatch=innerMatch; - if(!outerMatch) { - LOG_DEBUG(L"given attributes do not match node's attributes, returning false"); - return false; +inline void outputEscapedAttribute(wostringstream& out, const wstring& text) { + for (wstring::const_iterator it = text.begin(); it != text.end(); ++it) { + switch (*it) { + case L':': + case L';': + case L'\\': + out << L"\\"; + default: + out << *it; } } - LOG_DEBUG(L"Given attributes match node's attributes, returning true"); - return true; +} + +bool VBufStorage_fieldNode_t::matchAttributes(const std::vector<std::wstring>& attribs, const std::wregex& regexp) { + wostringstream test; + for (vector<wstring>::const_iterator attribName = attribs.begin(); attribName != attribs.end(); ++attribName) { + outputEscapedAttribute(test, *attribName); + test << L":"; + VBufStorage_attributeMap_t::const_iterator foundAttrib = attributes.find(*attribName); + if (foundAttrib != attributes.end()) + outputEscapedAttribute(test, foundAttrib->second); + test << L";"; + } + return regex_match(test.str(), regexp); } int VBufStorage_fieldNode_t::calculateOffsetInTree() const { @@ -896,7 +888,7 @@ VBufStorage_textContainer_t* VBufStorage_buffer_t::getTextInRange(int startOffs return new VBufStorage_textContainer_t(text); } -VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring& attribsString, int *startOffset, int *endOffset) { +VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring& attribs, const std::wstring ®exp, int *startOffset, int *endOffset) { if(this->rootNode==NULL) { LOG_DEBUGWARNING(L"buffer empty, returning NULL"); return NULL; @@ -922,6 +914,18 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, LOG_DEBUGWARNING(L"Could not find node at offset "<<offset<<L", returning NULL"); return NULL; } + // Split attribs at spaces. + vector<wstring> attribsList; + copy(istream_iterator<wstring, wchar_t, std::char_traits<wchar_t>>(wistringstream(attribs)), + istream_iterator<wstring, wchar_t, std::char_traits<wchar_t>>(), + back_inserter<vector<wstring> >(attribsList)); + wregex regexObj; + try { + regexObj=wregex(regexp); + } catch (...) { + LOG_ERROR(L"Error in regular expression"); + return NULL; + } LOG_DEBUG(L"starting from node "<<node->getDebugInfo()); LOG_DEBUG(L"initial start is "<<bufferStart<<L" and initial end is "<<bufferEnd); if(direction==VBufStorage_findDirection_forward) { @@ -931,7 +935,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, bufferEnd=bufferStart+node->length; LOG_DEBUG(L"start is now "<<bufferStart<<L" and end is now "<<bufferEnd); LOG_DEBUG(L"Checking node "<<node->getDebugInfo()); - if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsString)) { + if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsList,regexObj)) { LOG_DEBUG(L"found a match"); break; } @@ -943,7 +947,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, bufferStart+=tempRelativeStart; bufferEnd=bufferStart+node->length; LOG_DEBUG(L"start is now "<<bufferStart<<L" and end is now "<<bufferEnd); - if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsString)) { + if(node->length>0&&!(node->isHidden)&&node->matchAttributes(attribsList,regexObj)) { //Skip first containing parent match or parent match where offset hasn't changed if((bufferStart==offset)||(!skippedFirstMatch&&bufferStart<offset&&bufferEnd>offset)) { LOG_DEBUG(L"skipping initial parent"); @@ -963,7 +967,7 @@ VBufStorage_fieldNode_t* VBufStorage_buffer_t::findNodeByAttributes(int offset, if(node) { bufferEnd=bufferStart+node->length; } - } while(node!=NULL&&(node->isHidden||!node->matchAttributes(attribsString))); + } while(node!=NULL&&(node->isHidden||!node->matchAttributes(attribsList,regexObj))); LOG_DEBUG(L"end is now "<<bufferEnd); } if(node==NULL) { diff --git a/nvdaHelper/vbufBase/storage.h b/nvdaHelper/vbufBase/storage.h index 2a1e27a..3456584 100644 --- a/nvdaHelper/vbufBase/storage.h +++ b/nvdaHelper/vbufBase/storage.h @@ -19,6 +19,8 @@ http://www.gnu.org/licenses/old-licenses/gpl-2.0.html #include <map> #include <set> #include <list> +#include <vector> +#include <regex> /** * values to indicate a direction for searching @@ -224,7 +226,7 @@ class VBufStorage_fieldNode_t { * @param attribsString the string containing the attributes, each attribute can have multiple values to match on. * @return true if the attributes exist, false otherwize. */ - bool matchAttributes(const std::wstring& attribsString); + bool matchAttributes(const std::vector<std::wstring>& attribs, const std::wregex& regexp); /** * True if this node his hidden - searches will not locate this node. @@ -554,12 +556,13 @@ class VBufStorage_buffer_t { * Finds a field node that contains particular attributes. * @param offset offset in the buffer to start searching from, if -1 then starts at the root of the buffer. * @param direction which direction to search - * @param attribsString the attributes the node should contain + * @param attribs the attributes to search + * @param regexp regular expression the requested attributes must match * @param startOffset memory where the start offset of the found node can be placed * @param endOffset memory where the end offset of the found node will be placed * @return the found field node */ - virtual VBufStorage_fieldNode_t* findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring &attribsString, int *startOffset, int *endOffset); + virtual VBufStorage_fieldNode_t* findNodeByAttributes(int offset, VBufStorage_findDirection_t direction, const std::wstring &attribs, const std::wstring ®exp, int *startOffset, int *endOffset); /** * Retreaves the current selection offsets for the buffer diff --git a/source/aria.py b/source/aria.py index a23d2e4..8c54bc4 100755 --- a/source/aria.py +++ b/source/aria.py @@ -1,6 +1,6 @@ #aria.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2009-2012 NV Access Limited +#Copyright (C) 2009-2014 NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -79,4 +79,7 @@ landmarkRoles = { "search": _("search"), # Translators: Reported for the form landmark, normally found on web pages. "form": _("form"), + # Strictly speaking, region isn't a landmark, but it is very similar. + # Translators: Reported for a significant region, normally found on web pages. + "region": _("region"), } diff --git a/source/virtualBuffers/MSHTML.py b/source/virtualBuffers/MSHTML.py index 010a7d5..bcb5284 100644 --- a/source/virtualBuffers/MSHTML.py +++ b/source/virtualBuffers/MSHTML.py @@ -6,7 +6,7 @@ from comtypes import COMError import eventHandler -from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word +from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word, VBufStorage_findMatch_notEmpty import controlTypes import NVDAObjects.IAccessible.MSHTML import winUser @@ -261,7 +261,11 @@ class MSHTML(VirtualBuffer): elif nodeType=="focusable": attrs={"IAccessible::state_%s"%oleacc.STATE_SYSTEM_FOCUSABLE:[1]} elif nodeType=="landmark": - attrs={"HTMLAttrib::role":[VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles]} + attrs = [ + {"HTMLAttrib::role": [VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles if lr != "region"]}, + {"HTMLAttrib::role": [VBufStorage_findMatch_word("region")], + "name": [VBufStorage_findMatch_notEmpty]} + ] elif nodeType == "embeddedObject": attrs = {"IHTMLDOMNode::nodeName": ["OBJECT","EMBED","APPLET"]} elif nodeType == "separator": diff --git a/source/virtualBuffers/__init__.py b/source/virtualBuffers/__init__.py index 1435c46..45f94c3 100644 --- a/source/virtualBuffers/__init__.py +++ b/source/virtualBuffers/__init__.py @@ -42,23 +42,57 @@ VBufStorage_findDirection_up=2 VBufRemote_nodeHandle_t=ctypes.c_ulonglong -def VBufStorage_findMatch_word(word): - return "~w%s" % word - -def dictToMultiValueAttribsString(d): - mainList=[] - for k,v in d.iteritems(): - k=unicode(k).replace(':','\\:').replace(';','\\;').replace(',','\\,') - valList=[] - for i in v: - if i is None: - i="" +class VBufStorage_findMatch_word(unicode): + pass +VBufStorage_findMatch_notEmpty = object() + +FINDBYATTRIBS_ESCAPE_TABLE = { + # Symbols that are escaped in the attributes string. + ord(u":"): ur"\\:", + ord(u";"): ur"\\;", + ord(u"\\"): u"\\\\\\\\", +} +# Symbols that must be escaped for a regular expression. +FINDBYATTRIBS_ESCAPE_TABLE.update({(ord(s), u"\\" + s) for s in u"^$.*+?()[]{}|"}) +def _prepareForFindByAttributes(attribs): + escape = lambda text: unicode(text).translate(FINDBYATTRIBS_ESCAPE_TABLE) + reqAttrs = [] + regexp = [] + if isinstance(attribs, dict): + # Single option. + attribs = (attribs,) + # All options will match against all requested attributes, + # so first build the list of requested attributes. + for option in attribs: + for name in option: + reqAttrs.append(unicode(name)) + # Now build the regular expression. + for option in attribs: + optRegexp = [] + for name in reqAttrs: + optRegexp.append("%s:" % escape(name)) + values = option.get(name) + if not values: + # The value isn't tested for this attribute, so match any (or no) value. + optRegexp.append(r"(?:\\;|[^;])*;") + elif values[0] is VBufStorage_findMatch_notEmpty: + # There must be a value for this attribute. + optRegexp.append(r"(?:\\;|[^;])+;") + elif values[0] is None: + # There must be no value for this attribute. + optRegexp.append(r";") + elif isinstance(values[0], VBufStorage_findMatch_word): + # Assume all are word matches. + optRegexp.append(r"(?:\\;|[^;])*\b(?:") + optRegexp.append("|".join(escape(val) for val in values)) + optRegexp.append(r")\b(?:\\;|[^;])*;") else: - i=unicode(i).replace(':','\\:').replace(';','\\;').replace(',','\\,') - valList.append(i) - attrib="%s:%s"%(k,",".join(valList)) - mainList.append(attrib) - return "%s;"%";".join(mainList) + # Assume all are exact matches. + optRegexp.append("(?:") + optRegexp.append("|".join(escape(val) for val in values)) + optRegexp.append(");") + regexp.append("".join(optRegexp)) + return u" ".join(reqAttrs), u"|".join(regexp) class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): @@ -201,6 +235,10 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(self.obj.makeTextInfo(textInfos.offsets.Offsets(start, end)).text) attrs["table-%sheadertext" % axis] = "\n".join(textList) + if attrs.get("landmark") == "region" and not attrs.get("name"): + # We only consider region to be a landmark if it has a name. + del attrs["landmark"] + return attrs def _normalizeFormatField(self, attrs): @@ -237,7 +275,11 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(attrs["name"]) except KeyError: pass - textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) + if landmark == "region": + # The word landmark is superfluous for regions. + textList.append(aria.landmarkRoles[landmark]) + else: + textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) textList.append(super(VirtualBufferTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason)) return " ".join(textList) @@ -249,8 +291,12 @@ class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo): textList.append(field["name"]) except KeyError: pass - # Translators: This is spoken and brailled to indicate a landmark (example output: main landmark). - textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) + if landmark == "region": + # The word landmark is superfluous for regions. + textList.append(aria.landmarkRoles[landmark]) + else: + # Translators: This is spoken and brailled to indicate a landmark (example output: main landmark). + textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) text = super(VirtualBufferTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig) if text: textList.append(text) @@ -459,12 +505,10 @@ class ElementsListDialog(wx.Dialog): def getElementText(self, elInfo, docHandle, id, elType): if elType == "landmark": attrs = self._getControlFieldAttribs(elInfo, docHandle, id) - landmark = attrs.get("landmark") - if landmark: - name = attrs.get("name", "") - if name: - name += " " - return name + aria.landmarkRoles[landmark] + name = attrs.get("name", "") + if name: + name += " " + return name + aria.landmarkRoles[attrs["landmark"]] else: return elInfo.text.strip() @@ -898,7 +942,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte return self._iterNodesByAttribs(attribs, direction, offset) def _iterNodesByAttribs(self, attribs, direction="next", offset=-1): - attribs=dictToMultiValueAttribsString(attribs) + reqAttrs, regexp = _prepareForFindByAttributes(attribs) startOffset=ctypes.c_int() endOffset=ctypes.c_int() if direction=="next": @@ -912,7 +956,7 @@ class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInte while True: try: node=VBufRemote_nodeHandle_t() - NVDAHelper.localLib.VBuf_findNodeByAttributes(self.VBufHandle,offset,direction,attribs,ctypes.byref(startOffset),ctypes.byref(endOffset),ctypes.byref(node)) + NVDAHelper.localLib.VBuf_findNodeByAttributes(self.VBufHandle,offset,direction,reqAttrs,regexp,ctypes.byref(startOffset),ctypes.byref(endOffset),ctypes.byref(node)) except: return if not node: diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py index 6d4f8e5..6c34466 100755 --- a/source/virtualBuffers/gecko_ia2.py +++ b/source/virtualBuffers/gecko_ia2.py @@ -4,7 +4,7 @@ #See the file COPYING for more details. #Copyright (C) 2008-2012 NV Access Limited -from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word +from . import VirtualBuffer, VirtualBufferTextInfo, VBufStorage_findMatch_word, VBufStorage_findMatch_notEmpty import treeInterceptorHandler import controlTypes import NVDAObjects.IAccessible.mozilla @@ -217,7 +217,11 @@ class Gecko_ia2(VirtualBuffer): elif nodeType=="focusable": attrs={"IAccessible::state_%s"%oleacc.STATE_SYSTEM_FOCUSABLE:[1]} elif nodeType=="landmark": - attrs={"IAccessible2::attribute_xml-roles":[VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles]} + attrs = [ + {"IAccessible2::attribute_xml-roles": [VBufStorage_findMatch_word(lr) for lr in aria.landmarkRoles if lr != "region"]}, + {"IAccessible2::attribute_xml-roles": [VBufStorage_findMatch_word("region")], + "name": [VBufStorage_findMatch_notEmpty]} + ] elif nodeType=="embeddedObject": attrs={"IAccessible2::attribute_tag":self._searchableTagValues(["embed","object","applet"])} else: https://bitbucket.org/nvdaaddonteam/nvda/commits/3d963157c035/ Changeset: 3d963157c035 Branch: None User: mdcurran Date: 2014-02-21 00:12:42 Summary: Ensure that text copied to clipboard from displayModel contains line breaks. Affected #: 1 file diff --git a/source/displayModel.py b/source/displayModel.py index 035ad7e..36e34d8 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -407,7 +407,7 @@ class DisplayModelTextInfo(OffsetsTextInfo): return startOffset,endOffset def _get_clipboardText(self): - return super(DisplayModelTextInfo,self).clipboardText.replace('\0',' ') + return "\r\n".join(x.strip('\r\n') for x in self.getTextInChunks(textInfos.UNIT_LINE)) def getTextInChunks(self,unit): #Specifically handle the line unit as we have the line offsets pre-calculated, and we can not guarantee lines end with \n https://bitbucket.org/nvdaaddonteam/nvda/commits/6b7430601ce9/ Changeset: 6b7430601ce9 Branch: None User: mdcurran Date: 2014-02-21 00:13:42 Summary: Merge branch 't3900' into next. Incubates #3900 Affected #: 1 file diff --git a/source/displayModel.py b/source/displayModel.py index 2b3dec1..75d6ee4 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -449,7 +449,7 @@ class DisplayModelTextInfo(OffsetsTextInfo): return startOffset,endOffset def _get_clipboardText(self): - return super(DisplayModelTextInfo,self).clipboardText.replace('\0',' ') + return "\r\n".join(x.strip('\r\n') for x in self.getTextInChunks(textInfos.UNIT_LINE)) def getTextInChunks(self,unit): #Specifically handle the line unit as we have the line offsets pre-calculated, and we can not guarantee lines end with \n https://bitbucket.org/nvdaaddonteam/nvda/commits/e3eceac18ac8/ Changeset: e3eceac18ac8 Branch: None User: jteh Date: 2014-02-22 09:27:48 Summary: The focus is now restored correctly when releasing alt+tab without actually switching applications in some configurations. We map switchEnd and some other events to the desktop window because the original window is invalid, so eventHandler.shouldAcceptEvent had to be taught this. Re #3897. Affected #: 1 file diff --git a/source/eventHandler.py b/source/eventHandler.py index e71b6c9..bfec029 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -194,6 +194,10 @@ def shouldAcceptEvent(eventName, windowHandle=None): if eventName == "alert" and winUser.getClassName(winUser.getAncestor(windowHandle, winUser.GA_PARENT)) == "ToastChildWindowClass": # Toast notifications. return True + if windowHandle == winUser.getDesktopWindow(): + # #3897: We fire some events such as switchEnd and menuEnd on the desktop window + # because the original window is now invalid. + return True fg = winUser.getForegroundWindow() if winUser.isDescendantWindow(fg, windowHandle): https://bitbucket.org/nvdaaddonteam/nvda/commits/7935e8f4fafb/ Changeset: 7935e8f4fafb Branch: None User: jteh Date: 2014-02-22 09:47:35 Summary: Stop ignoring the Firefox Page Bookmarked window, OpenOffice/LibreOffice context menus and possibly other cases. In eventHandler.shouldAcceptEvent, as well as testing whether the window is a descendant of the foreground window, also check whether it's root owner is a descendant of the foreground. Re #3899, #3905. Affected #: 1 file diff --git a/source/eventHandler.py b/source/eventHandler.py index bfec029..b27ead7 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -200,7 +200,8 @@ def shouldAcceptEvent(eventName, windowHandle=None): return True fg = winUser.getForegroundWindow() - if winUser.isDescendantWindow(fg, windowHandle): + if (winUser.isDescendantWindow(fg, windowHandle) + or winUser.isDescendantWindow(fg, winUser.getAncestor(windowHandle, winUser.GA_ROOTOWNER))): # This is for the foreground application. return True if (winUser.user32.GetWindowLongW(windowHandle, winUser.GWL_EXSTYLE) & winUser.WS_EX_TOPMOST https://bitbucket.org/nvdaaddonteam/nvda/commits/e55984bccda6/ Changeset: e55984bccda6 Branch: next User: jteh Date: 2014-02-22 09:49:31 Summary: Merge branch 't3831' into next Incubates #3831. Fixes #3899, #3905. Affected #: 1 file diff --git a/source/eventHandler.py b/source/eventHandler.py index e71b6c9..b27ead7 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -194,9 +194,14 @@ def shouldAcceptEvent(eventName, windowHandle=None): if eventName == "alert" and winUser.getClassName(winUser.getAncestor(windowHandle, winUser.GA_PARENT)) == "ToastChildWindowClass": # Toast notifications. return True + if windowHandle == winUser.getDesktopWindow(): + # #3897: We fire some events such as switchEnd and menuEnd on the desktop window + # because the original window is now invalid. + return True fg = winUser.getForegroundWindow() - if winUser.isDescendantWindow(fg, windowHandle): + if (winUser.isDescendantWindow(fg, windowHandle) + or winUser.isDescendantWindow(fg, winUser.getAncestor(windowHandle, winUser.GA_ROOTOWNER))): # This is for the foreground application. return True if (winUser.user32.GetWindowLongW(windowHandle, winUser.GWL_EXSTYLE) & winUser.WS_EX_TOPMOST 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.