~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/virtualBuffers/__init__.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import time
2
2
import threading
3
3
import ctypes
4
 
import os
5
4
import collections
6
5
import itertools
7
6
import wx
8
7
import NVDAHelper
9
8
import XMLFormatting
10
 
from keyUtils import sendKey
11
9
import scriptHandler
12
10
from scriptHandler import isScriptWaiting
13
11
import speech
19
17
import config
20
18
import cursorManager
21
19
import gui
22
 
import virtualBufferHandler
23
20
import eventHandler
24
21
import braille
25
22
import queueHandler
26
23
from logHandler import log
27
24
import ui
28
25
import aria
 
26
import nvwave
 
27
import treeInterceptorHandler
 
28
import watchdog
29
29
 
30
30
VBufStorage_findDirection_forward=0
31
31
VBufStorage_findDirection_back=1
61
61
                NVDAHelper.localLib.VBuf_locateControlFieldNodeAtOffset(self.obj.VBufHandle, offset, ctypes.byref(startOffset), ctypes.byref(endOffset), ctypes.byref(docHandle), ctypes.byref(ID))
62
62
                return docHandle.value, ID.value
63
63
 
64
 
        def _getNVDAObjectFromOffset(self,offset):
65
 
                docHandle,ID=self._getFieldIdentifierFromOffset(offset)
66
 
                return self.obj.getNVDAObjectFromIdentifier(docHandle,ID)
67
 
 
68
 
        def _getOffsetsFromNVDAObject(self,obj):
69
 
                docHandle,ID=self.obj.getIdentifierFromNVDAObject(obj)
 
64
        def _getOffsetsFromFieldIdentifier(self, docHandle, ID):
70
65
                node = NVDAHelper.localLib.VBuf_getControlFieldNodeWithIdentifier(self.obj.VBufHandle, docHandle, ID)
71
66
                if not node:
72
67
                        raise LookupError
75
70
                NVDAHelper.localLib.VBuf_getFieldNodeOffsets(self.obj.VBufHandle, node, ctypes.byref(start), ctypes.byref(end))
76
71
                return start.value, end.value
77
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
 
 
81
        def _getOffsetsFromNVDAObjectInBuffer(self,obj):
 
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
 
78
102
        def __init__(self,obj,position):
79
103
                self.obj=obj
80
 
                if isinstance(position,NVDAObjects.NVDAObject):
81
 
                        start,end=self._getOffsetsFromNVDAObject(position)
82
 
                        position=textInfos.offsets.Offsets(start,end)
83
104
                super(VirtualBufferTextInfo,self).__init__(obj,position)
84
105
 
85
 
        def _get_NVDAObjectAtStart(self):
86
 
                return self._getNVDAObjectFromOffset(self._startOffset)
87
 
 
88
106
        def _getSelectionOffsets(self):
89
107
                start=ctypes.c_int()
90
108
                end=ctypes.c_int()
105
123
 
106
124
        def _getTextRange(self,start,end):
107
125
                if start==end:
108
 
                        return ""
109
 
                return NVDAHelper.VBuf_getTextInRange(self.obj.VBufHandle,start,end,False)
 
126
                        return u""
 
127
                return NVDAHelper.VBuf_getTextInRange(self.obj.VBufHandle,start,end,False) or u""
110
128
 
111
129
        def getTextWithFields(self,formatConfig=None):
112
130
                start=self._startOffset
150
168
                tableLayout=attrs.get('table-layout')
151
169
                if tableLayout:
152
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
 
153
188
                return attrs
154
189
 
155
190
        def _normalizeFormatField(self, attrs):
171
206
                        return startOffset.value,endOffset.value
172
207
                return super(VirtualBufferTextInfo, self)._getUnitOffsets(unit, offset)
173
208
 
174
 
        def getXMLFieldSpeech(self,attrs,fieldType,extraDetail=False,reason=None):
175
 
                return speech.getXMLFieldSpeech(self,attrs,fieldType,extraDetail=extraDetail,reason=reason)
176
 
 
177
 
        def copyToClipboard(self):
 
209
        def _get_clipboardText(self):
178
210
                # Blocks should start on a new line, but they don't necessarily have an end of line indicator.
179
211
                # Therefore, get the text in block (paragraph) chunks and join the chunks with \r\n.
180
212
                blocks = (block.strip("\r\n") for block in self.getTextInChunks(textInfos.UNIT_PARAGRAPH))
181
 
                return api.copyToClip("\r\n".join(blocks))
 
213
                return "\r\n".join(blocks)
182
214
 
183
215
        def getControlFieldSpeech(self, attrs, ancestorAttrs, fieldType, formatConfig=None, extraDetail=False, reason=None):
184
216
                textList = []
192
224
                try:
193
225
                        newNode, newStart, newEnd = next(self.obj._iterNodesByType("focusable", "up", self._startOffset))
194
226
                except StopIteration:
195
 
                        return None
 
227
                        return self.obj.rootNVDAObject
196
228
                if not newNode:
197
 
                        return None
 
229
                        return self.obj.rootNVDAObject
198
230
                docHandle=ctypes.c_int()
199
231
                ID=ctypes.c_int()
200
232
                NVDAHelper.localLib.VBuf_getIdentifierFromControlFieldNode(self.obj.VBufHandle, newNode, ctypes.byref(docHandle), ctypes.byref(ID))
215
247
 
