1
2
3
4
5
6
7 import weakref
8 import re
9 import baseObject
10 import config
11 import speech
12 import controlTypes
13
14 """Framework for accessing text content in widgets.
15 The core component of this framework is the L{TextInfo} class.
16 In order to access text content for a widget, a L{TextInfo} implementation is required.
17 A default implementation, L{NVDAObjects.NVDAObjectTextInfo}, is used to enable text review of information about a widget which does not have or support text content.
18 """
19
21 """Provides information about a piece of text."""
22
25
27 """Provides information about a control which encompasses text.
28 For example, a piece of text might be contained within a table, button, form, etc.
29 This field contains information about such a control, such as its role, name and description.
30 """
31
32
33 PRESCAT_SINGLELINE = "singleLine"
34
35 PRESCAT_MARKER = "marker"
36
37 PRESCAT_CONTAINER = "container"
38
39 PRESCAT_LAYOUT = None
40
42 role = self.get("role", controlTypes.ROLE_UNKNOWN)
43 states = self.get("states", set())
44
45
46 if not formatConfig["includeLayoutTables"] and role in (controlTypes.ROLE_TABLE, controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLEROWHEADER, controlTypes.ROLE_TABLECOLUMNHEADER):
47
48
49 if role == controlTypes.ROLE_TABLE:
50
51 table = self
52 else:
53
54 for anc in reversed(ancestors):
55 if anc.get("role") == controlTypes.ROLE_TABLE:
56 table = anc
57 break
58 else:
59 table = None
60 if table and table.get("table-layout", None):
61 return self.PRESCAT_LAYOUT
62 if reason in (speech.REASON_CARET, speech.REASON_SAYALL, speech.REASON_FOCUS) and (
63 (role == controlTypes.ROLE_LINK and not formatConfig["reportLinks"]) or
64 (role == controlTypes.ROLE_HEADING and not formatConfig["reportHeadings"]) or
65 (role == controlTypes.ROLE_BLOCKQUOTE and not formatConfig["reportBlockQuotes"]) or
66 (role in (controlTypes.ROLE_TABLE, controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLEROWHEADER, controlTypes.ROLE_TABLECOLUMNHEADER) and not formatConfig["reportTables"]) or
67 (role in (controlTypes.ROLE_LIST, controlTypes.ROLE_LISTITEM) and controlTypes.STATE_READONLY in states and not formatConfig["reportLists"])
68 ):
69
70 return self.PRESCAT_LAYOUT
71
72 if (
73 role in (controlTypes.ROLE_LINK, controlTypes.ROLE_HEADING, controlTypes.ROLE_BUTTON, controlTypes.ROLE_RADIOBUTTON, controlTypes.ROLE_CHECKBOX, controlTypes.ROLE_GRAPHIC, controlTypes.ROLE_MENUITEM, controlTypes.ROLE_TAB, controlTypes.ROLE_COMBOBOX, controlTypes.ROLE_SLIDER, controlTypes.ROLE_SPINBUTTON, controlTypes.ROLE_COMBOBOX, controlTypes.ROLE_PROGRESSBAR, controlTypes.ROLE_TOGGLEBUTTON)
74 or (role == controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_MULTILINE not in states and (controlTypes.STATE_READONLY not in states or controlTypes.STATE_FOCUSABLE in states))
75 or (role == controlTypes.ROLE_LIST and controlTypes.STATE_READONLY not in states)
76 ):
77 return self.PRESCAT_SINGLELINE
78 elif role in (controlTypes.ROLE_SEPARATOR, controlTypes.ROLE_EMBEDDEDOBJECT, controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLECOLUMNHEADER, controlTypes.ROLE_TABLEROWHEADER):
79 return self.PRESCAT_MARKER
80 elif (
81 role in (controlTypes.ROLE_BLOCKQUOTE, controlTypes.ROLE_FRAME, controlTypes.ROLE_INTERNALFRAME, controlTypes.ROLE_TOOLBAR, controlTypes.ROLE_MENUBAR, controlTypes.ROLE_POPUPMENU, controlTypes.ROLE_TABLE)
82 or (role == controlTypes.ROLE_EDITABLETEXT and (controlTypes.STATE_READONLY not in states or controlTypes.STATE_FOCUSABLE in states) and controlTypes.STATE_MULTILINE in states)
83 or (role == controlTypes.ROLE_LIST and controlTypes.STATE_READONLY in states)
84 or (role == controlTypes.ROLE_DOCUMENT and controlTypes.STATE_EDITABLE in states)
85 ):
86 return self.PRESCAT_CONTAINER
87
88 return self.PRESCAT_LAYOUT
89
91 """A command indicating a L{Field} in a sequence of text and fields.
92 When retrieving text with its associated fields, a L{TextInfo} provides a sequence of text strings and L{FieldCommand}s.
93 A command indicates the start or end of a control or that the formatting of the text has changed.
94 """
95
97 """Constructor.
98 @param command: The command; one of:
99 "controlStart", indicating the start of a L{ControlField};
100 "controlEnd", indicating the end of a L{ControlField}; or
101 "formatChange", indicating a L{FormatField} change.
102 @param field: The field associated with this command; may be C{None} for controlEnd.
103 @type field: L{Field}
104 """
105 if command not in ("controlStart","controlEnd","formatChange"):
106 raise ValueError("Unknown command: %s"%command)
107 elif command=="controlStart" and not isinstance(field,ControlField):
108 raise ValueError("command: %s needs a controlField"%command)
109 elif command=="formatChange" and not isinstance(field,FormatField):
110 raise ValueError("command: %s needs a formatField"%command)
111 self.command=command
112 self.field=field
113
114
115 POSITION_FIRST="first"
116 POSITION_LAST="last"
117 POSITION_CARET="caret"
118 POSITION_SELECTION="selection"
119 POSITION_ALL="all"
120
122 """Represents a point on the screen.
123 This is used when associating a point on the screen with a piece of text.
124 """
125
127 """
128 @param x: the x coordinate
129 @type x: int
130 @param y: The y coordinate
131 @type y: int
132 """
133 self.x=x
134 self.y=y
135
137 """Represents two points on the screen."""
138
139 - def __init__(self,startX,startY,endX,endY):
140 """
141 @param startX: the x coordinate of the first point.
142 @type startX: int
143 @param startY: The y coordinate of the first point.
144 @type startY: int
145 @param endX: the x coordinate of the second point.
146 @type endX: int
147 @param endY: the y coordinate of the second point.
148 @type endY: int
149 """
150 self.startX=startX
151 self.startY=startY
152 self.endX=endX
153 self.endY=endY
154
155 -class Bookmark(baseObject.AutoPropertyObject):
156 """Represents a static absolute position in some text.
157 This is used to construct a L{TextInfo} at an exact previously obtained position.
158 """
159
161 """
162 @param infoClass: The class of the L{TextInfo} object.
163 @type infoClass: type; subclass of L{TextInfo}
164 @param data: Data that can be used to reconstruct the position the textInfo object was in when it generated the bookmark.
165 """
166
167
168 self.infoClass=infoClass
169
170 self.data=data
171
173 if isinstance(other,Bookmark) and self.infoClass==other.infoClass and self.data==other.data:
174 return True
175
177 return not self==other
178
179
180 UNIT_CHARACTER="character"
181 UNIT_WORD="word"
182 UNIT_LINE="line"
183 UNIT_SENTENCE="sentence"
184 UNIT_PARAGRAPH="paragraph"
185 UNIT_PAGE="page"
186 UNIT_TABLE="table"
187 UNIT_ROW="row"
188 UNIT_COLUMN="column"
189 UNIT_CELL="cell"
190 UNIT_SCREEN="screen"
191 UNIT_STORY="story"
192 UNIT_READINGCHUNK="readingChunk"
193
194 unitLabels={
195 UNIT_CHARACTER:_("character"),
196 UNIT_WORD:_("word"),
197 UNIT_LINE:_("line"),
198 UNIT_PARAGRAPH:_("paragraph"),
199 }
200
201 -class TextInfo(baseObject.AutoPropertyObject):
202 """Provides information about a range of text in an object and facilitates access to all text in the widget.
203 A TextInfo represents a specific range of text, providing access to the text itself, as well as information about the text such as its formatting and any associated controls.
204 This range can be moved within the object's text relative to the initial position.
205
206 At a minimum, subclasses must:
207 * Extend the constructor so that it can set up the range at the specified position.
208 * Implement the L{move}, L{expand}, L{compareEndPoints}, L{setEndPoint} and L{copy} methods.
209 * Implement the L{text} and L{bookmark} attributes.
210 * Support at least the L{UNIT_CHARACTER}, L{UNIT_WORD} and L{UNIT_LINE} units.
211 * Support at least the L{POSITION_FIRST}, L{POSITION_LAST} and L{POSITION_ALL} positions.
212 If an implementation should support tracking with the mouse,
213 L{Points} must be supported as a position.
214 To support routing to a screen point from a given position, L{pointAtStart} must be implemented.
215 In order to support text formatting or control information, L{getTextWithFields} should be overridden.
216
217 @ivar bookmark: A unique identifier that can be used to make another textInfo object at this position.
218 @type bookmark: L{Bookmark}
219 """
220
221 - def __init__(self,obj,position):
222 """Constructor.
223 Subclasses must extend this, calling the superclass method first.
224 @param position: The initial position of this range; one of the POSITION_* constants or a position object supported by the implementation.
225 @type position: int, tuple or string
226 @param obj: The object containing the range of text being represented.
227 """
228 super(TextInfo,self).__init__()
229 self._obj=weakref.ref(obj) if type(obj)!=weakref.ProxyType else obj
230
231 self.basePosition=position
232
233 - def _get_obj(self):
234 """The object containing the range of text being represented."""
235 return self._obj()
236
238 return config.conf["mouse"]["mouseTextUnit"]
239
240 - def _get_text(self):
241 """The text with in this range.
242 Subclasses must implement this.
243 @return: The text.
244 @rtype: unicode
245 @note: The text is not guaranteed to be the exact length of the range in offsets.
246 """
247 raise NotImplementedError
248
249 - def getTextWithFields(self,formatConfig=None):
250 """Retreaves the text in this range, as well as any control/format fields associated therewith.
251 Subclasses may override this. The base implementation just returns the text.
252 @param formatConfig: Document formatting configuration, useful if you wish to force a particular configuration for a particular task.
253 @type formatConfig: dict
254 @return: A sequence of text strings interspersed with associated field commands.
255 @rtype: list of unicode and L{FieldCommand}
256 """
257 return [self.text]
258
259 - def unitIndex(self,unit):
260 """
261 @param unit: a unit constant for which you want to retreave an index
262 @type: string
263 @returns: The 1-based index of this unit, out of all the units of this type in the object
264 @rtype: int
265 """
266 raise NotImplementedError
267
268 - def unitCount(self,unit):
269 """
270 @param unit: a unit constant
271 @type unit: string
272 @returns: the number of units of this type in the object
273 @rtype: int
274 """
275 raise NotImplementedError
276
277 - def compareEndPoints(self,other,which):
278 """ compares one end of this range to one end of another range.
279 Subclasses must implement this.
280 @param other: the text range to compare with.
281 @type other: L{TextInfo}
282 @param which: The ends to compare; one of "startToStart", "startToEnd", "endToStart", "endToEnd".
283 @return: -1 if this end is before other end, 1 if this end is after other end or 0 if this end and other end are the same.
284 @rtype: int
285 """
286 raise NotImplementedError
287
288 - def isOverlapping(self, other):
289 """Determines whether this object overlaps another object in any way.
290 Note that collapsed objects can cause some confusion.
291 For example, in terms of offsets, (4, 4) and (4, 5) are not considered as overlapping.
292 Therefore, collapsed objects should probably be expanded to at least 1 character when using this method.
293 @param other: The TextInfo object being compared.
294 @type other: L{TextInfo}
295 @return: C{True} if the objects overlap, C{False} if not.
296 @rtype: bool
297 """
298 return self.compareEndPoints(other, "endToStart") > 0 and other.compareEndPoints(self, "endToStart") > 0
299
300 - def setEndPoint(self,other,which):
301 """Sets one end of this range to one end of another range.
302 Subclasses must implement this.
303 @param other: The range from which an end is being obtained.
304 @type other: L{TextInfo}
305 @param which: The ends to use; one of "startToStart", "startToEnd", "endToStart", "endToEnd".
306 """
307 raise NotImplementedError
308
310 """
311 @return: C{True} if representing a collapsed range, C{False} if the range is expanded to cover one or more characters.
312 @rtype: bool
313 """
314 return self.compareEndPoints(self,"startToEnd")==0
315
316 - def expand(self,unit):
317 """Expands the start and end of this text info object to a given unit
318 @param unit: a unit constant
319 @type unit: string
320 """
321 raise NotImplementedError
322
323 - def collapse(self, end=False):
324 """Collapses this text info object so that both endpoints are the same.
325 @param end: Whether to collapse to the end; C{True} to collapse to the end, C{False} to collapse to the start.
326 @type end: bool
327 """
328 raise NotImplementedError
329
331 """duplicates this text info object so that changes can be made to either one with out afecting the other
332 """
333 raise NotImplementedError
334
335 - def updateCaret(self):
336 """Moves the system caret to the position of this text info object"""
337 raise NotImplementedError
338
339 - def updateSelection(self):
340 """Moves the selection (usually the system caret) to the position of this text info object"""
341 raise NotImplementedError
342
343 - def _get_bookmark(self):
344 raise NotImplementedError
345
346 - def move(self,unit,direction,endPoint=None):
347 """Moves one or both of the endpoints of this object by the given unit and direction.
348 @param unit: the unit to move by; one of the UNIT_* constants.
349 @param direction: a positive value moves forward by a number of units, a negative value moves back a number of units
350 @type: int
351 @param endPoint: Either None, "start" or "end". If "start" then the start of the range is moved, if "end" then the end of the range is moved, if None - not specified then collapse to start and move both start and end.
352 @return: The number of units moved;
353 negative indicates backward movement, positive indicates forward movement,
354 0 means no movement.
355 @rtype: int
356 """
357 raise NotImplementedError
358
359 - def find(self,text,caseSensitive=False,reverse=False):
360 """Locates the given text and positions this TextInfo object at the start.
361 @param text: the text to search for
362 @type text: string
363 @param caceSensitive: true if case sensitivity search should be used, False if not
364 @type caseSensitive: bool
365 @param reverse: true then the search will go from current position towards the start of the text, if false then towards the end.
366 @type reverse: bool
367 @returns: True if text is found, false otherwise
368 @rtype: bool
369 """
370 raise NotImplementedError
371
373 """retreaves the NVDAObject related to the start of the range. Usually it is just the owner NVDAObject, but in the case of virtualBuffers it may be a descendant object.
374 @returns: the NVDAObject at the start
375 """
376 return self.obj
377
379 """Retrieves x and y coordinates corresponding with the textInfo start. It should return Point"""
380 raise NotImplementedError
381
383 """Text suitably formatted for copying to the clipboard. E.g. crlf characters inserted between lines."""
384 return convertToCrlf(self.text)
385
386 - def copyToClipboard(self):
387 """Copy the content of this instance to the clipboard.
388 @return: C{True} if successful, C{False} otherwise.
389 @rtype: bool
390 """
391 import api
392 return api.copyToClip(self.clipboardText)
393
394 - def getTextInChunks(self, unit):
395 """Retrieve the text of this instance in chunks of a given unit.
396 @param unit: The unit at which chunks should be split.
397 @return: Chunks of text.
398 @rtype: generator of str
399 """
400 unitInfo=self.copy()
401 unitInfo.collapse()
402 while unitInfo.compareEndPoints(self,"startToEnd")<0:
403 unitInfo.expand(unit)
404 chunkInfo=unitInfo.copy()
405 if chunkInfo.compareEndPoints(self,"startToStart")<0:
406 chunkInfo.setEndPoint(self,"startToStart")
407 if chunkInfo.compareEndPoints(self,"endToEnd")>0:
408 chunkInfo.setEndPoint(self,"endToEnd")
409 yield chunkInfo.text
410 unitInfo.collapse(end=True)
411
412 - def getControlFieldSpeech(self, attrs, ancestorAttrs, fieldType, formatConfig=None, extraDetail=False, reason=None):
413 return speech.getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason)
414
415 - def getControlFieldBraille(self, field, ancestors, reportStart, formatConfig):
416
417 import braille
418 return braille.getControlFieldBraille(field, ancestors, reportStart, formatConfig)
419
420 - def getEmbeddedObject(self, offset=0):
421 """Retrieve the embedded object associated with a particular embedded object character.
422 Where a text implementation allows other objects to be embedded in the text, embedded objects are represented by an embedded object character (\uFFFC).
423 When these characters are encountered, this method can be used to retrieve the associated embedded object.
424 @param offset: The offset of the embedded object character in question relative to the start of this instance.
425 @type offset: int
426 @return: The embedded object.
427 @rtype: L{NVDAObjects.NVDAObject}
428 """
429 raise NotImplementedError
430
431 RE_EOL = re.compile("\r\n|[\n\r]")
433 """Convert a string so that it contains only CRLF line endings.
434 @param text: The text to convert.
435 @type text: str
436 @return: The converted text.
437 @rtype: str
438 """
439 return RE_EOL.sub("\r\n", text)
440