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
36
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
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
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
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
86 ancestorCount = 0
87 while True:
88 try:
89 return self._getOffsetsFromNVDAObjectInBuffer(obj)
90 except LookupError:
91 pass
92
93
94 if ancestorCount == 2:
95
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
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
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
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
168 tableLayout=attrs.get('table-layout')
169 if tableLayout:
170 attrs['table-layout']=tableLayout=="1"
171
172
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
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
192
193 - def _getLineNumFromOffset(self, offset):
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
210
211
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
246 ELEMENT_TYPES = (
247 ("link", _("Lin&ks")),
248 ("heading", _("&Headings")),
249 ("landmark", _("Lan&dmarks")),
250 )
251 Element = collections.namedtuple("Element", ("textInfo", "text", "parent"))
252
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
296
298 if elType == "link":
299
300 self.activateButton.Enable()
301 self.SetAffirmativeId(self.activateButton.GetId())
302 else:
303
304 self.activateButton.Disable()
305 self.SetAffirmativeId(self.moveButton.GetId())
306
307
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
319 for parent in reversed(parentElements):
320 if self.isChildElement(elType, parent.textInfo, elInfo):
321 break
322 else:
323
324 parentElements.pop()
325 else:
326
327
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
335
336 try:
337 self._initialElement = self._elements[-2]
338 except IndexError:
339
340 pass
341
342
343 parentElements.append(element)
344
345
346 self.filter("", newElementType=True)
347
348 - def filter(self, filterText, newElementType=False):
349
350
351 defaultElement = self._initialElement if newElementType else self.tree.GetItemPyData(self.tree.GetSelection())
352
353 self.tree.DeleteChildren(self.treeRoot)
354
355
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
378 self.activateButton.Disable()
379 self.moveButton.Disable()
380 return
381
382
383 self.tree.SelectItem(defaultItem or self.tree.GetFirstChild(self.treeRoot)[0])
384
385
386 if self.AffirmativeId == self.activateButton.Id:
387 self.activateButton.Enable()
388 self.moveButton.Enable()
389
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
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
425
426 self._searchText = ""
427 self._searchCallLater = None
428 evt.Skip()
429
431 key = evt.KeyCode
432
433 if key == wx.WXK_RETURN:
434
435
436
437
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
447 self._searchText = ""
448 evt.Skip()
449
450 else:
451
452
453 char = unichr(evt.UnicodeKey).lower()
454
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
464 self._searchText = ""
465
466 - def search(self, searchText):
467 item = self.tree.GetSelection()
468 if not item:
469
470 return
471
472
473
474 items = itertools.chain(self._iterReachableTreeItemsFromItem(item), self._iterReachableTreeItemsFromItem(self.tree.GetFirstChild(self.treeRoot)[0]))
475 if len(searchText) == 1:
476
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
485 wx.Bell()
486
488 while item:
489 yield item
490
491 childItem = self.tree.GetFirstChild(item)[0]
492 if childItem and self.tree.IsExpanded(item):
493
494 for childItem in self._iterReachableTreeItemsFromItem(childItem):
495 yield childItem
496
497 item = self.tree.GetNextSibling(item)
498
500 self.filter(self.filterEdit.GetValue())
501 evt.Skip()
502
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
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
539 self.documentConstantIdentifier = self.documentConstantIdentifier
540 if not hasattr(self.rootNVDAObject.appModule, "_vbufRememberedCaretPositions"):
541 self.rootNVDAObject.appModule._vbufRememberedCaretPositions = {}
542 self._lastCaretPosition = None
543
547
549 return not self.isLoading and not self.VBufHandle
550
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
560 pass
561
562 self.unloadBuffer()
563
565 return bool(self.VBufHandle and not self.isLoading)
566
568 self.isLoading = True
569 self._loadProgressCallLater = wx.CallLater(1000, self._loadProgress)
570 threading.Thread(target=self._loadBuffer).start()
571
582
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
592 speech.speakMessage(_("Refreshed"))
593 if api.getFocusObject().treeInterceptor == self:
594 self.event_treeInterceptor_gainFocus()
595
598
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
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
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
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
668
669 focus = api.getFocusObject()
670 self.event_gainFocus(focus, lambda: focus.event_gainFocus())
671 if not self.passThrough:
672
673
674
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
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
706 if self.passThrough:
707 nextHandler()
708
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
728
761
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
773 script_activatePosition.__doc__ = _("activates the current object in the document")
774
781 script_refreshBuffer.__doc__ = _("Refreshes the document content")
782
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
791
799
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
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
869 wx.CallAfter(run)
870 script_elementsList.__doc__ = _("Presents a list of links, headings or landmarks")
871
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
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
924
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
946 script_collapseOrExpandControl.ignoreTreeInterceptorPassThrough = True
947
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
965 caretInfo=self.makeTextInfo(textInfos.POSITION_CARET)
966
967
968 if focus.role!=controlTypes.ROLE_DOCUMENT or controlTypes.STATE_EDITABLE in focus.states:
969
970 caretInfo.expand(textInfos.UNIT_CHARACTER)
971 if focusInfo.isOverlapping(caretInfo):
972 return False
973
974
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
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
995 obj.setFocus()
996 return True
997
1001
1005
1007 if self.passThrough:
1008 nextHandler()
1009
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
1034
1097
1098 event_gainFocus.ignoreIsReady=True
1099
1134
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
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
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
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
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
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
1191 raise LookupError
1192
1193
1194
1195 try:
1196 return next(self._iterTableCells(tableID, row=destRow, column=destCol))
1197 except StopIteration:
1198 pass
1199
1200
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
1207
1208 break
1209
1210 if axis == "row" or (axis == "column" and movement == "previous"):
1211
1212 raise LookupError
1213
1214 else:
1215
1216
1217
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
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
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
1250 script_nextRow.__doc__ = _("moves to the next table row")
1251
1254 script_previousRow.__doc__ = _("moves to the previous table row")
1255
1258 script_nextColumn.__doc__ = _("moves to the next table column")
1259
1262 script_previousColumn.__doc__ = _("moves to the previous table column")
1263
1264 APPLICATION_ROLES = (controlTypes.ROLE_APPLICATION, controlTypes.ROLE_DIALOG)
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
1282 links = self._iterNodesByType("link", direction=direction, offset=offset)
1283
1284 link1node, link1start, link1end = next(links)
1285 while True:
1286 link2node, link2start, link2end = next(links)
1287
1288 if direction == "next" and link2start - link1end > self.NOT_LINK_BLOCK_MIN_LEN:
1289 yield 0, link1end, link2start
1290
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
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
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
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
1327
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
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
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