216
248
                child = wx.RadioBox(self, wx.ID_ANY, label=_("Type:"), choices=tuple(et[1] for et in self.ELEMENT_TYPES))
217
249
                child.Bind(wx.EVT_RADIOBOX, self.onElementTypeChange)
218
 
                mainSizer.Add(child)
 
250
                mainSizer.Add(child,proportion=1)
219
251
 
220
252
                self.tree = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_SINGLE)
221
253
                self.tree.Bind(wx.EVT_SET_FOCUS, self.onTreeSetFocus)
222
254
                self.tree.Bind(wx.EVT_CHAR, self.onTreeChar)
223
255
                self.treeRoot = self.tree.AddRoot("root")
224
 
                mainSizer.Add(self.tree)
 
256
                mainSizer.Add(self.tree,proportion=7)
225
257
 
226
258
                sizer = wx.BoxSizer(wx.HORIZONTAL)
227
259
                label = wx.StaticText(self, wx.ID_ANY, _("&Filter by:"))
229
261
                self.filterEdit = wx.TextCtrl(self, wx.ID_ANY)
230
262
                self.filterEdit.Bind(wx.EVT_TEXT, self.onFilterEditTextChange)
231
263
                sizer.Add(self.filterEdit)
232
 
                mainSizer.Add(sizer)
 
264
                mainSizer.Add(sizer,proportion=1)
233
265
 
234
266
                sizer = wx.BoxSizer(wx.HORIZONTAL)
235
267
                self.activateButton = wx.Button(self, wx.ID_ANY, _("&Activate"))
239
271
                self.moveButton.Bind(wx.EVT_BUTTON, lambda evt: self.onAction(False))
240
272
                sizer.Add(self.moveButton)
241
273
                sizer.Add(wx.Button(self, wx.ID_CANCEL))
242
 
                mainSizer.Add(sizer)
 
274
                mainSizer.Add(sizer,proportion=1)
243
275
 
244
276
                mainSizer.Fit(self)
245
277
                self.SetSizer(mainSizer)
476
508
                speech.cancelSpeech()
477
509
                speech.speakTextInfo(element,reason=speech.REASON_FOCUS)
478
510
 
479
 
class VirtualBuffer(cursorManager.CursorManager):
 
511
class VirtualBuffer(cursorManager.CursorManager, treeInterceptorHandler.TreeInterceptor):
480
512
 
481
513
        REASON_QUICKNAV = "quickNav"
482
514
 
484
516
        programmaticScrollMayFireEvent = False
485
517
 
486
518
        def __init__(self,rootNVDAObject,backendName=None):
 
519
                super(VirtualBuffer,self).__init__(rootNVDAObject)
487
520
                self.backendName=backendName
488
 
                self.rootNVDAObject=rootNVDAObject
489
 
                super(VirtualBuffer,self).__init__()
490
521
                self.VBufHandle=None
491
 
                self._passThrough=False
 
522
                self.isLoading=False
492
523
                self.disableAutoPassThrough = False
493
524
                self.rootDocHandle,self.rootID=self.getIdentifierFromNVDAObject(self.rootNVDAObject)
494
525
                self._lastFocusObj = None
495
526
                self._hadFirstGainFocus = False
496
527
                self._lastProgrammaticScrollTime = None
497
 
 
498
 
        def _get_passThrough(self):
499
 
                return self._passThrough
500
 
 
501
 
        def _set_passThrough(self, state):
502
 
                if self._passThrough == state:
 
528
                # We need to cache this because it will be unavailable once the document dies.
 
529
                self.documentConstantIdentifier = self.documentConstantIdentifier
 
530
                if not hasattr(self.rootNVDAObject.appModule, "_vbufRememberedCaretPositions"):
 
531
                        self.rootNVDAObject.appModule._vbufRememberedCaretPositions = {}
 
532
                self._lastCaretPosition = None
 
533
 
 
534
        def prepare(self):
 
535
                self.shouldPrepare=False
 
536
                self.loadBuffer()
 
537
 
 
538
        def _get_shouldPrepare(self):
 
539
                return not self.isLoading and not self.VBufHandle
 
540
 
 
541
        def terminate(self):
 
542
                if not self.VBufHandle:
503
543
                        return
504
 
                self._passThrough = state
505
 
                if state:
506
 
                        braille.handler.handleGainFocus(api.getFocusObject())
507
 
                else:
508
 
                        braille.handler.handleGainFocus(self)
 
544
 
 
545
                if self.shouldRememberCaretPositionAcrossLoads and self._lastCaretPosition:
 
546
                        try:
 
547
                                self.rootNVDAObject.appModule._vbufRememberedCaretPositions[self.documentConstantIdentifier] = self._lastCaretPosition
 
548
                        except AttributeError:
 
549
                                # The app module died.
 
550
                                pass
 
551
 
 
552
                self.unloadBuffer()
 
553
 
 
554
        def _get_isReady(self):
 
555
                return bool(self.VBufHandle and not self.isLoading)
509
556
 
510
557
        def loadBuffer(self):
511
558
                self.isLoading = True
528
575
                del self._loadProgressCallLater
529
576
                self.isLoading = False
530
577
                if not success:
 
578
                        self.passThrough=True
531
579
                        return
532
580
                if self._hadFirstGainFocus:
533
581
                        # If this buffer has already had focus once while loaded, this is a refresh.
534
582
                        speech.speakMessage(_("Refreshed"))
535
 
                if api.getFocusObject().virtualBuffer == self:
