2
#A part of NonVisual Desktop Access (NVDA)
3
#This file is covered by the GNU General Public License.
4
#See the file COPYING for more details.
5
#Copyright (C) 2006-2010 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>
7
"""Common support for editable text.
12
from baseObject import ScriptableObject
17
from scriptHandler import isScriptWaiting
20
class EditableText(ScriptableObject):
21
"""Provides scripts to report appropriately when moving the caret in editable text fields.
22
This does not handle the selection change keys.
23
To have selection changes reported, the object must notify of selection changes.
24
If the object supports selection but does not notify of selection changes, L{EditableTextWithoutAutoSelectDetection} should be used instead.
26
If the object notifies of selection changes, the following should be done:
27
* When the object gains focus, L{initAutoSelectDetection} must be called.
28
* When the object notifies of a possible selection change, L{detectPossibleSelectionChange} must be called.
29
* Optionally, if the object notifies of changes to its content, L{hasContentChangedSinceLastSelection} should be set to C{True}.
30
@ivar hasContentChangedSinceLastSelection: Whether the content has changed since the last selection occurred.
31
@type hasContentChangedSinceLastSelection: bool
34
#: Whether to fire caretMovementFailed events when the caret doesn't move in response to a caret movement key.
35
shouldFireCaretMovementFailedEvents = False
37
def _hasCaretMoved(self, bookmark, retryInterval=0.01, timeout=0.03):
39
while elapsed < timeout:
42
api.processPendingEvents(processEventQueue=False)
43
if eventHandler.isPendingEvents("gainFocus"):
45
#The caret may stop working as the focus jumps, we want to stay in the while loop though
47
newBookmark = self.makeTextInfo(textInfos.POSITION_CARET).bookmark
48
except (RuntimeError,NotImplementedError):
51
if newBookmark!=bookmark:
53
time.sleep(retryInterval)
54
elapsed += retryInterval
57
def _caretScriptPostMovedHelper(self, speakUnit):
61
info = self.makeTextInfo(textInfos.POSITION_CARET)
64
if config.conf["reviewCursor"]["followCaret"] and api.getNavigatorObject() is self:
65
api.setReviewPosition(info.copy())
67
info.expand(speakUnit)
68
speech.speakTextInfo(info, unit=speakUnit, reason=speech.REASON_CARET)
70
def _caretMovementScriptHelper(self, gesture, unit):
72
info=self.makeTextInfo(textInfos.POSITION_CARET)
76
bookmark=info.bookmark
78
if not self._hasCaretMoved(bookmark) and self.shouldFireCaretMovementFailedEvents:
79
eventHandler.executeEvent("caretMovementFailed", self, gesture=gesture)
80
self._caretScriptPostMovedHelper(unit)
82
def script_caret_moveByLine(self,gesture):
83
self._caretMovementScriptHelper(gesture, textInfos.UNIT_LINE)
85
def script_caret_moveByCharacter(self,gesture):
86
self._caretMovementScriptHelper(gesture, textInfos.UNIT_CHARACTER)
88
def script_caret_moveByWord(self,gesture):
89
self._caretMovementScriptHelper(gesture, textInfos.UNIT_WORD)
91
def script_caret_moveByParagraph(self,gesture):
92
self._caretMovementScriptHelper(gesture, textInfos.UNIT_PARAGRAPH)
94
def _backspaceScriptHelper(self,unit,gesture):
96
oldInfo=self.makeTextInfo(textInfos.POSITION_CARET)
100
oldBookmark=oldInfo.bookmark
101
testInfo=oldInfo.copy()
102
res=testInfo.move(textInfos.UNIT_CHARACTER,-1)
104
testInfo.expand(unit)
105
delChunk=testInfo.text
109
if not self._hasCaretMoved(oldBookmark):
112
speech.speakMessage(delChunk)
114
speech.speakSpelling(delChunk)
115
self._caretScriptPostMovedHelper(None)
117
def script_caret_backspaceCharacter(self,gesture):
118
self._backspaceScriptHelper(textInfos.UNIT_CHARACTER,gesture)
120
def script_caret_backspaceWord(self,gesture):
121
self._backspaceScriptHelper(textInfos.UNIT_WORD,gesture)
123
def script_caret_delete(self,gesture):
125
info=self.makeTextInfo(textInfos.POSITION_CARET)
129
bookmark=info.bookmark
131
# We'll try waiting for the caret to move, but we don't care if it doesn't.
132
self._hasCaretMoved(bookmark)
133
self._caretScriptPostMovedHelper(textInfos.UNIT_CHARACTER)
134
braille.handler.handleCaretMove(self)
137
"kb:upArrow": "caret_moveByLine",
138
"kb:downArrow": "caret_moveByLine",
139
"kb:leftArrow": "caret_moveByCharacter",
140
"kb:rightArrow": "caret_moveByCharacter",
141
"kb:pageUp": "caret_moveByLine",
142
"kb:pageDown": "caret_moveByLine",
143
"kb:control+leftArrow": "caret_moveByWord",
144
"kb:control+rightArrow": "caret_moveByWord",
145
"kb:control+upArrow": "caret_moveByParagraph",
146
"kb:control+downArrow": "caret_moveByParagraph",
147
"kb:home": "caret_moveByCharacter",
148
"kb:end": "caret_moveByCharacter",
149
"kb:control+home": "caret_moveByLine",
150
"kb:control+end": "caret_moveByLine",
151
"kb:delete": "caret_delete",
152
"kb:numpadDelete": "caret_delete",
153
"kb:backspace": "caret_backspaceCharacter",
154
"kb:control+backspace": "caret_backspaceWord",
157
def initAutoSelectDetection(self):
158
"""Initialise automatic detection of selection changes.
159
This should be called when the object gains focus.
162
self._lastSelectionPos=self.makeTextInfo(textInfos.POSITION_SELECTION)
164
self._lastSelectionPos=None
165
self.hasContentChangedSinceLastSelection=False
167
def detectPossibleSelectionChange(self):
168
"""Detects if the selection has been changed, and if so it speaks the change.
171
newInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
173
# Just leave the old selection, which is usually better than nothing.
175
oldInfo=getattr(self,'_lastSelectionPos',None)
176
self._lastSelectionPos=newInfo.copy()
178
# There's nothing we can do, but at least the last selection will be right next time.
180
hasContentChanged=getattr(self,'hasContentChangedSinceLastSelection',False)
181
self.hasContentChangedSinceLastSelection=False
182
speech.speakSelectionChange(oldInfo,newInfo,generalize=hasContentChanged)
184
class EditableTextWithoutAutoSelectDetection(EditableText):
185
"""In addition to L{EditableText}, provides scripts to report appropriately when the selection changes.
186
This should be used when an object does not notify of selection changes.
189
def script_caret_changeSelection(self,gesture):
191
oldInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
196
if isScriptWaiting() or eventHandler.isPendingEvents("gainFocus"):
198
api.processPendingEvents(processEventQueue=False)
200
newInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
203
speech.speakSelectionChange(oldInfo,newInfo)
205
__changeSelectionGestures = (
207
"kb:shift+downArrow",
208
"kb:shift+leftArrow",
209
"kb:shift+rightArrow",
212
"kb:shift+control+leftArrow",
213
"kb:shift+control+rightArrow",
214
"kb:shift+control+upArrow",
215
"kb:shift+control+downArrow",
218
"kb:shift+control+home",
219
"kb:shift+control+end",
224
for gesture in self.__changeSelectionGestures:
225
self.bindGesture(gesture, "caret_changeSelection")