Package textInfos
[hide private]
[frames] | no frames]

Source Code for Package textInfos

  1  #textInfos/__init__.py 
  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> 
  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   
20 -class Field(dict):
21 """Provides information about a piece of text."""
22
23 -class FormatField(Field):
24 """Provides information about the formatting of text; e.g. font information and hyperlinks."""
25
26 -class ControlField(Field):
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 #: This field is usually a single line item; e.g. a link or heading. 33 PRESCAT_SINGLELINE = "singleLine" 34 #: This field is a marker; e.g. a separator or table cell. 35 PRESCAT_MARKER = "marker" 36 #: This field is a container, usually multi-line. 37 PRESCAT_CONTAINER = "container" 38 #: This field is just for layout. 39 PRESCAT_LAYOUT = None 40
41 - def getPresentationCategory(self, ancestors, formatConfig, reason=speech.REASON_CARET):
42 role = self.get("role", controlTypes.ROLE_UNKNOWN) 43 states = self.get("states", set()) 44 45 # Honour verbosity configuration. 46 if not formatConfig["includeLayoutTables"] and role in (controlTypes.ROLE_TABLE, controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLEROWHEADER, controlTypes.ROLE_TABLECOLUMNHEADER): 47 # The user doesn't want layout tables. 48 # Find the nearest table. 49 if role == controlTypes.ROLE_TABLE: 50 # This is the nearest table. 51 table = self 52 else: 53 # Search ancestors for the nearest table. 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 # This is just layout as far as the user is concerned. 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
90 -class FieldCommand(object):
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
96 - def __init__(self,command,field):
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 #Position constants 115 POSITION_FIRST="first" 116 POSITION_LAST="last" 117 POSITION_CARET="caret" 118 POSITION_SELECTION="selection" 119 POSITION_ALL="all" 120
121 -class Point(object):
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
126 - def __init__(self,x,y):
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
136 -class Points(object):
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
160 - def __init__(self,infoClass,data):
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 #: The class of the L{TextInfo} object. 167 #: @type: type; subclass of L{TextInfo} 168 self.infoClass=infoClass 169 #: Data that can be used to reconstruct the position the textInfo object was in when it generated the bookmark. 170 self.data=data
171
172 - def __eq__(self,other):
173 if isinstance(other,Bookmark) and self.infoClass==other.infoClass and self.data==other.data: 174 return True
175
176 - def __ne__(self,other):
177 return not self==other
178 179 #Unit constants 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 #: The position with which this instance was constructed. 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
237 - def _get_unit_mouseChunk(self):
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
309 - def _get_isCollapsed(self):
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
330 - def copy(self):
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
372 - def _get_NVDAObjectAtStart(self):
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
378 - def _get_pointAtStart(self):
379 """Retrieves x and y coordinates corresponding with the textInfo start. It should return Point""" 380 raise NotImplementedError
381
382 - def _get_clipboardText(self):
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 # Import late to avoid circular import. 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]")
432 -def convertToCrlf(text):
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