536
 
                        self.event_virtualBuffer_gainFocus()
 
583
                if api.getFocusObject().treeInterceptor == self:
 
584
                        self.event_treeInterceptor_gainFocus()
537
585
 
538
586
        def _loadProgress(self):
539
587
                ui.message(_("Loading document..."))
541
589
        def unloadBuffer(self):
542
590
                if self.VBufHandle is not None:
543
591
                        try:
544
 
                                NVDAHelper.localLib.VBuf_destroyBuffer(ctypes.byref(ctypes.c_int(self.VBufHandle)))
 
592
                                watchdog.cancellableExecute(NVDAHelper.localLib.VBuf_destroyBuffer, ctypes.byref(ctypes.c_int(self.VBufHandle)))
545
593
                        except WindowsError:
546
594
                                pass
547
595
                        self.VBufHandle=None
549
597
        def makeTextInfo(self,position):
550
598
                return self.TextInfo(self,position)
551
599
 
552
 
        def isNVDAObjectInVirtualBuffer(self,obj):
553
 
                pass
554
 
 
555
 
        def isAlive(self):
556
 
                pass
 
600
        def isNVDAObjectPartOfLayoutTable(self,obj):
 
601
                docHandle,ID=self.getIdentifierFromNVDAObject(obj)
 
602
                ID=unicode(ID)
 
603
                info=self.makeTextInfo(obj)
 
604
                info.collapse()
 
605
                info.expand(textInfos.UNIT_CHARACTER)
 
606
                fieldCommands=[x for x in info.getTextWithFields() if isinstance(x,textInfos.FieldCommand)]
 
607
                tableLayout=None
 
608
                tableID=None
 
609
                for fieldCommand in fieldCommands:
 
610
                        fieldID=fieldCommand.field.get("controlIdentifier_ID") if fieldCommand.field else None
 
611
                        if fieldID==ID:
 
612
                                tableLayout=fieldCommand.field.get('table-layout')
 
613
                                if tableLayout is not None:
 
614
                                        return tableLayout
 
615
                                tableID=fieldCommand.field.get('table-id')
 
616
                                break
 
617
                if tableID is None:
 
618
                        return False
 
619
                for fieldCommand in fieldCommands:
 
620
                        fieldID=fieldCommand.field.get("controlIdentifier_ID") if fieldCommand.field else None
 
621
                        if fieldID==tableID:
 
622
                                tableLayout=fieldCommand.field.get('table-layout',False)
 
623
                                break
 
624
                return tableLayout
 
625
 
 
626
 
 
627
 
557
628
 
558
629
        def getNVDAObjectFromIdentifier(self, docHandle, ID):
559
630
                """Retrieve an NVDAObject for a given node identifier.
576
647
                """
577
648
                raise NotImplementedError
578
649
 
579
 
        def event_virtualBuffer_gainFocus(self):
 
650
        def event_treeInterceptor_gainFocus(self):
580
651
                """Triggered when this virtual buffer gains focus.
581
652
                This event is only fired upon entering this buffer when it was not the current buffer before.
582
653
                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.
583
654
                """
 
655
                doSayAll=False
584
656
                if not self._hadFirstGainFocus:
585
657
                        # This buffer is gaining focus for the first time.
586
 
                        self._setInitialCaretPos()
587
658
                        # Fake a focus event on the focus object, as the buffer may have missed the actual focus event.
588
659
                        focus = api.getFocusObject()
589
660
                        self.event_gainFocus(focus, lambda: focus.event_gainFocus())
590
661
                        if not self.passThrough:
591
 
                                speech.cancelSpeech()
592
 
                                virtualBufferHandler.reportPassThrough(self)
593
 
                                speech.speakObjectProperties(self.rootNVDAObject,name=True)
 
662
                                # We only set the caret position if in browse mode.
 
663
                                # If in focus mode, the document must have forced the focus somewhere,
 
664
                                # so we don't want to override it.
 
665
                                initialPos = self._getInitialCaretPos()
 
666
                                if initialPos:
 
667
                                        self.selection = self.makeTextInfo(initialPos)
 
668
                                reportPassThrough(self)
 
669
                                doSayAll=config.conf['virtualBuffers']['autoSayAllOnPageLoad']
 
670
                        self._hadFirstGainFocus = True
 
671
 
 
672
                if not self.passThrough:
 
673
                        if doSayAll:
 
674
                                speech.speakObjectProperties(self.rootNVDAObject,name=True,states=True,reason=speech.REASON_FOCUS)
594
675
                                info=self.makeTextInfo(textInfos.POSITION_CARET)
595
676
                                sayAllHandler.readText(info,sayAllHandler.CURSOR_CARET)
596
 
                        self._hadFirstGainFocus = True
597
 
 
598
 
                else:
599
 
                        # This buffer has had focus before.
600
 
                        if not self.passThrough:
 
677
                        else:
601
678
                                # Speak it like we would speak focus on any other document object.
602
679
                                speech.speakObject(self.rootNVDAObject, reason=speech.REASON_FOCUS)
603
680
                                info = self.selection
607
684
                                        info.expand(textInfos.UNIT_LINE)
608
685
                                        speech.speakTextInfo(info, reason=speech.REASON_CARET)
609
686
 
610
 
                virtualBufferHandler.reportPassThrough(self)
 
687
                reportPassThrough(self)
611
688
                braille.handler.handleGainFocus(self)
612
689
 
