1
2
3
4
5
6
7 """Common support for editable text.
8 @note: If you want editable text functionality for an NVDAObject,
9 you should use the EditableText classes in L{NVDAObjects.behaviors}.
10 """
11
12 import time
13 import api
14 from baseObject import ScriptableObject
15 import braille
16 import speech
17 import config
18 import eventHandler
19 from scriptHandler import isScriptWaiting
20 import textInfos
21
22 -class EditableText(ScriptableObject):
23 """Provides scripts to report appropriately when moving the caret in editable text fields.
24 This does not handle the selection change keys.
25 To have selection changes reported, the object must notify of selection changes.
26 If the object supports selection but does not notify of selection changes, L{EditableTextWithoutAutoSelectDetection} should be used instead.
27
28 If the object notifies of selection changes, the following should be done:
29 * When the object gains focus, L{initAutoSelectDetection} must be called.
30 * When the object notifies of a possible selection change, L{detectPossibleSelectionChange} must be called.
31 * Optionally, if the object notifies of changes to its content, L{hasContentChangedSinceLastSelection} should be set to C{True}.
32 @ivar hasContentChangedSinceLastSelection: Whether the content has changed since the last selection occurred.
33 @type hasContentChangedSinceLastSelection: bool
34 """
35
36
37 shouldFireCaretMovementFailedEvents = False
38
39 - def _hasCaretMoved(self, bookmark, retryInterval=0.01, timeout=0.03):
40 """
41 Waits for the caret to move, for a timeout to elapse, or for a new focus event or script to be queued.
42 @param bookmark: a bookmark representing the position of the caret before it was instructed to move
43 @type bookmark: bookmark
44 @param retryInterval: the interval of time in seconds this method should wait before checking the caret each time.
45 @type retryInterval: float
46 @param timeout: the over all amount of time in seconds the method should wait before giving up completely.
47 @type timeout: float
48 @return: a tuple containing a boolean denoting whether this method timed out, and a TextInfo representing the old or updated caret position or None if interupted by a script or focus event.
49 @rtype: tuple
50 """
51 elapsed = 0
52 newInfo=None
53 while elapsed < timeout:
54 if isScriptWaiting():
55 return (False,None)
56 api.processPendingEvents(processEventQueue=False)
57 if eventHandler.isPendingEvents("gainFocus"):
58 return (True,None)
59
60 try:
61 newInfo = self.makeTextInfo(textInfos.POSITION_CARET)
62 newBookmark = newInfo.bookmark
63 except (RuntimeError,NotImplementedError):
64 newInfo=None
65 else:
66 if newBookmark!=bookmark:
67 return (True,newInfo)
68 time.sleep(retryInterval)
69 elapsed += retryInterval
70 return (False,newInfo)
71
72 - def _caretScriptPostMovedHelper(self, speakUnit, info=None):
73 if isScriptWaiting():
74 return
75 if not info:
76 try:
77 info = self.makeTextInfo(textInfos.POSITION_CARET)
78 except:
79 return
80 if config.conf["reviewCursor"]["followCaret"] and api.getNavigatorObject() is self:
81 api.setReviewPosition(info)
82 if speakUnit:
83 info.expand(speakUnit)
84 speech.speakTextInfo(info, unit=speakUnit, reason=speech.REASON_CARET)
85
86 - def _caretMovementScriptHelper(self, gesture, unit):
87 try:
88 info=self.makeTextInfo(textInfos.POSITION_CARET)
89 except:
90 gesture.send()
91 return
92 bookmark=info.bookmark
93 gesture.send()
94 caretMoved,newInfo=self._hasCaretMoved(bookmark)
95 if not caretMoved and self.shouldFireCaretMovementFailedEvents:
96 eventHandler.executeEvent("caretMovementFailed", self, gesture=gesture)
97 self._caretScriptPostMovedHelper(unit,newInfo)
98
101
104
105 - def script_caret_moveByWord(self,gesture):
107
110
111 - def _backspaceScriptHelper(self,unit,gesture):
112 try:
113 oldInfo=self.makeTextInfo(textInfos.POSITION_CARET)
114 except:
115 gesture.send()
116 return
117 oldBookmark=oldInfo.bookmark
118 testInfo=oldInfo.copy()
119 res=testInfo.move(textInfos.UNIT_CHARACTER,-1)
120 if res<0:
121 testInfo.expand(unit)
122 delChunk=testInfo.text
123 else:
124 delChunk=""
125 gesture.send()
126 caretMoved,newInfo=self._hasCaretMoved(oldBookmark)
127 if not caretMoved:
128 return
129 if len(delChunk)>1:
130 speech.speakMessage(delChunk)
131 else:
132 speech.speakSpelling(delChunk)
133 self._caretScriptPostMovedHelper(None,newInfo)
134
137
140
141 - def script_caret_delete(self,gesture):
142 try:
143 info=self.makeTextInfo(textInfos.POSITION_CARET)
144 except:
145 gesture.send()
146 return
147 bookmark=info.bookmark
148 gesture.send()
149
150 caretMoved,newInfo=self._hasCaretMoved(bookmark)
151 self._caretScriptPostMovedHelper(textInfos.UNIT_CHARACTER,newInfo)
152 braille.handler.handleCaretMove(self)
153
154 __gestures = {
155 "kb:upArrow": "caret_moveByLine",
156 "kb:downArrow": "caret_moveByLine",
157 "kb:leftArrow": "caret_moveByCharacter",
158 "kb:rightArrow": "caret_moveByCharacter",
159 "kb:pageUp": "caret_moveByLine",
160 "kb:pageDown": "caret_moveByLine",
161 "kb:control+leftArrow": "caret_moveByWord",
162 "kb:control+rightArrow": "caret_moveByWord",
163 "kb:control+upArrow": "caret_moveByParagraph",
164 "kb:control+downArrow": "caret_moveByParagraph",
165 "kb:home": "caret_moveByCharacter",
166 "kb:end": "caret_moveByCharacter",
167 "kb:control+home": "caret_moveByLine",
168 "kb:control+end": "caret_moveByLine",
169 "kb:delete": "caret_delete",
170 "kb:numpadDelete": "caret_delete",
171 "kb:backspace": "caret_backspaceCharacter",
172 "kb:control+backspace": "caret_backspaceWord",
173 }
174
176 """Initialise automatic detection of selection changes.
177 This should be called when the object gains focus.
178 """
179 try:
180 self._lastSelectionPos=self.makeTextInfo(textInfos.POSITION_SELECTION)
181 except:
182 self._lastSelectionPos=None
183 self.hasContentChangedSinceLastSelection=False
184
186 """Detects if the selection has been changed, and if so it speaks the change.
187 """
188 try:
189 newInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
190 except:
191
192 return
193 oldInfo=getattr(self,'_lastSelectionPos',None)
194 self._lastSelectionPos=newInfo.copy()
195 if not oldInfo:
196
197 return
198 hasContentChanged=getattr(self,'hasContentChangedSinceLastSelection',False)
199 self.hasContentChangedSinceLastSelection=False
200 speech.speakSelectionChange(oldInfo,newInfo,generalize=hasContentChanged)
201
203 """In addition to L{EditableText}, provides scripts to report appropriately when the selection changes.
204 This should be used when an object does not notify of selection changes.
205 """
206
208 try:
209 oldInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
210 except:
211 gesture.send()
212 return
213 gesture.send()
214 if isScriptWaiting() or eventHandler.isPendingEvents("gainFocus"):
215 return
216 api.processPendingEvents(processEventQueue=False)
217 try:
218 newInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
219 except:
220 return
221 speech.speakSelectionChange(oldInfo,newInfo)
222
223 __changeSelectionGestures = (
224 "kb:shift+upArrow",
225 "kb:shift+downArrow",
226 "kb:shift+leftArrow",
227 "kb:shift+rightArrow",
228 "kb:shift+pageUp",
229 "kb:shift+pageDown",
230 "kb:shift+control+leftArrow",
231 "kb:shift+control+rightArrow",
232 "kb:shift+control+upArrow",
233 "kb:shift+control+downArrow",
234 "kb:shift+home",
235 "kb:shift+end",
236 "kb:shift+control+home",
237 "kb:shift+control+end",
238 "kb:control+a",
239 )
240
241 - def initClass(self):
242 for gesture in self.__changeSelectionGestures:
243 self.bindGesture(gesture, "caret_changeSelection")
244