#braille.py #A part of NonVisual Desktop Access (NVDA) #Copyright (C) 2008-2014 NV Access Limited #This file is covered by the GNU General Public License. #See the file COPYING for more details. import api import controlTypes import eventHandler import winUser from . import IAccessible, getNVDAObjectFromEvent from NVDAObjects import NVDAObjectTextInfo from NVDAObjects.behaviors import EditableText from comtypes import GUID, COMError, IServiceProvider from comtypes.gen.AcrobatAccessLib import IAccID, IGetPDDomNode, IPDDomElement from logHandler import log SID_AccID = GUID("{449D454B-1F46-497e-B2B6-3357AED9912B}") SID_GetPDDomNode = GUID("{C0A1D5E9-1142-4cf3-B607-82FC3B96A4DF}") stdNamesToRoles = { # Part? Art? "Sect": controlTypes.ROLE_SECTION, "Div": controlTypes.ROLE_SECTION, "BlockQuote": controlTypes.ROLE_BLOCKQUOTE, "Caption": controlTypes.ROLE_CAPTION, # Toc? Toci? Index? Nonstruct? Private? # Table, TR, TH, TD covered by IAccessible "L": controlTypes.ROLE_LIST, "LI": controlTypes.ROLE_LISTITEM, "Lbl": controlTypes.ROLE_LABEL, # LBody "P": controlTypes.ROLE_PARAGRAPH, "H": controlTypes.ROLE_HEADING, # H1 to H6 handled separately # Span, Quote, Note, Reference, BibEntry, Code, Figure "Formula": controlTypes.ROLE_MATH, "Form": controlTypes.ROLE_FORM, } def normalizeStdName(stdName): if "H1" <= stdName <= "H6": return controlTypes.ROLE_HEADING, stdName[1] try: return stdNamesToRoles[stdName], None except KeyError: pass raise LookupError class AcrobatNode(IAccessible): def initOverlayClass(self): try: serv = self.IAccessibleObject.QueryInterface(IServiceProvider) except COMError: log.debugWarning("Could not get IServiceProvider") return if self.event_objectID > 0: self.accID = self.event_objectID elif self.event_childID > 0: self.accID = self.event_childID else: try: self.accID = serv.QueryService(SID_AccID, IAccID).get_accID() except COMError: self.accID = None # Get the IPDDomNode. try: self.pdDomNode = serv.QueryService(SID_GetPDDomNode, IGetPDDomNode).get_PDDomNode(self.IAccessibleChildID) except COMError: self.pdDomNode = None if self.pdDomNode: # If this node has IPDDomElement, query to that. try: self.pdDomNode = self.pdDomNode.QueryInterface(IPDDomElement) except COMError: pass def _get_role(self): try: return normalizeStdName(self.pdDomNode.GetStdName())[0] except (AttributeError, LookupError, COMError): pass role = super(AcrobatNode, self).role if role == controlTypes.ROLE_PANE: # Pane doesn't make sense for nodes in a document. role = controlTypes.ROLE_TEXTFRAME return role def scrollIntoView(self): try: self.pdDomNode.ScrollTo() except (AttributeError, COMError): log.debugWarning("IPDDomNode::ScrollTo failed", exc_info=True) def _isEqual(self, other): if self.windowHandle == other.windowHandle and self.accID and other.accID: return self.accID == other.accID return super(AcrobatNode, self)._isEqual(other) def _getNodeMathMl(self, node): tag = node.GetTagName() yield "<%s" % tag # Output relevant attributes. if tag == "mfenced": for attr in "open", "close", "separators": val = node.GetAttribute(attr, "XML-1.00") if val: yield ' %s="%s"' % (attr, val) yield ">" val = node.GetValue() if val: yield val else: for childNum in xrange(node.GetChildCount()): try: subNode = node.GetChild(childNum).QueryInterface(IPDDomElement) except COMError: continue for sub in self._getNodeMathMl(subNode): yield sub yield "" % tag def _get_mathMl(self): # There could be other stuff before the math element. Ug. for childNum in xrange(self.pdDomNode.GetChildCount()): try: child = self.pdDomNode.GetChild(childNum).QueryInterface(IPDDomElement) except COMError: continue if child.GetTagName() == "math": return "".join(self._getNodeMathMl(child)) raise LookupError class RootNode(AcrobatNode): shouldAllowIAccessibleFocusEvent = True def event_valueChange(self): # Acrobat has indicated that a page has died and been replaced by a new one. if not self.isInForeground: # If this isn't in the foreground, it doesn't matter, # as focus will be fired on the correct object when it is in the foreground again. return # The new page has the same event params, so we must bypass NVDA's IAccessible caching. obj = getNVDAObjectFromEvent(self.windowHandle, winUser.OBJID_CLIENT, 0) if not obj: return eventHandler.queueEvent("gainFocus",obj) class Document(RootNode): def _get_treeInterceptorClass(self): import virtualBuffers.adobeAcrobat return virtualBuffers.adobeAcrobat.AdobeAcrobat def _get_shouldAllowIAccessibleFocusEvent(self): # HACK: #1659: When moving the focus, Acrobat sometimes fires focus on the document before firing it on the real focus; # e.g. when tabbing through a multi-page form. # This causes extraneous verbosity. # Therefore, if already focused inside this document, only allow focus on the document if it has no active descendant. if api.getFocusObject().windowHandle == self.windowHandle: try: return self.IAccessibleObject.accFocus in (None, 0) except COMError: pass return super(Document, self).shouldAllowIAccessibleFocusEvent class RootTextNode(RootNode): """The message text node that appears instead of the document when the document is not available. """ def _get_parent(self): #hack: This code should be taken out once the crash is fixed in Adobe Reader X. #If going parent on a root text node after saying ok to the accessibility options (untagged) and before the processing document dialog appears, Reader X will crash. return api.getDesktopObject() class AcrobatTextInfo(NVDAObjectTextInfo): def _getStoryText(self): return self.obj.value or "" def _getCaretOffset(self): caret = getNVDAObjectFromEvent(self.obj.windowHandle, winUser.OBJID_CARET, 0) if not caret: raise RuntimeError("No caret") try: return int(caret.description) except (ValueError, TypeError): raise RuntimeError("Bad caret index") class EditableTextNode(EditableText, AcrobatNode): TextInfo = AcrobatTextInfo def event_valueChange(self): pass class AcrobatSDIWindowClient(IAccessible): def initOverlayClass(self): if self.name or not self.parent: return # HACK: There are three client objects, one with a name and two without. # The unnamed objects (probably manufactured by Acrobat) have broken next and previous relationships. # The unnamed objects' parent/grandparent is the named object, but when descending into the named object, the unnamed objects are skipped. # Given the brokenness of the unnamed objects, just skip them completely and use the parent/grandparent when they are encountered. try: acc = self.IAccessibleObject.accParent if not acc.accName(0): acc = acc.accParent except COMError: return self.IAccessibleObject = acc self.invalidateCache() class BadFocusStates(AcrobatNode): """An object which reports focus states when it shouldn't. """ def _get_states(self): states = super(BadFocusStates, self).states states.difference_update({controlTypes.STATE_FOCUSABLE, controlTypes.STATE_FOCUSED}) return states def findExtraOverlayClasses(obj, clsList): """Determine the most appropriate class(es) for Acrobat objects. This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method. """ role = obj.role states = obj.states if role == controlTypes.ROLE_DOCUMENT or (role == controlTypes.ROLE_PAGE and controlTypes.STATE_READONLY in states): clsList.append(Document) elif obj.event_childID == 0 and obj.event_objectID == winUser.OBJID_CLIENT: # Other root node. if role == controlTypes.ROLE_EDITABLETEXT: clsList.append(RootTextNode) else: clsList.append(RootNode) elif role == controlTypes.ROLE_EDITABLETEXT: if {controlTypes.STATE_READONLY, controlTypes.STATE_FOCUSABLE, controlTypes.STATE_LINKED} <= states: # HACK: Acrobat sets focus states on text nodes beneath links, # making them appear as read only editable text fields. clsList.append(BadFocusStates) elif controlTypes.STATE_FOCUSABLE in states: clsList.append(EditableTextNode) clsList.append(AcrobatNode)