613
 
        def event_virtualBuffer_loseFocus(self):
 
690
        def event_treeInterceptor_loseFocus(self):
614
691
                """Triggered when this virtual buffer loses focus.
615
692
                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.
616
693
                """
617
694
 
618
 
        def event_becomeNavigatorObject(self, obj, nextHandler):
619
 
                if self.passThrough:
620
 
                        nextHandler()
621
 
 
622
695
        def event_caret(self, obj, nextHandler):
623
696
                if self.passThrough:
624
697
                        nextHandler()
636
709
                if self.shouldPassThrough(obj):
637
710
                        obj.setFocus()
638
711
                        self.passThrough = True
639
 
                        virtualBufferHandler.reportPassThrough(self)
 
712
                        reportPassThrough(self)
640
713
                elif obj.role == controlTypes.ROLE_EMBEDDEDOBJECT:
641
714
                        obj.setFocus()
642
715
                        speech.speakObject(obj, reason=speech.REASON_FOCUS)
647
720
                super(VirtualBuffer, self)._set_selection(info)
648
721
                if isScriptWaiting() or not info.isCollapsed:
649
722
                        return
650
 
                api.setReviewPosition(info)
651
 
                obj=info.NVDAObjectAtStart
652
 
                if not obj:
653
 
                        log.debugWarning("Invalid NVDAObjectAtStart")
654
 
                        return
655
 
                if obj==self.rootNVDAObject:
656
 
                        return
 
723
                # Save the last caret position for use in terminate().
 
724
                # This must be done here because the buffer might be cleared just before terminate() is called,
 
725
                # causing the last caret position to be lost.
 
726
                caret = info.copy()
 
727
                caret.collapse()
 
728
                self._lastCaretPosition = caret.bookmark
 
729
                if config.conf['reviewCursor']['followCaret'] and api.getNavigatorObject() is self.rootNVDAObject:
 
730
                        api.setReviewPosition(info)
657
731
                if reason == speech.REASON_FOCUS:
658
732
                        focusObj = api.getFocusObject()
 
733
                        if focusObj==self.rootNVDAObject:
 
734
                                return
659
735
                else:
660
736
                        focusObj=info.focusableNVDAObjectAtStart
661
 
                if reason != speech.REASON_FOCUS:
 
737
                        obj=info.NVDAObjectAtStart
 
738
                        if not obj:
 
739
                                log.debugWarning("Invalid NVDAObjectAtStart")
 
740
                                return
 
741
                        if obj==self.rootNVDAObject:
 
742
                                return
662
743
                        if focusObj and not eventHandler.isPendingEvents("gainFocus") and focusObj!=self.rootNVDAObject and focusObj != api.getFocusObject() and self._shouldSetFocusToObj(focusObj):
663
744
                                focusObj.setFocus()
664
745
                        obj.scrollIntoView()
666
747
                                self._lastProgrammaticScrollTime = time.time()
667
748
                self.passThrough=self.shouldPassThrough(focusObj,reason=reason)
668
749
                # Queue the reporting of pass through mode so that it will be spoken after the actual content.
669
 
                queueHandler.queueFunction(queueHandler.eventQueue, virtualBufferHandler.reportPassThrough, self)
 
750
                queueHandler.queueFunction(queueHandler.eventQueue, reportPassThrough, self)
670
751
 
671
752
        def _shouldSetFocusToObj(self, obj):
672
753
                """Determine whether an object should receive focus.
676
757
                """
677
758
                return controlTypes.STATE_FOCUSABLE in obj.states
678
759
 
679
 
        def script_activatePosition(self,keyPress):
680
 
                if self.VBufHandle is None:
681
 
                        return sendKey(keyPress)
 
760
        def script_activatePosition(self,gesture):
682
761
                info=self.makeTextInfo(textInfos.POSITION_CARET)
683
762
                self._activatePosition(info)
684
 
        script_activatePosition.__doc__ = _("activates the current object in the virtual buffer")
685
 
 
686
 
        def _caretMovementScriptHelper(self, *args, **kwargs):
687
 
                if self.VBufHandle is None:
688
 
                        return 
689
 
                super(VirtualBuffer, self)._caretMovementScriptHelper(*args, **kwargs)
690
 
 
691
 
        def script_refreshBuffer(self,keyPress):
692
 
                if self.VBufHandle is None:
 
763
        script_activatePosition.__doc__ = _("activates the current object in the document")
 
764
 
 
765
        def script_refreshBuffer(self,gesture):
 
766
                if scriptHandler.isScriptWaiting():
 
767
                        # This script may cause subsequently queued scripts to fail, so don't execute.
693
768
                        return
694
769
                self.unloadBuffer()
695
770
                self.loadBuffer()
696
 
        script_refreshBuffer.__doc__ = _("Refreshes the virtual buffer content")
 
771
        script_refreshBuffer.__doc__ = _("Refreshes the document content")
697
772
 
698
 
        def script_toggleScreenLayout(self,keyPress):
 
773
        def script_toggleScreenLayout(self,gesture):
699
774
                config.conf["virtualBuffers"]["useScreenLayout"]=not config.conf["virtualBuffers"]["useScreenLayout"]
700
775
                onOff=_("on") if config.conf["virtualBuffers"]["useScreenLayout"] else _("off")
701
776
                speech.speakMessage(_("use screen layout %s")%onOff)
