1
2
3
4
5
6
7 import winUser
8 import textInfos
9 import controlTypes
10 import eventHandler
11 from NVDAObjects import NVDAObject
12 from editableText import EditableText
13 from treeInterceptorHandler import TreeInterceptor
14 import speech
15 import braille
16 from NVDAObjects import behaviors
17
18 -class CompoundTextInfo(textInfos.TextInfo):
19
20 - def _getObjectPosition(self, obj):
21 indexes = []
22 rootObj = self.obj.rootNVDAObject
23 while obj and obj != rootObj:
24 indexes.insert(0, obj.indexInParent)
25 obj = obj.parent
26 return indexes
27
28 - def compareEndPoints(self, other, which):
29 if which in ("startToStart", "startToEnd"):
30 selfTi = self._start
31 selfObj = self._startObj
32 else:
33 selfTi = self._end
34 selfObj = self._endObj
35 if which in ("startToStart", "endToStart"):
36 otherTi = other._start
37 otherObj = other._startObj
38 else:
39 otherTi = other._end
40 otherObj = other._endObj
41
42 if selfObj == otherObj:
43
44 return selfTi.compareEndPoints(otherTi, which)
45
46
47 return cmp(self._getObjectPosition(selfObj), other._getObjectPosition(otherObj))
48
50 if self._start.isCollapsed and self._startObj != self._endObj:
51
52
53
54 obj = self._startObj.flowsTo
55 if obj:
56 self._startObj = obj
57 self._start = obj.makeTextInfo(textInfos.POSITION_FIRST)
58
59 if self._startObj == self._endObj:
60
61 self._start.setEndPoint(self._end, "endToEnd")
62 self._end = self._start
63 self._endObj = self._startObj
64 else:
65
66 self._start.setEndPoint(self._startObj.makeTextInfo(textInfos.POSITION_ALL), "endToEnd")
67
68 self._end.setEndPoint(self._endObj.makeTextInfo(textInfos.POSITION_ALL), "startToStart")
69
70 - def setEndPoint(self, other, which):
71 if which == "startToStart":
72 self._start = other._start.copy()
73 self._startObj = other._startObj
74 elif which == "startToEnd":
75 self._start = other._end.copy()
76 self._start.setEndPoint(other._end, which)
77 self._startObj = other._endObj
78 elif which == "endToStart":
79 self._end = other._start.copy()
80 self._end.setEndPoint(other._start, which)
81 self._endObj = other._startObj
82 elif which == "endToEnd":
83 self._end = other._end.copy()
84 self._endObj = other._endObj
85 else:
86 raise ValueError("which=%s" % which)
87 self._normalizeStartAndEnd()
88
89 - def collapse(self, end=False):
90 if end:
91 if self._end.compareEndPoints(self._endObj.makeTextInfo(textInfos.POSITION_ALL), "endToEnd") == 0:
92
93
94
95
96 obj = self._endObj.flowsTo
97 if obj:
98 self._endObj = obj
99 self._end = obj.makeTextInfo(textInfos.POSITION_FIRST)
100 else:
101
102 self._end.collapse(end=True)
103 else:
104
105 self._end.collapse(end=True)
106 self._start = self._end
107 self._startObj = self._endObj
108
109 else:
110 self._start.collapse()
111 self._end = self._start
112 self._endObj = self._startObj
113
115 return self.__class__(self.obj, self)
116
117 - def updateCaret(self):
118 self._startObj.setFocus()
119 self._start.updateCaret()
120
121 - def updateSelection(self):
122 self._startObj.setFocus()
123 self._start.updateSelection()
124 if self._end is not self._start:
125 self._end.updateSelection()
126
127 - def _get_bookmark(self):
129
131 return self._startObj
132
134 return self._start.pointAtStart
135
136 - def _getControlFieldForObject(self, obj, ignoreEditableText=True):
137 role = obj.role
138 if ignoreEditableText and role in (controlTypes.ROLE_PARAGRAPH, controlTypes.ROLE_EDITABLETEXT):
139
140 return None
141 field = textInfos.ControlField()
142 field["role"] = obj.role
143 states = obj.states
144
145 states.discard(controlTypes.STATE_EDITABLE)
146 states.discard(controlTypes.STATE_MULTILINE)
147 states.discard(controlTypes.STATE_FOCUSED)
148 field["states"] = states
149 field["name"] = obj.name
150 field["_childcount"] = obj.childCount
151 field["level"] = obj.positionInfo.get("level")
152 if role == controlTypes.ROLE_TABLE:
153 field["table-id"] = 1
154 field["table-rowcount"] = obj.rowCount
155 field["table-columncount"] = obj.columnCount
156 if role in (controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLECOLUMNHEADER, controlTypes.ROLE_TABLEROWHEADER):
157 field["table-id"] = 1
158 field["table-rownumber"] = obj.rowNumber
159 field["table-columnnumber"] = obj.columnNumber
160 return field
161
162 - def _iterTextWithEmbeddedObjects(self, text, ti, fieldStart, textLength=None):
163 if textLength is None:
164 textLength = len(text)
165 chunkStart = 0
166 while chunkStart < textLength:
167 try:
168 chunkEnd = text.index(u"\uFFFC", chunkStart)
169 except ValueError:
170 yield text[chunkStart:]
171 break
172 if chunkStart != chunkEnd:
173 yield text[chunkStart:chunkEnd]
174 yield ti.getEmbeddedObject(fieldStart + chunkEnd)
175 chunkStart = chunkEnd + 1
176
177 - def __eq__(self, other):
178 return self._start == other._start and self._startObj == other._startObj and self._end == other._end and self._endObj == other._endObj
179
180 - def __ne__(self, other):
181 return not self == other
182
183 -class TreeCompoundTextInfo(CompoundTextInfo):
184
185 SINGLE_TEXTINFO_UNITS = (textInfos.UNIT_CHARACTER, textInfos.UNIT_WORD, textInfos.UNIT_LINE, textInfos.UNIT_SENTENCE, textInfos.UNIT_PARAGRAPH)
186
187 - def __init__(self, obj, position):
188 super(TreeCompoundTextInfo, self).__init__(obj, position)
189 rootObj = obj.rootNVDAObject
190 if isinstance(position, NVDAObject):
191
192 position = textInfos.POSITION_CARET
193 if isinstance(position, self.__class__):
194 self._start = position._start.copy()
195 self._startObj = position._startObj
196 if position._end is position._start:
197 self._end = self._start
198 else:
199 self._end = position._end.copy()
200 self._endObj = position._endObj
201 elif position == textInfos.POSITION_FIRST:
202 self._startObj = self._endObj = self._findContentDescendant(rootObj.firstChild)
203 self._start = self._end = self._startObj.makeTextInfo(position)
204 elif position == textInfos.POSITION_LAST:
205 self._startObj = self._endObj = self._findContentDescendant(rootObj.lastChild)
206 self._start = self._end = self._startObj.makeTextInfo(position)
207 elif position == textInfos.POSITION_ALL:
208 self._startObj = self._findContentDescendant(rootObj.firstChild)
209 self._endObj = self._findContentDescendant(rootObj.lastChild)
210 self._start = self._startObj.makeTextInfo(position)
211 self._end = self._endObj.makeTextInfo(position)
212 elif position == textInfos.POSITION_CARET:
213 self._startObj = self._endObj = obj.caretObject
214 self._start = self._end = self._startObj.makeTextInfo(position)
215 elif position == textInfos.POSITION_SELECTION:
216
217 self._startObj = self._endObj = self.obj.caretObject
218
219 tempObj = self._startObj
220 while tempObj and controlTypes.STATE_SELECTED in tempObj.states:
221 self._startObj = tempObj
222 tempObj = tempObj.flowsFrom
223 tempObj = self._endObj
224 while tempObj and controlTypes.STATE_SELECTED in tempObj.states:
225 self._endObj = tempObj
226 tempObj = tempObj.flowsTo
227 self._start = self._startObj.makeTextInfo(position)
228 if self._startObj is self._endObj:
229 self._end = self._start
230 else:
231 self._end = self._endObj.makeTextInfo(position)
232 else:
233 raise NotImplementedError
234
236 while obj and controlTypes.STATE_FOCUSABLE not in obj.states:
237 obj = obj.firstChild
238 return obj
239
240 - def _getTextInfos(self):
241 yield self._start
242 if self._startObj == self._endObj:
243 return
244 obj = self._startObj.flowsTo
245 while obj and obj != self._endObj:
246 yield obj.makeTextInfo(textInfos.POSITION_ALL)
247 obj = obj.flowsTo
248 yield self._end
249
250 - def _get_text(self):
251 return "".join(ti.text for ti in self._getTextInfos())
252
253 - def getTextWithFields(self, formatConfig=None):
254
255 fields = []
256 rootObj = self.obj.rootNVDAObject
257 obj = self._startObj
258 while obj and obj != rootObj:
259 field = self._getControlFieldForObject(obj)
260 if field:
261 fields.insert(0, textInfos.FieldCommand("controlStart", field))
262 obj = obj.parent
263
264 for ti in self._getTextInfos():
265 fieldStart = 0
266 for field in ti.getTextWithFields(formatConfig=formatConfig):
267 if isinstance(field, basestring):
268 textLength = len(field)
269 for chunk in self._iterTextWithEmbeddedObjects(field, ti, fieldStart, textLength=textLength):
270 if isinstance(chunk, basestring):
271 fields.append(chunk)
272 else:
273 controlField = self._getControlFieldForObject(chunk, ignoreEditableText=False)
274 controlField["alwaysReportName"] = True
275 fields.extend((textInfos.FieldCommand("controlStart", controlField),
276 u"\uFFFC",
277 textInfos.FieldCommand("controlEnd", None)))
278 fieldStart += textLength
279
280 else:
281 fields.append(field)
282 return fields
283
284 - def expand(self, unit):
285 if unit == textInfos.UNIT_READINGCHUNK:
286 unit = textInfos.UNIT_LINE
287
288 if unit in self.SINGLE_TEXTINFO_UNITS:
289
290 self._start.expand(unit)
291 self._end = self._start
292 self._endObj = self._startObj
293 else:
294 raise NotImplementedError
295
296 - def move(self, unit, direction, endPoint=None):
297 if direction == 0:
298 return 0
299
300 if unit == textInfos.UNIT_READINGCHUNK:
301 unit = textInfos.UNIT_LINE
302
303 if unit not in self.SINGLE_TEXTINFO_UNITS:
304 raise NotImplementedError
305
306 if not endPoint or endPoint == "start":
307 moveTi = self._start
308 moveObj = self._startObj
309 elif endPoint == "end":
310 moveTi = self._end
311 moveObj = self._endObj
312
313 goPrevious = direction < 0
314 remainingMovement = direction
315 count0MoveAs = 0
316 while True:
317 movement = moveTi.move(unit, remainingMovement, endPoint=endPoint)
318 if movement == 0 and count0MoveAs != 0:
319 movement = count0MoveAs
320 remainingMovement -= movement
321 count0MoveAs = 0
322 if remainingMovement == 0:
323
324 break
325
326
327 tempObj = moveObj.flowsFrom if goPrevious else moveObj.flowsTo
328 if tempObj:
329 moveObj = tempObj
330 else:
331 break
332 if goPrevious:
333 moveTi = moveObj.makeTextInfo(textInfos.POSITION_ALL)
334 moveTi.collapse(end=True)
335
336
337
338 count0MoveAs = -1
339 else:
340 moveTi = moveObj.makeTextInfo(textInfos.POSITION_FIRST)
341 if endPoint == "end":
342
343
344
345
346 count0MoveAs = 1
347 else:
348
349 remainingMovement -= 1
350 if remainingMovement == 0:
351
352 break
353
354 if not endPoint or endPoint == "start":
355 self._start = moveTi
356 self._startObj = moveObj
357 if not endPoint or endPoint == "end":
358 self._end = moveTi
359 self._endObj = moveObj
360 self._normalizeStartAndEnd()
361
362 return direction - remainingMovement
363
365 TextInfo = TreeCompoundTextInfo
366
369
371 root = self.rootNVDAObject
372 return winUser.isWindow(root.windowHandle)
373
375 root = self.rootNVDAObject
376 while obj:
377 if obj.windowHandle != root.windowHandle:
378 return False
379 if obj == root:
380 return True
381 obj = obj.parent
382 return False
383
384 - def makeTextInfo(self, position):
385 return self.TextInfo(self, position)
386
389
404
408
414
417
420
423
426
429