Package virtualBuffers
[hide private]
[frames] | no frames]

Source Code for Package virtualBuffers

   1  import time 
   2  import threading 
   3  import ctypes 
   4  import collections 
   5  import itertools 
   6  import wx 
   7  import NVDAHelper 
   8  import XMLFormatting 
   9  import scriptHandler 
  10  from scriptHandler import isScriptWaiting 
  11  import speech 
  12  import NVDAObjects 
  13  import api 
  14  import sayAllHandler 
  15  import controlTypes 
  16  import textInfos.offsets 
  17  import config 
  18  import cursorManager 
  19  import gui 
  20  import eventHandler 
  21  import braille 
  22  import queueHandler 
  23  from logHandler import log 
  24  import ui 
  25  import aria 
  26  import nvwave 
  27  import treeInterceptorHandler 
  28  import watchdog 
  29   
  30  VBufStorage_findDirection_forward=0 
  31  VBufStorage_findDirection_back=1 
  32  VBufStorage_findDirection_up=2 
33 34 -def VBufStorage_findMatch_word(word):
35 return "~w%s" % word
36
37 -def dictToMultiValueAttribsString(d):
38 mainList=[] 39 for k,v in d.iteritems(): 40 k=unicode(k).replace(':','\\:').replace(';','\\;').replace(',','\\,') 41 valList=[] 42 for i in v: 43 if i is None: 44 i="" 45 else: 46 i=unicode(i).replace(':','\\:').replace(';','\\;').replace(',','\\,') 47 valList.append(i) 48 attrib="%s:%s"%(k,",".join(valList)) 49 mainList.append(attrib) 50 return "%s;"%";".join(mainList)
51
52 -class VirtualBufferTextInfo(textInfos.offsets.OffsetsTextInfo):
53 54 UNIT_CONTROLFIELD = "controlField" 55
56 - def _getFieldIdentifierFromOffset(self, offset):
57 startOffset = ctypes.c_int() 58 endOffset = ctypes.c_int() 59 docHandle = ctypes.c_int() 60 ID = ctypes.c_int() 61 NVDAHelper.localLib.VBuf_locateControlFieldNodeAtOffset(self.obj.VBufHandle, offset, ctypes.byref(startOffset), ctypes.byref(endOffset), ctypes.byref(docHandle), ctypes.byref(ID)) 62 return docHandle.value, ID.value
63
64 - def _getOffsetsFromFieldIdentifier(self, docHandle, ID):
65 node = NVDAHelper.localLib.VBuf_getControlFieldNodeWithIdentifier(self.obj.VBufHandle, docHandle, ID) 66 if not node: 67 raise LookupError 68 start = ctypes.c_int() 69 end = ctypes.c_int() 70 NVDAHelper.localLib.VBuf_getFieldNodeOffsets(self.obj.VBufHandle, node, ctypes.byref(start), ctypes.byref(end)) 71 return start.value, end.value
72
73 - def _getPointFromOffset(self,offset):
74 o=self._getNVDAObjectFromOffset(offset) 75 return textInfos.Point(o.location[0],o.location[1])
76
77 - def _getNVDAObjectFromOffset(self,offset):
78 docHandle,ID=self._getFieldIdentifierFromOffset(offset) 79 return self.obj.getNVDAObjectFromIdentifier(docHandle,ID)
80
82 docHandle,ID=self.obj.getIdentifierFromNVDAObject(obj) 83 return self._getOffsetsFromFieldIdentifier(docHandle,ID)
84
85 - def _getOffsetsFromNVDAObject(self, obj):
86 ancestorCount = 0 87 while True: 88 try: 89 return self._getOffsetsFromNVDAObjectInBuffer(obj) 90 except LookupError: 91 pass 92 # Interactive list/combo box descendants aren't rendered into the buffer, even though they are still considered part of it. 93 # Use the list/combo box in this case. 94 if ancestorCount == 2: 95 # This is not a list/combo box descendant. 96 break 97 obj = obj.parent 98 ancestorCount += 1 99 if not obj or obj.role not in (controlTypes.ROLE_LIST, controlTypes.ROLE_COMBOBOX): 100 break
101
102 - def __init__(self,obj,position):
103 self.obj=obj 104 super(VirtualBufferTextInfo,self).__init__(obj,position)
105
106 - def _getSelectionOffsets(self):
107 start=ctypes.c_int() 108 end=ctypes.c_int() 109 NVDAHelper.localLib.VBuf_getSelectionOffsets(self.obj.VBufHandle,ctypes.byref(start),ctypes.byref(end)) 110 return start.value,end.value
111
112 - def _setSelectionOffsets(self,start,end):
113 NVDAHelper.localLib.VBuf_setSelectionOffsets(self.obj.VBufHandle,start,end)
114
115 - def _getCaretOffset(self):
116 return self._getSelectionOffsets()[0]
117
118 - def _setCaretOffset(self,offset):
119 return self._setSelectionOffsets(offset,offset)
120
121 - def _getStoryLength(self):
122 return NVDAHelper.localLib.VBuf_getTextLength(self.obj.VBufHandle)
123
124 - def _getTextRange(self,start,end):
125 if start==end: 126 return u"" 127 return NVDAHelper.VBuf_getTextInRange(self.obj.VBufHandle,start,end,False) or u""
128
129 - def getTextWithFields(self,formatConfig=None):
130 start=self._startOffset 131 end=self._endOffset 132 if start==end: 133 return "" 134 text=NVDAHelper.VBuf_getTextInRange(self.obj.VBufHandle,start,end,True) 135 if not text: 136 return "" 137 commandList=XMLFormatting.XMLTextParser().parse(text) 138 for index in xrange(len(commandList)): 139 if isinstance(commandList[index],textInfos.FieldCommand): 140 field=commandList[index].field 141 if isinstance(field,textInfos.ControlField): 142 commandList[index].field=self._normalizeControlField(field) 143 elif isinstance(field,textInfos.FormatField): 144 commandList[index].field=self._normalizeFormatField(field) 145 return commandList
146
147 - def _getWordOffsets(self,offset):
148 #Use VBuf_getBufferLineOffsets with out screen layout to find out the range of the current field 149 lineStart=ctypes.c_int() 150 lineEnd=ctypes.c_int() 151 NVDAHelper.localLib.VBuf_getLineOffsets(self.obj.VBufHandle,offset,0,False,ctypes.byref(lineStart),ctypes.byref(lineEnd)) 152 word_startOffset,word_endOffset=super(VirtualBufferTextInfo,self)._getWordOffsets(offset) 153 return (max(lineStart.value,word_startOffset),min(lineEnd.value,word_endOffset))
154
155 - def _getLineOffsets(self,offset):
156 lineStart=ctypes.c_int() 157 lineEnd=ctypes.c_int() 158 NVDAHelper.localLib.VBuf_getLineOffsets(self.obj.VBufHandle,offset,config.conf["virtualBuffers"]["maxLineLength"],config.conf["virtualBuffers"]["useScreenLayout"],ctypes.byref(lineStart),ctypes.byref(lineEnd)) 159 return lineStart.value,lineEnd.value
160
161 - def _getParagraphOffsets(self,offset):
162 lineStart=ctypes.c_int() 163 lineEnd=ctypes.c_int() 164 NVDAHelper.localLib.VBuf_getLineOffsets(self.obj.VBufHandle,offset,0,True,ctypes.byref(lineStart),ctypes.byref(lineEnd)) 165 return lineStart.value,lineEnd.value
166
167 - def _normalizeControlField(self,attrs):
168 tableLayout=attrs.get('table-layout') 169 if tableLayout: 170 attrs['table-layout']=tableLayout=="1" 171 172 # Handle table row and column headers. 173 for axis in "row", "column": 174 attr = attrs.pop("table-%sheadercells" % axis, None) 175 if not attr: 176 continue 177 cellIdentifiers = [identifier.split(",") for identifier in attr.split(";") if identifier] 178 # Get the text for the header cells. 179 textList = [] 180 for docHandle, ID in cellIdentifiers: 181 try: 182 start, end = self._getOffsetsFromFieldIdentifier(int(docHandle), int(ID)) 183 except (LookupError, ValueError): 184 continue 185 textList.append(self.obj.makeTextInfo(textInfos.offsets.Offsets(start, end)).text) 186 attrs["table-%sheadertext" % axis] = "\n".join(textList) 187 188 return attrs
189
190 - def _normalizeFormatField(self, attrs):
191 return attrs
192
193 - def _getLineNumFromOffset(self, offset):
194 return None
195
197 return self._getFieldIdentifierFromOffset( self._startOffset)
198
199 - def _getUnitOffsets(self, unit, offset):
200 if unit == self.UNIT_CONTROLFIELD: 201 startOffset=ctypes.c_int() 202 endOffset=ctypes.c_int() 203 docHandle=ctypes.c_int() 204 ID=ctypes.c_int() 205 NVDAHelper.localLib.VBuf_locateControlFieldNodeAtOffset(self.obj.VBufHandle,offset,ctypes.byref(startOffset),ctypes.byref(endOffset),ctypes.byref(docHandle),ctypes.byref(ID)) 206 return startOffset.value,endOffset.value 207 return super(VirtualBufferTextInfo, self)._getUnitOffsets(unit, offset)
208
209 - def _get_clipboardText(self):
210 # Blocks should start on a new line, but they don't necessarily have an end of line indicator. 211 # Therefore, get the text in block (paragraph) chunks and join the chunks with \r\n. 212 blocks = (block.strip("\r\n") for block in self.getTextInChunks(textInfos.UNIT_PARAGRAPH)) 213 return "\r\n".join(blocks)
214
215 - def getControlFieldSpeech(self, attrs, ancestorAttrs, fieldType, formatConfig=None, extraDetail=False, reason=None):
216 textList = [] 217 landmark = attrs.get("landmark") 218 if formatConfig["reportLandmarks"] and fieldType == "start_addedToControlFieldStack" and landmark: 219 textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) 220 textList.append(super(VirtualBufferTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason)) 221 return " ".join(textList)
222
223 - def getControlFieldBraille(self, field, ancestors, reportStart, formatConfig):
224 textList = [] 225 landmark = field.get("landmark") 226 if formatConfig["reportLandmarks"] and reportStart and landmark and field.get("_startOfNode"): 227 textList.append(_("%s landmark") % aria.landmarkRoles[landmark]) 228 text = super(VirtualBufferTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig) 229 if text: 230 textList.append(text) 231 return " ".join(textList)
232
234 try: 235 newNode, newStart, newEnd = next(self.obj._iterNodesByType("focusable", "up", self._startOffset)) 236 except StopIteration: 237 return self.obj.rootNVDAObject 238 if not newNode: 239 return self.obj.rootNVDAObject 240 docHandle=ctypes.c_int() 241 ID=ctypes.c_int() 242 NVDAHelper.localLib.VBuf_getIdentifierFromControlFieldNode(self.obj.VBufHandle, newNode, ctypes.byref(docHandle), ctypes.byref(ID)) 243 return self.obj.getNVDAObjectFromIdentifier(docHandle.value,ID.value)
244
245 -class ElementsListDialog(wx.Dialog):
246 ELEMENT_TYPES = ( 247 ("link", _("Lin&ks")), 248 ("heading", _("&Headings")), 249 ("landmark", _("Lan&dmarks")), 250 ) 251 Element = collections.namedtuple("Element", ("textInfo", "text", "parent")) 252
253 - def __init__(self, vbuf):
254 self.vbuf = vbuf 255 super(ElementsListDialog, self).__init__(gui.mainFrame, wx.ID_ANY, _("Elements List")) 256 mainSizer = wx.BoxSizer(wx.VERTICAL) 257 258 child = wx.RadioBox(self, wx.ID_ANY, label=_("Type:"), choices=tuple(et[1] for et in self.ELEMENT_TYPES)) 259 child.Bind(wx.EVT_RADIOBOX, self.onElementTypeChange) 260 mainSizer.Add(child,proportion=1) 261 262 self.tree = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_SINGLE) 263 self.tree.Bind(wx.EVT_SET_FOCUS, self.onTreeSetFocus) 264 self.tree.Bind(wx.EVT_CHAR, self.onTreeChar) 265 self.treeRoot = self.tree.AddRoot("root") 266 mainSizer.Add(self.tree,proportion=7) 267 268 sizer = wx.BoxSizer(wx.HORIZONTAL) 269 label = wx.StaticText(self, wx.ID_ANY, _("&Filter by:")) 270 sizer.Add(label) 271 self.filterEdit = wx.TextCtrl(self, wx.ID_ANY) 272 self.filterEdit.Bind(wx.EVT_TEXT, self.onFilterEditTextChange) 273 sizer.Add(self.filterEdit) 274 mainSizer.Add(sizer,proportion=1) 275 276 sizer = wx.BoxSizer(wx.HORIZONTAL) 277 self.activateButton = wx.Button(self, wx.ID_ANY, _("&Activate")) 278 self.activateButton.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(True)) 279 sizer.Add(self.activateButton) 280 self.moveButton = wx.Button(self, wx.ID_ANY, _("&Move to")) 281 self.moveButton.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(False)) 282 sizer.Add(self.moveButton) 283 sizer.Add(wx.Button(self, wx.ID_CANCEL)) 284 mainSizer.Add(sizer,proportion=1) 285 286 mainSizer.Fit(self) 287 self.SetSizer(mainSizer) 288 289 self.tree.SetFocus() 290 self.initElementType(self.ELEMENT_TYPES[0][0])
291
292 - def onElementTypeChange(self, evt):
293 # We need to make sure this gets executed after the focus event. 294 # Otherwise, NVDA doesn't seem to get the event. 295 queueHandler.queueFunction(queueHandler.eventQueue, self.initElementType, self.ELEMENT_TYPES[evt.GetInt()][0])
296
297 - def initElementType(self, elType):
298 if elType == "link": 299 # Links can be activated. 300 self.activateButton.Enable() 301 self.SetAffirmativeId(self.activateButton.GetId()) 302 else: 303 # No other element type can be activated. 304 self.activateButton.Disable() 305 self.SetAffirmativeId(self.moveButton.GetId()) 306 307 # Gather the elements of this type. 308 self._elements = [] 309 self._initialElement = None 310 311 caret = self.vbuf.selection 312 caret.expand("character") 313 314 parentElements = [] 315 for node, start, end in self.vbuf._iterNodesByType(elType): 316 elInfo = self.vbuf.makeTextInfo(textInfos.offsets.Offsets(start, end)) 317 318 # Find the parent element, if any. 319 for parent in reversed(parentElements): 320 if self.isChildElement(elType, parent.textInfo, elInfo): 321 break 322 else: 323 # We're not a child of this parent, so this parent has no more children and can be removed from the stack. 324 parentElements.pop() 325 else: 326 # No parent found, so we're at the root. 327 # Note that parentElements will be empty at this point, as all parents are no longer relevant and have thus been removed from the stack. 328 parent = None 329 330 element = self.Element(elInfo, self.getElementText(elInfo, elType), parent) 331 self._elements.append(element) 332 333 if not self._initialElement and elInfo.compareEndPoints(caret, "startToStart") > 0: 334 # The element immediately preceding or overlapping the caret should be the initially selected element. 335 # This element immediately follows the caret, so we want the previous element. 336 try: 337 self._initialElement = self._elements[-2] 338 except IndexError: 339 # No previous element. 340 pass 341 342 # This could be the parent of a subsequent element, so add it to the parents stack. 343 parentElements.append(element) 344 345 # Start with no filtering. 346 self.filter("", newElementType=True)
347
348 - def filter(self, filterText, newElementType=False):
349 # If this is a new element type, use the element nearest the cursor. 350 # Otherwise, use the currently selected element. 351 defaultElement = self._initialElement if newElementType else self.tree.GetItemPyData(self.tree.GetSelection()) 352 # Clear the tree. 353 self.tree.DeleteChildren(self.treeRoot) 354 355 # Populate the tree with elements matching the filter text. 356 elementsToTreeItems = {} 357 item = None 358 defaultItem = None 359 matched = False 360 for element in self._elements: 361 if filterText not in element.text.lower(): 362 item = None 363 continue 364 matched = True 365 parent = element.parent 366 if parent: 367 parent = elementsToTreeItems.get(parent) 368 item = self.tree.AppendItem(parent or self.treeRoot, element.text) 369 self.tree.SetItemPyData(item, element) 370 elementsToTreeItems[element] = item 371 if element == defaultElement: 372 defaultItem = item 373 374 self.tree.ExpandAll() 375 376 if not matched: 377 # No items, so disable the buttons. 378 self.activateButton.Disable() 379 self.moveButton.Disable() 380 return 381 382 # If there's no default item, use the first item in the tree. 383 self.tree.SelectItem(defaultItem or self.tree.GetFirstChild(self.treeRoot)[0]) 384 # Enable the button(s). 385 # If the activate button isn't the default button, it is disabled for this element type and shouldn't be enabled here. 386 if self.AffirmativeId == self.activateButton.Id: 387 self.activateButton.Enable() 388 self.moveButton.Enable()
389
390 - def _getControlFieldAttrib(self, info, attrib):
391 info = info.copy() 392 info.expand(textInfos.UNIT_CHARACTER) 393 for field in reversed(info.getTextWithFields()): 394 if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"): 395 # Not a control field. 396 continue 397 val = field.field.get(attrib) 398 if val: 399 return val 400 return None
401
402 - def getElementText(self, elInfo, elType):
403 if elType == "landmark": 404 landmark = self._getControlFieldAttrib(elInfo, "landmark") 405 if landmark: 406 return aria.landmarkRoles[landmark] 407 408 else: 409 return elInfo.text.strip()
410
411 - def isChildElement(self, elType, parent, child):
412 if parent.isOverlapping(child): 413 return True 414 415 elif elType == "heading": 416 try: 417 if int(self._getControlFieldAttrib(child, "level")) > int(self._getControlFieldAttrib(parent, "level")): 418 return True 419 except (ValueError, TypeError): 420 return False 421 422 return False
423
424 - def onTreeSetFocus(self, evt):
425 # Start with no search. 426 self._searchText = "" 427 self._searchCallLater = None 428 evt.Skip()
429
430 - def onTreeChar(self, evt):
431 key = evt.KeyCode 432 433 if key == wx.WXK_RETURN: 434 # The enter key should be propagated to the dialog and thus activate the default button, 435 # but this is broken (wx ticket #3725). 436 # Therefore, we must catch the enter key here. 437 # Activate the current default button. 438 evt = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, wx.ID_ANY) 439 button = self.FindWindowById(self.AffirmativeId) 440 if button.Enabled: 441 button.ProcessEvent(evt) 442 else: 443 wx.Bell() 444 445 elif key >= wx.WXK_START or key == wx.WXK_BACK: 446 # Non-printable character. 447 self._searchText = "" 448 evt.Skip() 449 450 else: 451 # Search the list. 452 # We have to implement this ourselves, as tree views don't accept space as a search character. 453 char = unichr(evt.UnicodeKey).lower() 454 # IF the same character is typed twice, do the same search. 455 if self._searchText != char: 456 self._searchText += char 457 if self._searchCallLater: 458 self._searchCallLater.Restart() 459 else: 460 self._searchCallLater = wx.CallLater(1000, self._clearSearchText) 461 self.search(self._searchText)
462
463 - def _clearSearchText(self):
464 self._searchText = ""
465
466 - def search(self, searchText):
467 item = self.tree.GetSelection() 468 if not item: 469 # No items. 470 return 471 472 # First try searching from the current item. 473 # Failing that, search from the first item. 474 items = itertools.chain(self._iterReachableTreeItemsFromItem(item), self._iterReachableTreeItemsFromItem(self.tree.GetFirstChild(self.treeRoot)[0])) 475 if len(searchText) == 1: 476 # If only a single character has been entered, skip (search after) the current item. 477 next(items) 478 479 for item in items: 480 if self.tree.GetItemText(item).lower().startswith(searchText): 481 self.tree.SelectItem(item) 482 return 483 484 # Not found. 485 wx.Bell()
486
487 - def _iterReachableTreeItemsFromItem(self, item):
488 while item: 489 yield item 490 491 childItem = self.tree.GetFirstChild(item)[0] 492 if childItem and self.tree.IsExpanded(item): 493 # Has children and is reachable, so recurse. 494 for childItem in self._iterReachableTreeItemsFromItem(childItem): 495 yield childItem 496 497 item = self.tree.GetNextSibling(item)
498
499 - def onFilterEditTextChange(self, evt):
500 self.filter(self.filterEdit.GetValue()) 501 evt.Skip()
502
503 - def onAction(self, activate):
504 self.Close() 505 506 item = self.tree.GetSelection() 507 element = self.tree.GetItemPyData(item).textInfo 508 newCaret = element.copy() 509 newCaret.collapse() 510 self.vbuf.selection = newCaret 511 512 if activate: 513 self.vbuf._activatePosition(element) 514 else: 515 wx.CallLater(100, self._reportElement, element)
516
517 - def _reportElement(self, element):
520
521 -class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInterceptor):
522 523 REASON_QUICKNAV = "quickNav" 524 525 TextInfo=VirtualBufferTextInfo 526 programmaticScrollMayFireEvent = False 527
528 - def __init__(self,rootNVDAObject,backendName=None):
529 super(VirtualBuffer,self).__init__(rootNVDAObject) 530 self.backendName=backendName 531 self.VBufHandle=None 532 self.isLoading=False 533 self.disableAutoPassThrough = False 534 self.rootDocHandle,self.rootID=self.getIdentifierFromNVDAObject(self.rootNVDAObject) 535 self._lastFocusObj = None 536 self._hadFirstGainFocus = False 537 self._lastProgrammaticScrollTime = None 538 # We need to cache this because it will be unavailable once the document dies. 539 self.documentConstantIdentifier = self.documentConstantIdentifier 540 if not hasattr(self.rootNVDAObject.appModule, "_vbufRememberedCaretPositions"): 541 self.rootNVDAObject.appModule._vbufRememberedCaretPositions = {} 542 self._lastCaretPosition = None
543
544 - def prepare(self):
545 self.shouldPrepare=False 546 self.loadBuffer()
547
548 - def _get_shouldPrepare(self):
549 return not self.isLoading and not self.VBufHandle
550
551 - def terminate(self):
552 if not self.VBufHandle: 553 return 554 555 if self.shouldRememberCaretPositionAcrossLoads and self._lastCaretPosition: 556 try: 557 self.rootNVDAObject.appModule._vbufRememberedCaretPositions[self.documentConstantIdentifier] = self._lastCaretPosition 558 except AttributeError: 559 # The app module died. 560 pass 561 562 self.unloadBuffer()
563
564 - def _get_isReady(self):
565 return bool(self.VBufHandle and not self.isLoading)
566
567 - def loadBuffer(self):
568 self.isLoading = True 569 self._loadProgressCallLater = wx.CallLater(1000, self._loadProgress) 570 threading.Thread(target=self._loadBuffer).start()
571
572 - def _loadBuffer(self):
573 try: 574 self.VBufHandle=NVDAHelper.localLib.VBuf_createBuffer(self.rootNVDAObject.appModule.helperLocalBindingHandle,self.rootDocHandle,self.rootID,unicode(self.backendName)) 575 if not self.VBufHandle: 576 raise RuntimeError("Could not remotely create virtualBuffer") 577 except: 578 log.error("", exc_info=True) 579 queueHandler.queueFunction(queueHandler.eventQueue, self._loadBufferDone, success=False) 580 return 581 queueHandler.queueFunction(queueHandler.eventQueue, self._loadBufferDone)
582
583 - def _loadBufferDone(self, success=True):
584 self._loadProgressCallLater.Stop() 585 del self._loadProgressCallLater 586 self.isLoading = False 587 if not success: 588 self.passThrough=True 589 return 590 if self._hadFirstGainFocus: 591 # If this buffer has already had focus once while loaded, this is a refresh. 592 speech.speakMessage(_("Refreshed")) 593 if api.getFocusObject().treeInterceptor == self: 594 self.event_treeInterceptor_gainFocus()
595
596 - def _loadProgress(self):
597 ui.message(_("Loading document..."))
598
599 - def unloadBuffer(self):
600 if self.VBufHandle is not None: 601 try: 602 watchdog.cancellableExecute(NVDAHelper.localLib.VBuf_destroyBuffer, ctypes.byref(ctypes.c_int(self.VBufHandle))) 603 except WindowsError: 604 pass 605 self.VBufHandle=None
606
607 - def makeTextInfo(self,position):
608 return self.TextInfo(self,position)
609
610 - def isNVDAObjectPartOfLayoutTable(self,obj):
611 docHandle,ID=self.getIdentifierFromNVDAObject(obj) 612 ID=unicode(ID) 613 info=self.makeTextInfo(obj) 614 info.collapse() 615 info.expand(textInfos.UNIT_CHARACTER) 616 fieldCommands=[x for x in info.getTextWithFields() if isinstance(x,textInfos.FieldCommand)] 617 tableLayout=None 618 tableID=None 619 for fieldCommand in fieldCommands: 620 fieldID=fieldCommand.field.get("controlIdentifier_ID") if fieldCommand.field else None 621 if fieldID==ID: 622 tableLayout=fieldCommand.field.get('table-layout') 623 if tableLayout is not None: 624 return tableLayout 625 tableID=fieldCommand.field.get('table-id') 626 break 627 if tableID is None: 628 return False 629 for fieldCommand in fieldCommands: 630 fieldID=fieldCommand.field.get("controlIdentifier_ID") if fieldCommand.field else None 631 if fieldID==tableID: 632 tableLayout=fieldCommand.field.get('table-layout',False) 633 break 634 return tableLayout
635 636 637 638
639 - def getNVDAObjectFromIdentifier(self, docHandle, ID):
640 """Retrieve an NVDAObject for a given node identifier. 641 Subclasses must override this method. 642 @param docHandle: The document handle. 643 @type docHandle: int 644 @param ID: The ID of the node. 645 @type ID: int 646 @return: The NVDAObject. 647 @rtype: L{NVDAObjects.NVDAObject} 648 """ 649 raise NotImplementedError
650
651 - def getIdentifierFromNVDAObject(self,obj):
652 """Retreaves the virtualBuffer field identifier from an NVDAObject. 653 @param obj: the NVDAObject to retreave the field identifier from. 654 @type obj: L{NVDAObject} 655 @returns: a the field identifier as a doc handle and ID paire. 656 @rtype: 2-tuple. 657 """ 658 raise NotImplementedError
659
661 """Triggered when this virtual buffer gains focus. 662 This event is only fired upon entering this buffer when it was not the current buffer before. 663 This is different to L{event_gainFocus}, which is fired when an object inside this buffer gains focus, even if that object is in the same buffer. 664 """ 665 doSayAll=False 666 if not self._hadFirstGainFocus: 667 # This buffer is gaining focus for the first time. 668 # Fake a focus event on the focus object, as the buffer may have missed the actual focus event. 669 focus = api.getFocusObject() 670 self.event_gainFocus(focus, lambda: focus.event_gainFocus()) 671 if not self.passThrough: 672 # We only set the caret position if in browse mode. 673 # If in focus mode, the document must have forced the focus somewhere, 674 # so we don't want to override it. 675 initialPos = self._getInitialCaretPos() 676 if initialPos: 677 self.selection = self.makeTextInfo(initialPos) 678 reportPassThrough(self) 679 doSayAll=config.conf['virtualBuffers']['autoSayAllOnPageLoad'] 680 self._hadFirstGainFocus = True 681 682 if not self.passThrough: 683 if doSayAll: 684 speech.speakObjectProperties(self.rootNVDAObject,name=True,states=True,reason=speech.REASON_FOCUS) 685 info=self.makeTextInfo(textInfos.POSITION_CARET) 686 sayAllHandler.readText(info,sayAllHandler.CURSOR_CARET) 687 else: 688 # Speak it like we would speak focus on any other document object. 689 speech.speakObject(self.rootNVDAObject, reason=speech.REASON_FOCUS) 690 info = self.selection 691 if not info.isCollapsed: 692 speech.speakSelectionMessage(_("selected %s"), info.text) 693 else: 694 info.expand(textInfos.UNIT_LINE) 695 speech.speakTextInfo(info, reason=speech.REASON_CARET, unit=textInfos.UNIT_LINE) 696 697 reportPassThrough(self) 698 braille.handler.handleGainFocus(self)
699
701 """Triggered when this virtual buffer loses focus. 702 This event is only fired when the focus moves to a new object which is not within this virtual buffer; i.e. upon leaving this virtual buffer. 703 """
704
705 - def event_caret(self, obj, nextHandler):
706 if self.passThrough: 707 nextHandler()
708
709 - def _activateNVDAObject(self, obj):
710 """Activate an object in response to a user request. 711 This should generally perform the default action or click on the object. 712 @param obj: The object to activate. 713 @type obj: L{NVDAObjects.NVDAObject} 714 """ 715 obj.doAction()
716
717 - def _activatePosition(self, info):
718 obj = info.NVDAObjectAtStart 719 if self.shouldPassThrough(obj): 720 obj.setFocus() 721 self.passThrough = True 722 reportPassThrough(self) 723 elif obj.role == controlTypes.ROLE_EMBEDDEDOBJECT: 724 obj.setFocus() 725 speech.speakObject(obj, reason=speech.REASON_FOCUS) 726 else: 727 self._activateNVDAObject(obj)
728
729 - def _set_selection(self, info, reason=speech.REASON_CARET):
730 super(VirtualBuffer, self)._set_selection(info) 731 if isScriptWaiting() or not info.isCollapsed: 732 return 733 # Save the last caret position for use in terminate(). 734 # This must be done here because the buffer might be cleared just before terminate() is called, 735 # causing the last caret position to be lost. 736 caret = info.copy() 737 caret.collapse() 738 self._lastCaretPosition = caret.bookmark 739 if config.conf['reviewCursor']['followCaret'] and api.getNavigatorObject() is self.rootNVDAObject: 740 api.setReviewPosition(info) 741 if reason == speech.REASON_FOCUS: 742 focusObj = api.getFocusObject() 743 if focusObj==self.rootNVDAObject: 744 return 745 else: 746 focusObj=info.focusableNVDAObjectAtStart 747 obj=info.NVDAObjectAtStart 748 if not obj: 749 log.debugWarning("Invalid NVDAObjectAtStart") 750 return 751 if obj==self.rootNVDAObject: 752 return 753 if focusObj and not eventHandler.isPendingEvents("gainFocus") and focusObj!=self.rootNVDAObject and focusObj != api.getFocusObject() and self._shouldSetFocusToObj(focusObj): 754 focusObj.setFocus() 755 obj.scrollIntoView() 756 if self.programmaticScrollMayFireEvent: 757 self._lastProgrammaticScrollTime = time.time() 758 self.passThrough=self.shouldPassThrough(focusObj,reason=reason) 759 # Queue the reporting of pass through mode so that it will be spoken after the actual content. 760 queueHandler.queueFunction(queueHandler.eventQueue, reportPassThrough, self)
761
762 - def _shouldSetFocusToObj(self, obj):
763 """Determine whether an object should receive focus. 764 Subclasses should override this method. 765 @param obj: The object in question. 766 @type obj: L{NVDAObjects.NVDAObject} 767 """ 768 return controlTypes.STATE_FOCUSABLE in obj.states
769
770 - def script_activatePosition(self,gesture):
773 script_activatePosition.__doc__ = _("activates the current object in the document") 774
775 - def script_refreshBuffer(self,gesture):
776 if scriptHandler.isScriptWaiting(): 777 # This script may cause subsequently queued scripts to fail, so don't execute. 778 return 779 self.unloadBuffer() 780 self.loadBuffer()
781 script_refreshBuffer.__doc__ = _("Refreshes the document content") 782
783 - def script_toggleScreenLayout(self,gesture):
784 config.conf["virtualBuffers"]["useScreenLayout"]=not config.conf["virtualBuffers"]["useScreenLayout"] 785 onOff=_("on") if config.conf["virtualBuffers"]["useScreenLayout"] else _("off") 786 speech.speakMessage(_("use screen layout %s")%onOff)
787 script_toggleScreenLayout.__doc__ = _("Toggles on and off if the screen layout is preserved while rendering the document content") 788
789 - def _searchableAttributesForNodeType(self,nodeType):
790 pass
791
792 - def _iterNodesByType(self,nodeType,direction="next",offset=-1):
793 if nodeType == "notLinkBlock": 794 return self._iterNotLinkBlock(direction=direction, offset=offset) 795 attribs=self._searchableAttribsForNodeType(nodeType) 796 if not attribs: 797 return iter(()) 798 return self._iterNodesByAttribs(attribs, direction, offset)
799
800 - def _iterNodesByAttribs(self, attribs, direction="next", offset=-1):
801 attribs=dictToMultiValueAttribsString(attribs) 802 startOffset=ctypes.c_int() 803 endOffset=ctypes.c_int() 804 if direction=="next": 805 direction=VBufStorage_findDirection_forward 806 elif direction=="previous": 807 direction=VBufStorage_findDirection_back 808 elif direction=="up": 809 direction=VBufStorage_findDirection_up 810 else: 811 raise ValueError("unknown direction: %s"%direction) 812 while True: 813 try: 814 node=NVDAHelper.localLib.VBuf_findNodeByAttributes(self.VBufHandle,offset,direction,attribs,ctypes.byref(startOffset),ctypes.byref(endOffset)) 815 except: 816 return 817 if not node: 818 return 819 yield node, startOffset.value, endOffset.value 820 offset=startOffset
821
822 - def _quickNavScript(self,gesture, nodeType, direction, errorMessage, readUnit):
823 info=self.makeTextInfo(textInfos.POSITION_CARET) 824 startOffset=info._startOffset 825 endOffset=info._endOffset 826 try: 827 node, startOffset, endOffset = next(self._iterNodesByType(nodeType, direction, startOffset)) 828 except StopIteration: 829 speech.speakMessage(errorMessage) 830 return 831 info = self.makeTextInfo(textInfos.offsets.Offsets(startOffset, endOffset)) 832 if readUnit: 833 fieldInfo = info.copy() 834 info.collapse() 835 info.move(readUnit, 1, endPoint="end") 836 if info.compareEndPoints(fieldInfo, "endToEnd") > 0: 837 # We've expanded past the end of the field, so limit to the end of the field. 838 info.setEndPoint(fieldInfo, "endToEnd") 839 speech.speakTextInfo(info, reason=speech.REASON_FOCUS) 840 info.collapse() 841 self._set_selection(info, reason=self.REASON_QUICKNAV)
842 843 @classmethod
844 - def addQuickNav(cls, nodeType, key, nextDoc, nextError, prevDoc, prevError, readUnit=None):
845 scriptSuffix = nodeType[0].upper() + nodeType[1:] 846 scriptName = "next%s" % scriptSuffix 847 funcName = "script_%s" % scriptName 848 script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "next", nextError, readUnit) 849 script.__doc__ = nextDoc 850 script.__name__ = funcName 851 setattr(cls, funcName, script) 852 cls.__gestures["kb:%s" % key] = scriptName 853 scriptName = "previous%s" % scriptSuffix 854 funcName = "script_%s" % scriptName 855 script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "previous", prevError, readUnit) 856 script.__doc__ = prevDoc 857 script.__name__ = funcName 858 setattr(cls, funcName, script) 859 cls.__gestures["kb:shift+%s" % key] = scriptName
860
861 - def script_elementsList(self,gesture):
862 # We need this to be a modal dialog, but it mustn't block this script. 863 def run(): 864 gui.mainFrame.prePopup() 865 d = ElementsListDialog(self) 866 d.ShowModal() 867 d.Destroy() 868 gui.mainFrame.postPopup()
869 wx.CallAfter(run)
870 script_elementsList.__doc__ = _("Presents a list of links, headings or landmarks") 871
872 - def shouldPassThrough(self, obj, reason=None):
873 """Determine whether pass through mode should be enabled or disabled for a given object. 874 @param obj: The object in question. 875 @type obj: L{NVDAObjects.NVDAObject} 876 @param reason: The reason for this query; one of the speech reasons, L{REASON_QUICKNAV}, or C{None} for manual pass through mode activation by the user. 877 @return: C{True} if pass through mode should be enabled, C{False} if it should be disabled. 878 """ 879 if reason and ( 880 self.disableAutoPassThrough 881 or (reason == speech.REASON_FOCUS and not config.conf["virtualBuffers"]["autoPassThroughOnFocusChange"]) 882 or (reason == speech.REASON_CARET and not config.conf["virtualBuffers"]["autoPassThroughOnCaretMove"]) 883 ): 884 # This check relates to auto pass through and auto pass through is disabled, so don't change the pass through state. 885 return self.passThrough 886 if reason == self.REASON_QUICKNAV: 887 return False 888 states = obj.states 889 if controlTypes.STATE_FOCUSABLE not in states and controlTypes.STATE_FOCUSED not in states: 890 return False 891 role = obj.role 892 if controlTypes.STATE_READONLY in states and role != controlTypes.ROLE_EDITABLETEXT: 893 return False 894 if reason == speech.REASON_CARET: 895 return role == controlTypes.ROLE_EDITABLETEXT or (role == controlTypes.ROLE_DOCUMENT and controlTypes.STATE_EDITABLE in states) 896 if reason == speech.REASON_FOCUS and role in (controlTypes.ROLE_LISTITEM, controlTypes.ROLE_RADIOBUTTON, controlTypes.ROLE_TAB): 897 return True 898 if role in (controlTypes.ROLE_COMBOBOX, controlTypes.ROLE_EDITABLETEXT, controlTypes.ROLE_LIST, controlTypes.ROLE_SLIDER, controlTypes.ROLE_TABCONTROL, controlTypes.ROLE_MENUBAR, controlTypes.ROLE_POPUPMENU, controlTypes.ROLE_MENUITEM, controlTypes.ROLE_TREEVIEW, controlTypes.ROLE_TREEVIEWITEM, controlTypes.ROLE_SPINBUTTON) or controlTypes.STATE_EDITABLE in states: 899 return True 900 return False
901
902 - def event_caretMovementFailed(self, obj, nextHandler, gesture=None):
903 if not self.passThrough or not gesture or not config.conf["virtualBuffers"]["autoPassThroughOnCaretMove"]: 904 return nextHandler() 905 if gesture.mainKeyName in ("home", "end"): 906 # Home, end, control+home and control+end should not disable pass through. 907 return nextHandler() 908 script = self.getScript(gesture) 909 if not script: 910 return nextHandler() 911 912 # We've hit the edge of the focused control. 913 # Therefore, move the virtual caret to the same edge of the field. 914 info = self.makeTextInfo(textInfos.POSITION_CARET) 915 info.expand(info.UNIT_CONTROLFIELD) 916 if gesture.mainKeyName in ("leftArrow", "upArrow", "pageUp"): 917 info.collapse() 918 else: 919 info.collapse(end=True) 920 info.move(textInfos.UNIT_CHARACTER, -1) 921 info.updateCaret() 922 923 scriptHandler.queueScript(script, gesture)
924
925 - def script_disablePassThrough(self, gesture):
926 if not self.passThrough or self.disableAutoPassThrough: 927 return gesture.send() 928 self.passThrough = False 929 self.disableAutoPassThrough = False 930 reportPassThrough(self)
931 script_disablePassThrough.ignoreTreeInterceptorPassThrough = True 932
933 - def script_collapseOrExpandControl(self, gesture):
934 if self.passThrough: 935 gesture.send() 936 if not self.disableAutoPassThrough: 937 self.passThrough = False 938 reportPassThrough(self) 939 else: 940 oldFocus = api.getFocusObject() 941 oldFocusStates = oldFocus.states 942 gesture.send() 943 if oldFocus.role == controlTypes.ROLE_COMBOBOX and controlTypes.STATE_COLLAPSED in oldFocusStates: 944 self.passThrough = True 945 reportPassThrough(self)
946 script_collapseOrExpandControl.ignoreTreeInterceptorPassThrough = True 947
948 - def _tabOverride(self, direction):
949 """Override the tab order if the virtual buffer caret is not within the currently focused node. 950 This is done because many nodes are not focusable and it is thus possible for the virtual buffer caret to be unsynchronised with the focus. 951 In this case, we want tab/shift+tab to move to the next/previous focusable node relative to the virtual buffer caret. 952 If the virtual buffer caret is within the focused node, the tab/shift+tab key should be passed through to allow normal tab order navigation. 953 Note that this method does not pass the key through itself if it is not overridden. This should be done by the calling script if C{False} is returned. 954 @param direction: The direction in which to move. 955 @type direction: str 956 @return: C{True} if the tab order was overridden, C{False} if not. 957 @rtype: bool 958 """ 959 focus = api.getFocusObject() 960 try: 961 focusInfo = self.makeTextInfo(focus) 962 except: 963 return False 964 # We only want to override the tab order if the caret is not within the focused node. 965 caretInfo=self.makeTextInfo(textInfos.POSITION_CARET) 966 #Only check that the caret is within the focus for things that ar not documents 967 #As for documents we should always override 968 if focus.role!=controlTypes.ROLE_DOCUMENT or controlTypes.STATE_EDITABLE in focus.states: 969 # Expand to one character, as isOverlapping() doesn't yield the desired results with collapsed ranges. 970 caretInfo.expand(textInfos.UNIT_CHARACTER) 971 if focusInfo.isOverlapping(caretInfo): 972 return False 973 # If we reach here, we do want to override tab/shift+tab if possible. 974 # Find the next/previous focusable node. 975 try: 976 newNode, newStart, newEnd = next(self._iterNodesByType("focusable", direction, caretInfo._startOffset)) 977 except StopIteration: 978 return False 979 docHandle=ctypes.c_int() 980 ID=ctypes.c_int() 981 NVDAHelper.localLib.VBuf_getIdentifierFromControlFieldNode(self.VBufHandle, newNode, ctypes.byref(docHandle), ctypes.byref(ID)) 982 obj=self.getNVDAObjectFromIdentifier(docHandle.value,ID.value) 983 newInfo=self.makeTextInfo(textInfos.offsets.Offsets(newStart,newEnd)) 984 if obj == api.getFocusObject(): 985 # This node is already focused, so we need to move to and speak this node here. 986 newCaret = newInfo.copy() 987 newCaret.collapse() 988 self._set_selection(newCaret,reason=speech.REASON_FOCUS) 989 if self.passThrough: 990 obj.event_gainFocus() 991 else: 992 speech.speakTextInfo(newInfo,reason=speech.REASON_FOCUS) 993 else: 994 # This node doesn't have the focus, so just set focus to it. The gainFocus event will handle the rest. 995 obj.setFocus() 996 return True
997
998 - def script_tab(self, gesture):
999 if not self._tabOverride("next"): 1000 gesture.send()
1001
1002 - def script_shiftTab(self, gesture):
1003 if not self._tabOverride("previous"): 1004 gesture.send()
1005
1006 - def event_focusEntered(self,obj,nextHandler):
1007 if self.passThrough: 1008 nextHandler()
1009
1010 - def _shouldIgnoreFocus(self, obj):
1011 """Determines whether focus on a given object should be ignored. 1012 @param obj: The object in question. 1013 @type obj: L{NVDAObjects.NVDAObject} 1014 @return: C{True} if focus on L{obj} should be ignored, C{False} otherwise. 1015 @rtype: bool 1016 """ 1017 return False
1018
1019 - def _postGainFocus(self, obj):
1020 """Executed after a gainFocus within the virtual buffer. 1021 This will not be executed if L{event_gainFocus} determined that it should abort and call nextHandler. 1022 @param obj: The object that gained focus. 1023 @type obj: L{NVDAObjects.NVDAObject} 1024 """
1025
1026 - def _replayFocusEnteredEvents(self):
1027 # We blocked the focusEntered events because we were in browse mode, 1028 # but now that we've switched to focus mode, we need to fire them. 1029 for parent in api.getFocusAncestors()[api.getFocusDifferenceLevel():]: 1030 try: 1031 parent.event_focusEntered() 1032 except: 1033 log.exception("Error executing focusEntered event: %s" % parent)
1034
1035 - def event_gainFocus(self, obj, nextHandler):
1036 if not self.isReady: 1037 if self.passThrough: 1038 nextHandler() 1039 return 1040 if not self.passThrough and self._lastFocusObj==obj: 1041 # This was the last non-document node with focus, so don't handle this focus event. 1042 # Otherwise, if the user switches away and back to this document, the cursor will jump to this node. 1043 # This is not ideal if the user was positioned over a node which cannot receive focus. 1044 return 1045 if obj==self.rootNVDAObject: 1046 if self.passThrough: 1047 return nextHandler() 1048 return 1049 if not self.passThrough and self._shouldIgnoreFocus(obj): 1050 return 1051 self._lastFocusObj=obj 1052 1053 try: 1054 focusInfo = self.makeTextInfo(obj) 1055 except: 1056 # This object is not in the virtual buffer, even though it resides beneath the document. 1057 # Automatic pass through should be enabled in certain circumstances where this occurs. 1058 if not self.passThrough and self.shouldPassThrough(obj,reason=speech.REASON_FOCUS): 1059 self.passThrough=True 1060 reportPassThrough(self) 1061 self._replayFocusEnteredEvents() 1062 return nextHandler() 1063 1064 #We only want to update the caret and speak the field if we're not in the same one as before 1065 caretInfo=self.makeTextInfo(textInfos.POSITION_CARET) 1066 # Expand to one character, as isOverlapping() doesn't treat, for example, (4,4) and (4,5) as overlapping. 1067 caretInfo.expand(textInfos.UNIT_CHARACTER) 1068 if not self._hadFirstGainFocus or not focusInfo.isOverlapping(caretInfo): 1069 # The virtual buffer caret is not within the focus node. 1070 oldPassThrough=self.passThrough 1071 if not oldPassThrough: 1072 # If pass-through is disabled, cancel speech, as a focus change should cause page reading to stop. 1073 # This must be done before auto-pass-through occurs, as we want to stop page reading even if pass-through will be automatically enabled by this focus change. 1074 speech.cancelSpeech() 1075 self.passThrough=self.shouldPassThrough(obj,reason=speech.REASON_FOCUS) 1076 if not self.passThrough: 1077 # We read the info from the buffer instead of the control itself. 1078 speech.speakTextInfo(focusInfo,reason=speech.REASON_FOCUS) 1079 # However, we still want to update the speech property cache so that property changes will be spoken properly. 1080 speech.speakObject(obj,speech.REASON_ONLYCACHE) 1081 else: 1082 if not oldPassThrough: 1083 self._replayFocusEnteredEvents() 1084 nextHandler() 1085 focusInfo.collapse() 1086 self._set_selection(focusInfo,reason=speech.REASON_FOCUS) 1087 else: 1088 # The virtual buffer caret was already at the focused node. 1089 if not self.passThrough: 1090 # This focus change was caused by a virtual caret movement, so don't speak the focused node to avoid double speaking. 1091 # However, we still want to update the speech property cache so that property changes will be spoken properly. 1092 speech.speakObject(obj,speech.REASON_ONLYCACHE) 1093 else: 1094 return nextHandler() 1095 1096 self._postGainFocus(obj)
1097 1098 event_gainFocus.ignoreIsReady=True 1099
1100 - def _handleScrollTo(self, obj):
1101 """Handle scrolling the buffer to a given object in response to an event. 1102 Subclasses should call this from an event which indicates that the buffer has scrolled. 1103 @postcondition: The buffer caret is moved to L{obj} and the buffer content for L{obj} is reported. 1104 @param obj: The object to which the buffer should scroll. 1105 @type obj: L{NVDAObjects.NVDAObject} 1106 @return: C{True} if the buffer was scrolled, C{False} if not. 1107 @rtype: bool 1108 @note: If C{False} is returned, calling events should probably call their nextHandler. 1109 """ 1110 if self.programmaticScrollMayFireEvent and self._lastProgrammaticScrollTime and time.time() - self._lastProgrammaticScrollTime < 0.4: 1111 # This event was probably caused by this buffer's call to scrollIntoView(). 1112 # Therefore, ignore it. Otherwise, the cursor may bounce back to the scroll point. 1113 # However, pretend we handled it, as we don't want it to be passed on to the object either. 1114 return True 1115 1116 try: 1117 scrollInfo = self.makeTextInfo(obj) 1118 except: 1119 return False 1120 1121 #We only want to update the caret and speak the field if we're not in the same one as before 1122 caretInfo=self.makeTextInfo(textInfos.POSITION_CARET) 1123 # Expand to one character, as isOverlapping() doesn't treat, for example, (4,4) and (4,5) as overlapping. 1124 caretInfo.expand(textInfos.UNIT_CHARACTER) 1125 if not scrollInfo.isOverlapping(caretInfo): 1126 if scrollInfo.isCollapsed: 1127 scrollInfo.expand(textInfos.UNIT_LINE) 1128 speech.speakTextInfo(scrollInfo,reason=speech.REASON_CARET) 1129 scrollInfo.collapse() 1130 self.selection = scrollInfo 1131 return True 1132 1133 return False
1134
1135 - def _getTableCellCoords(self, info):
1136 if info.isCollapsed: 1137 info = info.copy() 1138 info.expand(textInfos.UNIT_CHARACTER) 1139 for field in reversed(info.getTextWithFields()): 1140 if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"): 1141 # Not a control field. 1142 continue 1143 attrs = field.field 1144 if "table-id" in attrs and "table-rownumber" in attrs: 1145 break 1146 else: 1147 raise LookupError("Not in a table cell") 1148 return (int(attrs["table-id"]), 1149 int(attrs["table-rownumber"]), int(attrs["table-columnnumber"]), 1150 int(attrs.get("table-rowsspanned", 1)), int(attrs.get("table-columnsspanned", 1)))
1151
1152 - def _iterTableCells(self, tableID, startPos=None, direction="next", row=None, column=None):
1153 attrs = {"table-id": [str(tableID)]} 1154 # row could be 0. 1155 if row is not None: 1156 attrs["table-rownumber"] = [str(row)] 1157 if column is not None: 1158 attrs["table-columnnumber"] = [str(column)] 1159 startPos = startPos._startOffset if startPos else -1 1160 results = self._iterNodesByAttribs(attrs, offset=startPos, direction=direction) 1161 if not startPos and not row and not column and direction == "next": 1162 # The first match will be the table itself, so skip it. 1163 next(results) 1164 for node, start, end in results: 1165 yield self.makeTextInfo(textInfos.offsets.Offsets(start, end))
1166
1167 - def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis):
1168 if not axis: 1169 # First or last. 1170 if movement == "first": 1171 startPos = None 1172 direction = "next" 1173 elif movement == "last": 1174 startPos = self.makeTextInfo(textInfos.POSITION_LAST) 1175 direction = "previous" 1176 try: 1177 return next(self._iterTableCells(tableID, startPos=startPos, direction=direction)) 1178 except StopIteration: 1179 raise LookupError 1180 1181 # Determine destination row and column. 1182 destRow = origRow 1183 destCol = origCol 1184 if axis == "row": 1185 destRow += origRowSpan if movement == "next" else -1 1186 elif axis == "column": 1187 destCol += origColSpan if movement == "next" else -1 1188 1189 if destCol < 1: 1190 # Optimisation: We're definitely at the edge of the column. 1191 raise LookupError 1192 1193 # Optimisation: Try searching for exact destination coordinates. 1194 # This won't work if they are covered by a cell spanning multiple rows/cols, but this won't be true in the majority of cases. 1195 try: 1196 return next(self._iterTableCells(tableID, row=destRow, column=destCol)) 1197 except StopIteration: 1198 pass 1199 1200 # Cells are grouped by row, so in most cases, we simply need to search in the right direction. 1201 for info in self._iterTableCells(tableID, direction=movement, startPos=startPos): 1202 _ignore, row, col, rowSpan, colSpan = self._getTableCellCoords(info) 1203 if row <= destRow < row + rowSpan and col <= destCol < col + colSpan: 1204 return info 1205 elif row > destRow and movement == "next": 1206 # Optimisation: We've gone forward past destRow, so we know we won't find the cell. 1207 # We can't reverse this logic when moving backwards because there might be a prior cell on an earlier row which spans multiple rows. 1208 break 1209 1210 if axis == "row" or (axis == "column" and movement == "previous"): 1211 # In most cases, there's nothing more to try. 1212 raise LookupError 1213 1214 else: 1215 # We're moving forward by column. 1216 # In this case, there might be a cell on an earlier row which spans multiple rows. 1217 # Therefore, try searching backwards. 1218 for info in self._iterTableCells(tableID, direction="previous", startPos=startPos): 1219 _ignore, row, col, rowSpan, colSpan = self._getTableCellCoords(info) 1220 if row <= destRow < row + rowSpan and col <= destCol < col + colSpan: 1221 return info 1222 else: 1223 raise LookupError
1224
1225 - def _tableMovementScriptHelper(self, movement="next", axis=None):
1226 if isScriptWaiting(): 1227 return 1228 formatConfig=config.conf["documentFormatting"].copy() 1229 formatConfig["reportTables"]=True 1230 formatConfig["includeLayoutTables"]=True 1231 try: 1232 tableID, origRow, origCol, origRowSpan, origColSpan = self._getTableCellCoords(self.selection) 1233 except LookupError: 1234 ui.message(_("Not in a table cell")) 1235 return 1236 1237 try: 1238 info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis) 1239 except LookupError: 1240 ui.message(_("edge of table")) 1241 # Retrieve the cell on which we started. 1242 info = next(self._iterTableCells(tableID, row=origRow, column=origCol)) 1243 1244 speech.speakTextInfo(info,formatConfig=formatConfig,reason=speech.REASON_CARET) 1245 info.collapse() 1246 self.selection = info
1247
1248 - def script_nextRow(self, gesture):
1249 self._tableMovementScriptHelper(axis="row", movement="next")
1250 script_nextRow.__doc__ = _("moves to the next table row") 1251
1252 - def script_previousRow(self, gesture):
1253 self._tableMovementScriptHelper(axis="row", movement="previous")
1254 script_previousRow.__doc__ = _("moves to the previous table row") 1255
1256 - def script_nextColumn(self, gesture):
1257 self._tableMovementScriptHelper(axis="column", movement="next")
1258 script_nextColumn.__doc__ = _("moves to the next table column") 1259
1260 - def script_previousColumn(self, gesture):
1261 self._tableMovementScriptHelper(axis="column", movement="previous")
1262 script_previousColumn.__doc__ = _("moves to the previous table column") 1263 1264 APPLICATION_ROLES = (controlTypes.ROLE_APPLICATION, controlTypes.ROLE_DIALOG)
1265 - def _isNVDAObjectInApplication(self, obj):
1266 """Determine whether a given object is within an application. 1267 The object is considered to be within an application if it or one of its ancestors has an application role. 1268 This should only be called on objects beneath the buffer's root NVDAObject. 1269 @param obj: The object in question. 1270 @type obj: L{NVDAObjects.NVDAObject} 1271 @return: C{True} if L{obj} is within an application, C{False} otherwise. 1272 @rtype: bool 1273 """ 1274 while obj and obj != self.rootNVDAObject: 1275 if obj.role in self.APPLICATION_ROLES: 1276 return True 1277 obj = obj.parent 1278 return False
1279 1280 NOT_LINK_BLOCK_MIN_LEN = 30
1281 - def _iterNotLinkBlock(self, direction="next", offset=-1):
1282 links = self._iterNodesByType("link", direction=direction, offset=offset) 1283 # We want to compare each link against the next link. 1284 link1node, link1start, link1end = next(links) 1285 while True: 1286 link2node, link2start, link2end = next(links) 1287 # If the distance between the links is small, this is probably just a piece of non-link text within a block of links; e.g. an inactive link of a nav bar. 1288 if direction == "next" and link2start - link1end > self.NOT_LINK_BLOCK_MIN_LEN: 1289 yield 0, link1end, link2start 1290 # If we're moving backwards, the order of the links in the document will be reversed. 1291 elif direction == "previous" and link1start - link2end > self.NOT_LINK_BLOCK_MIN_LEN: 1292 yield 0, link2end, link1start 1293 link1node, link1start, link1end = link2node, link2start, link2end
1294
1295 - def _getInitialCaretPos(self):
1296 """Retrieve the initial position of the caret after the buffer has been loaded. 1297 This position, if any, will be passed to L{makeTextInfo}. 1298 Subclasses should extend this method. 1299 @return: The initial position of the caret, C{None} if there isn't one. 1300 @rtype: TextInfo position 1301 """ 1302 if self.shouldRememberCaretPositionAcrossLoads: 1303 try: 1304 return self.rootNVDAObject.appModule._vbufRememberedCaretPositions[self.documentConstantIdentifier] 1305 except KeyError: 1306 pass 1307 return None
1308
1309 - def _get_documentConstantIdentifier(self):
1310 """Get the constant identifier for this document. 1311 This identifier should uniquely identify all instances (not just one instance) of a document for at least the current session of the hosting application. 1312 Generally, the document URL should be used. 1313 @return: The constant identifier for this document, C{None} if there is none. 1314 """ 1315 return None
1316
1317 - def _get_shouldRememberCaretPositionAcrossLoads(self):
1318 """Specifies whether the position of the caret should be remembered when this document is loaded again. 1319 This is useful when the browser remembers the scroll position for the document, 1320 but does not communicate this information via APIs. 1321 The remembered caret position is associated with this document using L{documentConstantIdentifier}. 1322 @return: C{True} if the caret position should be remembered, C{False} if not. 1323 @rtype: bool 1324 """ 1325 docConstId = self.documentConstantIdentifier 1326 # Return True if the URL indicates that this is probably a web browser document. 1327 # We do this check because we don't want to remember caret positions for email messages, etc. 1328 return isinstance(docConstId, basestring) and docConstId.split("://", 1)[0] in ("http", "https", "ftp", "ftps", "file")
1329 1330 __gestures = { 1331 "kb:enter": "activatePosition", 1332 "kb:space": "activatePosition", 1333 "kb:NVDA+f5": "refreshBuffer", 1334 "kb:NVDA+v": "toggleScreenLayout", 1335 "kb:NVDA+f7": "elementsList", 1336 "kb:escape": "disablePassThrough", 1337 "kb:alt+upArrow": "collapseOrExpandControl", 1338 "kb:alt+downArrow": "collapseOrExpandControl", 1339 "kb:tab": "tab", 1340 "kb:shift+tab": "shiftTab", 1341 "kb:control+alt+downArrow": "nextRow", 1342 "kb:control+alt+upArrow": "previousRow", 1343 "kb:control+alt+rightArrow": "nextColumn", 1344 "kb:control+alt+leftArrow": "previousColumn", 1345 } 1346 1347 # Add quick navigation scripts. 1348 qn = VirtualBuffer.addQuickNav 1349 qn("heading", key="h", nextDoc=_("moves to the next heading"), nextError=_("no next heading"), 1350 prevDoc=_("moves to the previous heading"), prevError=_("no previous heading")) 1351 qn("heading1", key="1", nextDoc=_("moves to the next heading at level 1"), nextError=_("no next heading at level 1"), 1352 prevDoc=_("moves to the previous heading at level 1"), prevError=_("no previous heading at level 1")) 1353 qn("heading2", key="2", nextDoc=_("moves to the next heading at level 2"), nextError=_("no next heading at level 2"), 1354 prevDoc=_("moves to the previous heading at level 2"), prevError=_("no previous heading at level 2")) 1355 qn("heading3", key="3", nextDoc=_("moves to the next heading at level 3"), nextError=_("no next heading at level 3"), 1356 prevDoc=_("moves to the previous heading at level 3"), prevError=_("no previous heading at level 3")) 1357 qn("heading4", key="4", nextDoc=_("moves to the next heading at level 4"), nextError=_("no next heading at level 4"), 1358 prevDoc=_("moves to the previous heading at level 4"), prevError=_("no previous heading at level 4")) 1359 qn("heading5", key="5", nextDoc=_("moves to the next heading at level 5"), nextError=_("no next heading at level 5"), 1360 prevDoc=_("moves to the previous heading at level 5"), prevError=_("no previous heading at level 5")) 1361 qn("heading6", key="6", nextDoc=_("moves to the next heading at level 6"), nextError=_("no next heading at level 6"), 1362 prevDoc=_("moves to the previous heading at level 6"), prevError=_("no previous heading at level 6")) 1363 qn("table", key="t", nextDoc=_("moves to the next table"), nextError=_("no next table"), 1364 prevDoc=_("moves to the previous table"), prevError=_("no previous table"), readUnit=textInfos.UNIT_LINE) 1365 qn("link", key="k", nextDoc=_("moves to the next link"), nextError=_("no next link"), 1366 prevDoc=_("moves to the previous link"), prevError=_("no previous link")) 1367 qn("visitedLink", key="v", nextDoc=_("moves to the next visited link"), nextError=_("no next visited link"), 1368 prevDoc=_("moves to the previous visited link"), prevError=_("no previous visited link")) 1369 qn("unvisitedLink", key="u", nextDoc=_("moves to the next unvisited link"), nextError=_("no next unvisited link"), 1370 prevDoc=_("moves to the previous unvisited link"), prevError=_("no previous unvisited link")) 1371 qn("formField", key="f", nextDoc=_("moves to the next form field"), nextError=_("no next form field"), 1372 prevDoc=_("moves to the previous form field"), prevError=_("no previous form field"), readUnit=textInfos.UNIT_LINE) 1373 qn("list", key="l", nextDoc=_("moves to the next list"), nextError=_("no next list"), 1374 prevDoc=_("moves to the previous list"), prevError=_("no previous list"), readUnit=textInfos.UNIT_LINE) 1375 qn("listItem", key="i", nextDoc=_("moves to the next list item"), nextError=_("no next list item"), 1376 prevDoc=_("moves to the previous list item"), prevError=_("no previous list item")) 1377 qn("button", key="b", nextDoc=_("moves to the next button"), nextError=_("no next button"), 1378 prevDoc=_("moves to the previous button"), prevError=_("no previous button")) 1379 qn("edit", key="e", nextDoc=_("moves to the next edit field"), nextError=_("no next edit field"), 1380 prevDoc=_("moves to the previous edit field"), prevError=_("no previous edit field"), readUnit=textInfos.UNIT_LINE) 1381 qn("frame", key="m", nextDoc=_("moves to the next frame"), nextError=_("no next frame"), 1382 prevDoc=_("moves to the previous frame"), prevError=_("no previous frame"), readUnit=textInfos.UNIT_LINE) 1383 qn("separator", key="s", nextDoc=_("moves to the next separator"), nextError=_("no next separator"), 1384 prevDoc=_("moves to the previous separator"), prevError=_("no previous separator")) 1385 qn("radioButton", key="r", nextDoc=_("moves to the next radio button"), nextError=_("no next radio button"), 1386 prevDoc=_("moves to the previous radio button"), prevError=_("no previous radio button")) 1387 qn("comboBox", key="c", nextDoc=_("moves to the next combo box"), nextError=_("no next combo box"), 1388 prevDoc=_("moves to the previous combo box"), prevError=_("no previous combo box")) 1389 qn("checkBox", key="x", nextDoc=_("moves to the next check box"), nextError=_("no next check box"), 1390 prevDoc=_("moves to the previous check box"), prevError=_("no previous check box")) 1391 qn("graphic", key="g", nextDoc=_("moves to the next graphic"), nextError=_("no next graphic"), 1392 prevDoc=_("moves to the previous graphic"), prevError=_("no previous graphic")) 1393 qn("blockQuote", key="q", nextDoc=_("moves to the next block quote"), nextError=_("no next block quote"), 1394 prevDoc=_("moves to the previous block quote"), prevError=_("no previous block quote")) 1395 qn("notLinkBlock", key="n", nextDoc=_("skips forward past a block of links"), nextError=_("no more text after a block of links"), 1396 prevDoc=_("skips backward past a block of links"), prevError=_("no more text before a block of links"), readUnit=textInfos.UNIT_LINE) 1397 qn("landmark", key="d", nextDoc=_("moves to the next landmark"), nextError=_("no next landmark"), 1398 prevDoc=_("moves to the previous landmark"), prevError=_("no previous landmark"), readUnit=textInfos.UNIT_LINE) 1399 qn("embeddedObject", key="o", nextDoc=_("moves to the next embedded object"), nextError=_("no next embedded object"), 1400 prevDoc=_("moves to the previous embedded object"), prevError=_("no previous embedded object")) 1401 del qn
1402 1403 -def reportPassThrough(virtualBuffer):
1404 """Reports the virtual buffer pass through mode if it has changed. 1405 @param virtualBuffer: The current virtual buffer. 1406 @type virtualBuffer: L{virtualBuffers.VirtualBuffer} 1407 """ 1408 if virtualBuffer.passThrough != reportPassThrough.last: 1409 if config.conf["virtualBuffers"]["passThroughAudioIndication"]: 1410 sound = r"waves\focusMode.wav" if virtualBuffer.passThrough else r"waves\browseMode.wav" 1411 nvwave.playWaveFile(sound) 1412 else: 1413 speech.speakMessage(_("focus mode") if virtualBuffer.passThrough else _("browse mode")) 1414 reportPassThrough.last = virtualBuffer.passThrough
1415 reportPassThrough.last = False 1416