702
 
        script_toggleScreenLayout.__doc__ = _("Toggles on and off if the screen layout is preserved while rendering the virtual buffer content")
 
777
        script_toggleScreenLayout.__doc__ = _("Toggles on and off if the screen layout is preserved while rendering the document content")
703
778
 
704
779
        def _searchableAttributesForNodeType(self,nodeType):
705
780
                pass
734
809
                        yield node, startOffset.value, endOffset.value
735
810
                        offset=startOffset
736
811
 
737
 
        def _quickNavScript(self,keyPress, nodeType, direction, errorMessage, readUnit):
738
 
                if self.VBufHandle is None:
739
 
                        return sendKey(keyPress)
 
812
        def _quickNavScript(self,gesture, nodeType, direction, errorMessage, readUnit):
740
813
                info=self.makeTextInfo(textInfos.POSITION_CARET)
741
814
                startOffset=info._startOffset
742
815
                endOffset=info._endOffset
762
835
                scriptSuffix = nodeType[0].upper() + nodeType[1:]
763
836
                scriptName = "next%s" % scriptSuffix
764
837
                funcName = "script_%s" % scriptName
765
 
                script = lambda self,keyPress: self._quickNavScript(keyPress, nodeType, "next", nextError, readUnit)
 
838
                script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "next", nextError, readUnit)
766
839
                script.__doc__ = nextDoc
767
840
                script.__name__ = funcName
768
841
                setattr(cls, funcName, script)
769
 
                cls.bindKey(key, scriptName)
 
842
                cls.__gestures["kb:%s" % key] = scriptName
770
843
                scriptName = "previous%s" % scriptSuffix
771
844
                funcName = "script_%s" % scriptName
772
 
                script = lambda self,keyPress: self._quickNavScript(keyPress, nodeType, "previous", prevError, readUnit)
 
845
                script = lambda self,gesture: self._quickNavScript(gesture, nodeType, "previous", prevError, readUnit)
773
846
                script.__doc__ = prevDoc
774
847
                script.__name__ = funcName
775
848
                setattr(cls, funcName, script)
776
 
                cls.bindKey("shift+%s" % key, scriptName)
 
849
                cls.__gestures["kb:shift+%s" % key] = scriptName
777
850
 
778
 
        def script_elementsList(self,keyPress):
779
 
                if self.VBufHandle is None:
780
 
                        return
 
851
        def script_elementsList(self,gesture):
781
852
                # We need this to be a modal dialog, but it mustn't block this script.
782
853
                def run():
783
854
                        gui.mainFrame.prePopup()
805
876
                if reason == self.REASON_QUICKNAV:
806
877
                        return False
807
878
                states = obj.states
808
 
                if controlTypes.STATE_FOCUSABLE not in states or controlTypes.STATE_READONLY in states:
 
879
                if controlTypes.STATE_FOCUSABLE not in states and controlTypes.STATE_FOCUSED not in states:
809
880
                        return False
810
881
                role = obj.role
 
882
                if controlTypes.STATE_READONLY in states and role != controlTypes.ROLE_EDITABLETEXT:
 
883
                        return False
811
884
                if reason == speech.REASON_CARET:
812
885
                        return role == controlTypes.ROLE_EDITABLETEXT or (role == controlTypes.ROLE_DOCUMENT and controlTypes.STATE_EDITABLE in states)
813
 
                if reason == speech.REASON_FOCUS and role in (controlTypes.ROLE_LISTITEM, controlTypes.ROLE_RADIOBUTTON):
 
886
                if reason == speech.REASON_FOCUS and role in (controlTypes.ROLE_LISTITEM, controlTypes.ROLE_RADIOBUTTON, controlTypes.ROLE_TAB):
814
887
                        return True
815
 
                if role in (controlTypes.ROLE_COMBOBOX, controlTypes.ROLE_EDITABLETEXT, controlTypes.ROLE_LIST, controlTypes.ROLE_SLIDER, controlTypes.ROLE_TABCONTROL, controlTypes.ROLE_TAB, controlTypes.ROLE_MENUBAR, controlTypes.ROLE_POPUPMENU, controlTypes.ROLE_MENUITEM, controlTypes.ROLE_TREEVIEW, controlTypes.ROLE_TREEVIEWITEM, controlTypes.ROLE_SPINBUTTON) or controlTypes.STATE_EDITABLE in states:
 
888
                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:
816
889
                        return True
817
890
                return False
818
891
 
819
 
        def event_caretMovementFailed(self, obj, nextHandler, keyPress=None):
820
 
                if not self.passThrough or not keyPress or not config.conf["virtualBuffers"]["autoPassThroughOnCaretMove"]:
 
892
        def event_caretMovementFailed(self, obj, nextHandler, gesture=None):
 
893
                if not self.passThrough or not gesture or not config.conf["virtualBuffers"]["autoPassThroughOnCaretMove"]:
821
894
                        return nextHandler()
822
 
                if keyPress[1] in ("extendedhome", "extendedend"):
 
895
                if gesture.mainKeyName in ("home", "end"):
823
896
                        # Home, end, control+home and control+end should not disable pass through.
824
897
                        return nextHandler()
825
 
                script = self.getScript(keyPress)
 
898
                script = self.getScript(gesture)
826
899
                if not script:
827
900
                        return nextHandler()
828
901
 
830
903
                # Therefore, move the virtual caret to the same edge of the field.
831
904
                info = self.makeTextInfo(textInfos.POSITION_CARET)
832
905
                info.expand(info.UNIT_CONTROLFIELD)
833
 
                if keyPress[1] in ("extendedleft", "extendedup", "extendedprior"):
 
906
                if gesture.mainKeyName in ("extendedleft", "extendedup", "extendedprior"):
834
907
                        info.collapse()
835
908
                else:
836
909
                        info.collapse(end=True)
837
910
                        info.move(textInfos.UNIT_CHARACTER, -1)
838
911
                info.updateCaret()
839
912
 
840
 
                scriptHandler.queueScript(script, keyPress)
 
913
                scriptHandler.queueScript(script, gesture)
841
914
 
842
 
        def script_disablePassThrough(self, keyPress):
 
915
        def script_disablePassThrough(self, gesture):
843
916
                if not self.passThrough or self.disableAutoPassThrough:
844
 
                        return sendKey(keyPress)
 
917
                        return gesture.send()
845
918
                self.passThrough = False
846
919
                self.disableAutoPassThrough = False
847
 
                virtualBufferHandler.reportPassThrough(self)
848
 
        script_disablePassThrough.ignoreVirtualBufferPassThrough = True
 
920
                reportPassThrough(self)
 
921
        script_disablePassThrough.ignoreTreeInterceptorPassThrough = True
849
922
 
850
 
        def script_collapseOrExpandControl(self, keyPress):
851
 
                sendKey(keyPress)
852
 
                if not self.passThrough:
853
 
                        return
854
 
                self.passThrough = False
855
 
                virtualBufferHandler.reportPassThrough(self)
856
 
        script_collapseOrExpandControl.ignoreVirtualBufferPassThrough = True
 
923
        def script_collapseOrExpandControl(self, gesture):
 
924
                if self.passThrough:
 
925
                        gesture.send()
 
926
                        if not self.disableAutoPassThrough:
 
927
                                self.passThrough = False
 
928
                                reportPassThrough(self)
 
929
                else:
 
930
                        oldFocus = api.getFocusObject()
 
931
                        oldFocusStates = oldFocus.states
 
932
                        gesture.send()
 
933
                        if oldFocus.role == controlTypes.ROLE_COMBOBOX and controlTypes.STATE_COLLAPSED in oldFocusStates:
 
934
                                self.passThrough = True
 
935
                                reportPassThrough(self)
 
936
        script_collapseOrExpandControl.ignoreTreeInterceptorPassThrough = True
857
937
 
858
938
        def _tabOverride(self, direction):
859
939
                """Override the tab order if the virtual buffer caret is not within the currently focused node.
866
946
                @return: C{True} if the tab order was overridden, C{False} if not.
867
947
                @rtype: bool
868
948
                """
869
 
                if self.VBufHandle is None:
870
 
                        return False
871
 
 
872
949
                focus = api.getFocusObject()
873
950
                try:
874
951
                        focusInfo = self.makeTextInfo(focus)
876
953
                        return False
877
954
                # We only want to override the tab order if the caret is not within the focused node.
878
955
                caretInfo=self.makeTextInfo(textInfos.POSITION_CARET)
879
 
                # Expand to one character, as isOverlapping() doesn't yield the desired results with collapsed ranges.
880
 
                caretInfo.expand(textInfos.UNIT_CHARACTER)
881
 
                if focusInfo.isOverlapping(caretInfo):
882
 
                        return False
 
956
                #Only check that the caret is within the focus for things that ar not documents
 
957
                #As for documents we should always override
 
958
                if focus.role!=controlTypes.ROLE_DOCUMENT or controlTypes.STATE_EDITABLE in focus.states:
 
959
                        # Expand to one character, as isOverlapping() doesn't yield the desired results with collapsed ranges.
 
960
                        caretInfo.expand(textInfos.UNIT_CHARACTER)
 
961
                        if focusInfo.isOverlapping(caretInfo):
 
962
                                return False
883
963
                # If we reach here, we do want to override tab/shift+tab if possible.
884
964
                # Find the next/previous focusable node.
885
965
                try:
905
985
                        obj.setFocus()
906
986
                return True
907
987
 
908
 
        def script_tab(self, keyPress):
 
988
        def script_tab(self, gesture):
909
989
                if not self._tabOverride("next"):
910
 
                        sendKey(keyPress)
 
990
                        gesture.send()
911
991
 
912
 
        def script_shiftTab(self, keyPress):
 
992
        def script_shiftTab(self, gesture):
913
993
                if not self._tabOverride("previous"):
914
 
                        sendKey(keyPress)
 
994
                        gesture.send()
915
995
 
916
996
        def event_focusEntered(self,obj,nextHandler):
917
997
                if self.passThrough:
933
1013
                @type obj: L{NVDAObjects.NVDAObject}
934
1014
                """
935
1015
 
 
1016
        def _replayFocusEnteredEvents(self):
 
1017
                # We blocked the focusEntered events because we were in browse mode,
 
1018
                # but now that we've switched to focus mode, we need to fire them.
 
1019
                for parent in api.getFocusAncestors()[api.getFocusDifferenceLevel():]:
 
1020
                        try:
 
1021
                                parent.event_focusEntered()
 
1022
                        except:
 
1023
                                log.exception("Error executing focusEntered event: %s" % parent)
 
1024
 
936
1025
        def event_gainFocus(self, obj, nextHandler):
 
1026
                if not self.isReady:
 
1027
                        if self.passThrough:
 
1028
                                nextHandler()
 
1029
                        return
937
1030
                if not self.passThrough and self._lastFocusObj==obj:
938
1031
                        # This was the last non-document node with focus, so don't handle this focus event.
939
1032
                        # Otherwise, if the user switches away and back to this document, the cursor will jump to this node.
940
1033
                        # This is not ideal if the user was positioned over a node which cannot receive focus.
941
1034
                        return
942
 
                if self.VBufHandle is None:
943
 
                        return nextHandler()
944
1035
                if obj==self.rootNVDAObject:
945
1036
                        if self.passThrough:
946
1037
                                return nextHandler()
956
1047
                        # Automatic pass through should be enabled in certain circumstances where this occurs.
957
1048
                        if not self.passThrough and self.shouldPassThrough(obj,reason=speech.REASON_FOCUS):
958
1049
                                self.passThrough=True
959
 
                                virtualBufferHandler.reportPassThrough(self)
 
1050
                                reportPassThrough(self)
 
1051
                                self._replayFocusEnteredEvents()
960
1052
                        return nextHandler()
961
1053
 
962
1054
                #We only want to update the caret and speak the field if we're not in the same one as before
964
1056
                # Expand to one character, as isOverlapping() doesn't treat, for example, (4,4) and (4,5) as overlapping.
965
1057
                caretInfo.expand(textInfos.UNIT_CHARACTER)
966
1058
                if not self._hadFirstGainFocus or not focusInfo.isOverlapping(caretInfo):
967
 
                        # The virtual buffer caret has already been moved inside the focus node.
968
 
                        if not self.passThrough:
 
1059
                        # The virtual buffer caret is not within the focus node.
 
1060
                        oldPassThrough=self.passThrough
 
1061
                        if not oldPassThrough:
969
1062
                                # If pass-through is disabled, cancel speech, as a focus change should cause page reading to stop.
970
1063
                                # 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.
971
1064
                                speech.cancelSpeech()
976
1069
                                # However, we still want to update the speech property cache so that property changes will be spoken properly.
977
1070
                                speech.speakObject(obj,speech.REASON_ONLYCACHE)
978
1071
                        else:
 
1072
                                if not oldPassThrough:
 
1073
                                        self._replayFocusEnteredEvents()
979
1074
                                nextHandler()
980
1075
                        focusInfo.collapse()
981
1076
                        self._set_selection(focusInfo,reason=speech.REASON_FOCUS)
990
1085
 
991
1086
                self._postGainFocus(obj)
992
1087
 
 
1088
        event_gainFocus.ignoreIsReady=True
 
1089
 
993
1090
        def _handleScrollTo(self, obj):
994
1091
                """Handle scrolling the buffer to a given object in response to an event.
995
1092
                Subclasses should call this from an event which indicates that the buffer has scrolled.
1000
1097
                @rtype: bool
1001
1098
                @note: If C{False} is returned, calling events should probably call their nextHandler.
1002
1099
                """
1003
 
                if not self.VBufHandle:
1004
 
                        return False
1005
 
 
1006
1100
                if self.programmaticScrollMayFireEvent and self._lastProgrammaticScrollTime and time.time() - self._lastProgrammaticScrollTime < 0.4:
1007
1101
                        # This event was probably caused by this buffer's call to scrollIntoView().
1008
1102
                        # Therefore, ignore it. Otherwise, the cursor may bounce back to the scroll point.
1141
1235
                info.collapse()
1142
1236
                self.selection = info
1143
1237
 
1144
 
        def script_nextRow(self, keyPress):
 
1238
        def script_nextRow(self, gesture):
1145
1239
                self._tableMovementScriptHelper(axis="row", movement="next")
1146
1240
        script_nextRow.__doc__ = _("moves to the next table row")
1147
1241
 
1148
 
        def script_previousRow(self, keyPress):
 
1242
        def script_previousRow(self, gesture):
1149
1243
                self._tableMovementScriptHelper(axis="row", movement="previous")
1150
1244
        script_previousRow.__doc__ = _("moves to the previous table row")
1151
1245
 
1152
 
        def script_nextColumn(self, keyPress):
 
1246
        def script_nextColumn(self, gesture):
1153
1247
                self._tableMovementScriptHelper(axis="column", movement="next")
1154
1248
        script_nextColumn.__doc__ = _("moves to the next table column")
1155
1249
 
1156
 
        def script_previousColumn(self, keyPress):
 
1250
        def script_previousColumn(self, gesture):
1157
1251
                self._tableMovementScriptHelper(axis="column", movement="previous")
1158
1252
        script_previousColumn.__doc__ = _("moves to the previous table column")
1159
1253
 
1169
1263
                """
1170
1264
                while obj and obj != self.rootNVDAObject:
1171
1265
                        if obj.role in self.APPLICATION_ROLES:
1172
 
                                return False
 
1266
                                return True
1173
1267
                        obj = obj.parent
1174
 
                return True
 
1268
                return False
1175
1269
 
1176
1270
        NOT_LINK_BLOCK_MIN_LEN = 30
1177
1271
        def _iterNotLinkBlock(self, direction="next", offset=-1):
1188
1282
                                yield 0, link2end, link1start
1189
1283
                        link1node, link1start, link1end = link2node, link2start, link2end
1190
1284
 
1191
 
        def _setInitialCaretPos(self):
1192
 
                """Set the initial position of the caret after the buffer has been loaded.
1193
 
                The return value is primarily used so that overriding methods can determine whether they need to set an initial position.
1194
 
                @return: C{True} if an initial position was set.
 
1285
        def _getInitialCaretPos(self):
 
1286
                """Retrieve the initial position of the caret after the buffer has been loaded.
 
1287
                This position, if any, will be passed to L{makeTextInfo}.
 
1288
                Subclasses should extend this method.
 
1289
                @return: The initial position of the caret, C{None} if there isn't one.
 
1290
                @rtype: TextInfo position
 
1291
                """
 
1292
                if self.shouldRememberCaretPositionAcrossLoads:
 
1293
                        try:
 
1294
                                return self.rootNVDAObject.appModule._vbufRememberedCaretPositions[self.documentConstantIdentifier]
 
1295
                        except KeyError:
 
1296
                                pass
 
1297
                return None
 
1298
 
 
1299
        def _get_documentConstantIdentifier(self):
 
1300
                """Get the constant identifier for this document.
 
1301
                This identifier should uniquely identify all instances (not just one instance) of a document for at least the current session of the hosting application.
 
1302
                Generally, the document URL should be used.
 
1303
                @return: The constant identifier for this document, C{None} if there is none.
 
1304
                """
 
1305
                return None
 
1306
 
 
1307
        def _get_shouldRememberCaretPositionAcrossLoads(self):
 
1308
                """Specifies whether the position of the caret should be remembered when this document is loaded again.
 
1309
                This is useful when the browser remembers the scroll position for the document,
 
1310
                but does not communicate this information via APIs.
 
1311
                The remembered caret position is associated with this document using L{documentConstantIdentifier}.
 
1312
                @return: C{True} if the caret position should be remembered, C{False} if not.
1195
1313
                @rtype: bool
1196
1314
                """
1197
 
                return False
 
1315
                docConstId = self.documentConstantIdentifier
 
1316
                # Return True if the URL indicates that this is probably a web browser document.
 
1317
                # We do this check because we don't want to remember caret positions for email messages, etc.
 
1318
                return isinstance(docConstId, basestring) and docConstId.split("://", 1)[0] in ("http", "https", "ftp", "ftps", "file")
1198
1319
 
1199
 
[VirtualBuffer.bindKey(keyName,scriptName) for keyName,scriptName in (
1200
 
        ("Return","activatePosition"),
1201
 
        ("Space","activatePosition"),
1202
 
        ("NVDA+f5","refreshBuffer"),
1203
 
        ("NVDA+v","toggleScreenLayout"),
1204
 
        ("NVDA+f7","elementsList"),
1205
 
        ("escape","disablePassThrough"),
1206
 
        ("alt+extendedUp","collapseOrExpandControl"),
1207
 
        ("alt+extendedDown","collapseOrExpandControl"),
1208
 
        ("tab", "tab"),
1209
 
        ("shift+tab", "shiftTab"),
1210
 
        ("control+alt+extendedDown", "nextRow"),
1211
 
        ("control+alt+extendedUp", "previousRow"),
1212
 
        ("control+alt+extendedRight", "nextColumn"),
1213
 
        ("control+alt+extendedLeft", "previousColumn"),
1214
 
)]
 
1320
        __gestures = {
 
1321
                "kb:enter": "activatePosition",
 
1322
                "kb:space": "activatePosition",
 
1323
                "kb:NVDA+f5": "refreshBuffer",
 
1324
                "kb:NVDA+v": "toggleScreenLayout",
 
1325
                "kb:NVDA+f7": "elementsList",
 
1326
                "kb:escape": "disablePassThrough",
 
1327
                "kb:alt+upArrow": "collapseOrExpandControl",
 
1328
                "kb:alt+downArrow": "collapseOrExpandControl",
 
1329
                "kb:tab": "tab",
 
1330
                "kb:shift+tab": "shiftTab",
 
1331
                "kb:control+alt+downArrow": "nextRow",
 
1332
                "kb:control+alt+upArrow": "previousRow",
 
1333
                "kb:control+alt+rightArrow": "nextColumn",
 
1334
                "kb:control+alt+leftArrow": "previousColumn",
 
1335
        }
1215
1336
 
1216
1337
# Add quick navigation scripts.
1217
1338
qn = VirtualBuffer.addQuickNav
1268
1389
qn("embeddedObject", key="o", nextDoc=_("moves to the next embedded object"), nextError=_("no next embedded object"),
1269
1390
        prevDoc=_("moves to the previous embedded object"), prevError=_("no previous embedded object"))
1270
1391
del qn
 
1392
 
 
1393
def reportPassThrough(virtualBuffer):
 
1394
        """Reports the virtual buffer pass through mode if it has changed.
 
1395
        @param virtualBuffer: The current virtual buffer.
 
1396
        @type virtualBuffer: L{virtualBuffers.VirtualBuffer}
 
1397
        """
 
1398
        if virtualBuffer.passThrough != reportPassThrough.last:
 
1399
                if config.conf["virtualBuffers"]["passThroughAudioIndication"]:
 
1400
                        sound = r"waves\focusMode.wav" if virtualBuffer.passThrough else r"waves\browseMode.wav"
 
1401
                        nvwave.playWaveFile(sound)
 
1402
                else:
 
1403
                        speech.speakMessage(_("focus mode") if virtualBuffer.passThrough else _("browse mode"))
 
1404
                reportPassThrough.last = virtualBuffer.passThrough
 
1405
reportPassThrough.last = False