Module braille
[hide private]
[frames] | no frames]

Source Code for Module braille

   1  #braille.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) 2008-2011 James Teh <jamie@jantrid.net>, Michael Curran <mick@kulgan.net> 
   6   
   7  import itertools 
   8  import os 
   9  import pkgutil 
  10  import wx 
  11  import louis 
  12  import keyboardHandler 
  13  import baseObject 
  14  import config 
  15  from logHandler import log 
  16  import controlTypes 
  17  import api 
  18  import textInfos 
  19  import speech 
  20  import brailleDisplayDrivers 
  21  import inputCore 
  22   
  23  #: The directory in which liblouis braille tables are located. 
  24  TABLES_DIR = r"louis\tables" 
  25   
  26  #: The table filenames and descriptions. 
  27  TABLES = ( 
  28          ("ar-ar-g1.utb", _("Arabic grade 1")), 
  29          ("bg.ctb", _("Bulgarian 8 dot computer braille")), 
  30          ("cy-cy-g1.utb", _("Welsh grade 1")), 
  31          ("cy-cy-g2.ctb", _("Welsh grade 2")), 
  32          ("cz-cz-g1.utb", _("Czech grade 1")), 
  33          ("da-dk-g1.utb", _("Danish grade 1")), 
  34          ("de-de-comp8.ctb", _("German 8 dot computer braille")), 
  35          ("de-de-g0.utb", _("German grade 0")), 
  36          ("de-de-g1.ctb", _("German grade 1")), 
  37          ("de-de-g2.ctb", _("German grade 2")), 
  38          ("en-gb-g1.utb", _("English (U.K.) grade 1")), 
  39          ("en-GB-g2.ctb", _("English (U.K.) grade 2")), 
  40          ("en-us-comp6.ctb", _("English (U.S.) 6 dot computer braille")), 
  41          ("en-us-comp8.ctb", _("English (U.S.) 8 dot computer braille")), 
  42          ("en-us-g1.ctb", _("English (U.S.) grade 1")), 
  43          ("en-us-g2.ctb", _("English (U.S.) grade 2")), 
  44          ("Es-Es-g1.utb", _("Spanish grade 1")), 
  45          ("fi-fi-8dot.ctb", _("Finnish 8 dot computer braille")), 
  46          ("fr-ca-g1.utb", _("French (Canada) grade 1")), 
  47          ("Fr-Ca-g2.ctb", _("French (Canada) grade 2")), 
  48          ("fr-bfu-comp6.utb", _("French (unified) 6 dot computer braille")), 
  49          ("fr-bfu-comp8.utb", _("French (unified) 8 dot computer braille")), 
  50          ("fr-bfu-g2.ctb", _("French (unified) Grade 2")), 
  51          ("gr-gr-g1.utb", _("Greek (Greece) grade 1")), 
  52          ("gez-g1.ctb", _("Ethiopic grade 1")), 
  53          ("he.ctb", _("Hebrew 8 dot computer braille")), 
  54          ("hi-in-g1.utb", _("Hindi grade 1")), 
  55          ("hr.ctb", _("Croatian 8 dot computer braille")), 
  56          ("hu1.ctb", _("Hungarian 8 dot computer braille")), 
  57          ("it-it-g1.utb2", _("Italian grade 1")), 
  58          ("Lv-Lv-g1.utb", _("Latvian grade 1")), 
  59          ("nl-be-g1.utb", _("Dutch (Belgium) grade 1")), 
  60          ("Nl-Nl-g1.utb", _("Dutch (netherlands) grade 1")), 
  61          ("no-no.ctb", _("Norwegian 8 dot computer braille")), 
  62          ("No-No-g0.utb", _("Norwegian grade 0")), 
  63          ("No-No-g1.ctb", _("Norwegian grade 1")), 
  64          ("No-No-g2.ctb", _("Norwegian grade 2")), 
  65          ("No-No-g3.ctb", _("Norwegian grade 3")), 
  66          ("Pl-Pl-g1.utb", _("Polish grade 1")), 
  67          ("Pt-Pt-g1.utb", _("Portuguese grade 1")), 
  68          ("ru-ru-g1.utb", _("Russian grade 1")), 
  69          ("Se-Se-g1.utb", _("Swedish grade 1")), 
  70          ("sk-sk-g1.utb", _("Slovak")), 
  71          ("sl-si-g1.utb", _("Slovene grade 1")), 
  72          ("sr-g1.ctb", _("Serbian grade 1")), 
  73          ("tr.ctb", _("Turkish grade 1")), 
  74          ("UEBC-g1.utb", _("Unified English Braille Code grade 1")), 
  75          ("UEBC-g2.ctb", _("Unified English Braille Code grade 2")), 
  76          ("zh-hk.ctb", _("Chinese (Hong Kong, Cantonese)")), 
  77          ("zh-tw.ctb", _("Chinese (Taiwan, Mandarin)")), 
  78  ) 
  79   
  80  roleLabels = { 
  81          controlTypes.ROLE_EDITABLETEXT: _("edt"), 
  82          controlTypes.ROLE_LIST: _("lst"), 
  83          controlTypes.ROLE_MENUBAR: _("mnubar"), 
  84          controlTypes.ROLE_POPUPMENU: _("mnu"), 
  85          controlTypes.ROLE_BUTTON: _("btn"), 
  86          controlTypes.ROLE_CHECKBOX: _("chk"), 
  87          controlTypes.ROLE_RADIOBUTTON: _("rbtn"), 
  88          controlTypes.ROLE_COMBOBOX: _("cbo"), 
  89          controlTypes.ROLE_LINK: _("lnk"), 
  90          controlTypes.ROLE_DIALOG: _("dlg"), 
  91          controlTypes.ROLE_TREEVIEW: _("tv"), 
  92          controlTypes.ROLE_TABLE: _("tb"), 
  93          # Translators: Displayed in braille for an object which is a separator. 
  94          controlTypes.ROLE_SEPARATOR: _("-----"), 
  95  } 
  96   
  97  positiveStateLabels = { 
  98          # Translators: Displayed in braille when an object (e.g. a check box) is checked. 
  99          controlTypes.STATE_CHECKED: _("(x)"), 
 100          # Translators: Displayed in braille when an object (e.g. a check box) is half checked. 
 101          controlTypes.STATE_HALFCHECKED: _("(-)"), 
 102          # Translators: Displayed in braille when an object is selected. 
 103          controlTypes.STATE_SELECTED: _("sel"), 
 104          # Translators: Displayed in braille when an object has a popup (usually a sub-menu). 
 105          controlTypes.STATE_HASPOPUP: _("submnu"), 
 106          # Translators: Displayed in braille when an object supports autocompletion. 
 107          controlTypes.STATE_AUTOCOMPLETE: _("..."), 
 108          # Translators: Displayed in braille when an object (e.g. a tree view item) is expanded. 
 109          controlTypes.STATE_EXPANDED: _("-"), 
 110          # Translators: Displayed in braille when an object (e.g. a tree view item) is collapsed. 
 111          controlTypes.STATE_COLLAPSED: _("+"), 
 112          # Translators: Displayed in braille when an object (e.g. an editable text field) is read-only. 
 113          controlTypes.STATE_READONLY: _("ro"), 
 114  } 
 115  negativeStateLabels = { 
 116          # Translators: Displayed in braille when an object (e.g. a check box) is not checked. 
 117          controlTypes.STATE_CHECKED: _("( )"), 
 118  } 
 119   
 120  DOT7 = 64 
 121  DOT8 = 128 
122 123 -def NVDAObjectHasUsefulText(obj):
124 import displayModel 125 return issubclass(obj.TextInfo,displayModel.DisplayModelTextInfo) or obj.role in (controlTypes.ROLE_EDITABLETEXT, controlTypes.ROLE_TERMINAL) or controlTypes.STATE_EDITABLE in obj.states
126
127 -def _getDisplayDriver(name):
128 return __import__("brailleDisplayDrivers.%s" % name, globals(), locals(), ("brailleDisplayDrivers",)).BrailleDisplayDriver
129
130 -def getDisplayList():
131 displayList = [] 132 for loader, name, isPkg in pkgutil.iter_modules(brailleDisplayDrivers.__path__): 133 if name.startswith('_'): 134 continue 135 try: 136 display = _getDisplayDriver(name) 137 if display.check(): 138 displayList.append((display.name, display.description)) 139 except: 140 pass 141 return displayList
142
143 -class Region(object):
144 """A region of braille to be displayed. 145 Each portion of braille to be displayed is represented by a region. 146 The region is responsible for retrieving its text and cursor position, translating it into braille cells and handling cursor routing requests relative to its braille cells. 147 The L{BrailleBuffer} containing this region will call L{update} and expect that L{brailleCells} and L{brailleCursorPos} will be set appropriately. 148 L{routeTo} will be called to handle a cursor routing request. 149 """ 150
151 - def __init__(self):
152 #: The original, raw text of this region. 153 self.rawText = "" 154 #: The position of the cursor in L{rawText}, C{None} if the cursor is not in this region. 155 #: @type: int 156 self.cursorPos = None 157 #: The translated braille representation of this region. 158 #: @type: [int, ...] 159 self.brailleCells = [] 160 #: liblouis typeform flags for each character in L{rawText}, 161 #: C{None} if no typeform info. 162 #: @type: [int, ...] 163 self.rawTextTypeforms = None 164 #: A list mapping positions in L{rawText} to positions in L{brailleCells}. 165 #: @type: [int, ...] 166 self.rawToBraillePos = [] 167 #: A list mapping positions in L{brailleCells} to positions in L{rawText}. 168 #: @type: [int, ...] 169 self.brailleToRawPos = [] 170 #: The position of the cursor in L{brailleCells}, C{None} if the cursor is not in this region. 171 #: @type: int 172 self.brailleCursorPos = None 173 #: Whether to hide all previous regions. 174 #: @type: bool 175 self.hidePreviousRegions = False 176 #: Whether this region should be positioned at the absolute left of the display when focused. 177 #: @type: bool 178 self.focusToHardLeft = False
179
180 - def update(self):
181 """Update this region. 182 Subclasses should extend this to update L{rawText} and L{cursorPos} if necessary. 183 The base class method handles translation of L{rawText} into braille, placing the result in L{brailleCells}. 184 Typeform information from L{rawTextTypeforms} is used, if any. 185 L{rawToBraillePos} and L{brailleToRawPos} are updated according to the translation. 186 L{brailleCursorPos} is similarly updated based on L{cursorPos}. 187 @postcondition: L{brailleCells} and L{brailleCursorPos} are updated and ready for rendering. 188 """ 189 mode = louis.dotsIO | louis.pass1Only 190 if config.conf["braille"]["expandAtCursor"] and self.cursorPos is not None: 191 mode |= louis.compbrlAtCursor 192 text=unicode(self.rawText).replace('\0','') 193 braille, self.brailleToRawPos, self.rawToBraillePos, brailleCursorPos = louis.translate( 194 [os.path.join(TABLES_DIR, config.conf["braille"]["translationTable"]), 195 "braille-patterns.cti"], 196 text, 197 # liblouis mutates typeform if it is a list. 198 typeform=tuple(self.rawTextTypeforms) if isinstance(self.rawTextTypeforms, list) else self.rawTextTypeforms, 199 mode=mode, cursorPos=self.cursorPos or 0) 200 # liblouis gives us back a character string of cells, so convert it to a list of ints. 201 # For some reason, the highest bit is set, so only grab the lower 8 bits. 202 self.brailleCells = [ord(cell) & 255 for cell in braille] 203 # HACK: Work around a liblouis bug whereby an empty braille translation is returned. 204 if not self.brailleCells: 205 # Just provide a space. 206 self.brailleCells.append(0) 207 self.brailleToRawPos.append(0) 208 if self.cursorPos is not None: 209 # HACK: Work around a liblouis bug whereby the returned cursor position is not within the braille cells returned. 210 if brailleCursorPos >= len(self.brailleCells): 211 brailleCursorPos = len(self.brailleCells) - 1 212 else: 213 brailleCursorPos = None 214 self.brailleCursorPos = brailleCursorPos
215
216 - def routeTo(self, braillePos):
217 """Handle a cursor routing request. 218 For example, this might activate an object or move the cursor to the requested position. 219 @param braillePos: The routing position in L{brailleCells}. 220 @type braillePos: int 221 @note: If routing the cursor, L{brailleToRawPos} can be used to translate L{braillePos} into a position in L{rawText}. 222 """
223
224 - def nextLine(self):
225 """Move to the next line if possible. 226 """
227
228 - def previousLine(self, start=False):
229 """Move to the previous line if possible. 230 @param start: C{True} to move to the start of the line, C{False} to move to the end. 231 @type start: bool 232 """
233
234 -class TextRegion(Region):
235 """A simple region containing a string of text. 236 """ 237
238 - def __init__(self, text):
239 super(TextRegion, self).__init__() 240 self.rawText = text
241
242 -def getBrailleTextForProperties(**propertyValues):
243 # TODO: Don't use speech functions. 244 textList = [] 245 name = propertyValues.get("name") 246 if name: 247 textList.append(name) 248 role = propertyValues.get("role") 249 states = propertyValues.get("states") 250 positionInfo = propertyValues.get("positionInfo") 251 level = positionInfo.get("level") if positionInfo else None 252 rowNumber = propertyValues.get("rowNumber") 253 columnNumber = propertyValues.get("columnNumber") 254 includeTableCellCoords = propertyValues.get("includeTableCellCoords", True) 255 if role is not None: 256 if role == controlTypes.ROLE_HEADING and level: 257 # Translators: Displayed in braille for a heading with a level. 258 # %s is replaced with the level. 259 roleText = _("h%s") % level 260 level = None 261 elif role == controlTypes.ROLE_LINK and states and controlTypes.STATE_VISITED in states: 262 states = states.copy() 263 states.discard(controlTypes.STATE_VISITED) 264 # Translators: Displayed in braille for a link which has been visited. 265 roleText = _("vlnk") 266 elif (name or rowNumber or columnNumber) and role in speech.silentRolesOnFocus: 267 roleText = None 268 else: 269 roleText = roleLabels.get(role, controlTypes.speechRoleLabels[role]) 270 else: 271 role = propertyValues.get("_role") 272 roleText = None 273 value = propertyValues.get("value") 274 if value and role not in speech.silentValuesForRoles: 275 textList.append(value) 276 if states: 277 positiveStates = speech.processPositiveStates(role, states, speech.REASON_FOCUS, states) 278 textList.extend(positiveStateLabels.get(state, controlTypes.speechStateLabels[state]) for state in positiveStates) 279 negativeStates = speech.processNegativeStates(role, states, speech.REASON_FOCUS, None) 280 textList.extend(negativeStateLabels.get(state, _("not %s") % controlTypes.speechStateLabels[state]) for state in negativeStates) 281 if roleText: 282 textList.append(roleText) 283 description = propertyValues.get("description") 284 if description: 285 textList.append(description) 286 keyboardShortcut = propertyValues.get("keyboardShortcut") 287 if keyboardShortcut: 288 textList.append(keyboardShortcut) 289 if positionInfo: 290 indexInGroup = positionInfo.get("indexInGroup") 291 similarItemsInGroup = positionInfo.get("similarItemsInGroup") 292 if indexInGroup and similarItemsInGroup: 293 textList.append(_("%s of %s") % (indexInGroup, similarItemsInGroup)) 294 if level is not None: 295 # Translators: Displayed in braille when an object (e.g. a tree view item) has a hierarchical level. 296 # %s is replaced with the level. 297 textList.append(_('lv %s')%positionInfo['level']) 298 if rowNumber: 299 if includeTableCellCoords: 300 # Translators: Displayed in braille for a table cell row number. 301 # %s is replaced with the row number. 302 textList.append(_("r%s") % rowNumber) 303 if columnNumber: 304 columnHeaderText = propertyValues.get("columnHeaderText") 305 if columnHeaderText: 306 textList.append(columnHeaderText) 307 if includeTableCellCoords: 308 # Translators: Displayed in braille for a table cell column number. 309 # %s is replaced with the column number. 310 textList.append(_("c%s") % columnNumber) 311 return " ".join([x for x in textList if x])
312
313 -class NVDAObjectRegion(Region):
314 """A region to provide a braille representation of an NVDAObject. 315 This region will update based on the current state of the associated NVDAObject. 316 A cursor routing request will activate the object's default action. 317 """ 318
319 - def __init__(self, obj, appendText=""):
320 """Constructor. 321 @param obj: The associated NVDAObject. 322 @type obj: L{NVDAObjects.NVDAObject} 323 @param appendText: Text which should always be appended to the NVDAObject text, useful if this region will always precede other regions. 324 @type appendText: str 325 """ 326 super(NVDAObjectRegion, self).__init__() 327 self.obj = obj 328 self.appendText = appendText
329
330 - def update(self):
331 obj = self.obj 332 text = getBrailleTextForProperties(name=obj.name, role=obj.role, value=obj.value if not NVDAObjectHasUsefulText(obj) else None , states=obj.states, description=obj.description, keyboardShortcut=obj.keyboardShortcut, positionInfo=obj.positionInfo) 333 self.rawText = text + self.appendText 334 super(NVDAObjectRegion, self).update()
335
336 - def routeTo(self, braillePos):
337 try: 338 self.obj.doAction() 339 except NotImplementedError: 340 pass
341
342 -class ReviewNVDAObjectRegion(NVDAObjectRegion):
343
344 - def routeTo(self, braillePos):
345 pass
346
347 -def getControlFieldBraille(field, ancestors, reportStart, formatConfig):
348 presCat = field.getPresentationCategory(ancestors, formatConfig) 349 if reportStart: 350 # If this is a container, only report it if this is the start of the node. 351 if presCat == field.PRESCAT_CONTAINER and not field.get("_startOfNode"): 352 return None 353 else: 354 # We only report ends for containers 355 # and only if this is the end of the node. 356 if presCat != field.PRESCAT_CONTAINER or not field.get("_endOfNode"): 357 return None 358 359 role = field.get("role", controlTypes.ROLE_UNKNOWN) 360 states = field.get("states", set()) 361 362 if presCat == field.PRESCAT_LAYOUT: 363 # The only item we report for these fields is clickable, if present. 364 if controlTypes.STATE_CLICKABLE in states: 365 return getBrailleTextForProperties(states={controlTypes.STATE_CLICKABLE}) 366 return None 367 368 elif role in (controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLECOLUMNHEADER, controlTypes.ROLE_TABLEROWHEADER) and field.get("table-id"): 369 # Table cell. 370 reportTableHeaders = formatConfig["reportTableHeaders"] 371 reportTableCellCoords = formatConfig["reportTableCellCoords"] 372 props = { 373 "states": states, 374 "rowNumber": field.get("table-rownumber"), 375 "columnNumber": field.get("table-columnnumber"), 376 "includeTableCellCoords": reportTableCellCoords 377 } 378 if reportTableHeaders: 379 props["columnHeaderText"] = field.get("table-columnheadertext") 380 return getBrailleTextForProperties(**props) 381 382 elif reportStart: 383 props = {"role": role, "states": states} 384 if config.conf["presentation"]["reportKeyboardShortcuts"]: 385 kbShortcut = field.get("keyboardShortcut") 386 if kbShortcut: 387 props["keyboardShortcut"] = kbShortcut 388 level = field.get("level") 389 if level: 390 props["positionInfo"] = {"level": level} 391 return getBrailleTextForProperties(**props) 392 else: 393 # Translators: Displayed in braille at the end of a control field such as a list or table. 394 # %s is replaced with the control's role. 395 return (_("%s end") % 396 getBrailleTextForProperties(role=role))
397
398 -def getFormatFieldBraille(field):
399 linePrefix = field.get("line-prefix") 400 if linePrefix: 401 return linePrefix 402 return None
403
404 -class TextInfoRegion(Region):
405
406 - def __init__(self, obj):
407 super(TextInfoRegion, self).__init__() 408 self.obj = obj
409
410 - def _isMultiline(self):
411 # A region's object can either be an NVDAObject or a tree interceptor. 412 # Tree interceptors should always be multiline. 413 from treeInterceptorHandler import TreeInterceptor 414 if isinstance(self.obj, TreeInterceptor): 415 return True 416 # Terminals are inherently multiline, so they don't have the multiline state. 417 return (self.obj.role == controlTypes.ROLE_TERMINAL or controlTypes.STATE_MULTILINE in self.obj.states)
418
419 - def _getCursor(self):
420 """Retrieve the collapsed cursor. 421 This should be the start or end of the selection returned by L{_getSelection}. 422 @return: The cursor. 423 """ 424 try: 425 return self.obj.makeTextInfo(textInfos.POSITION_CARET) 426 except: 427 return self.obj.makeTextInfo(textInfos.POSITION_FIRST)
428
429 - def _getSelection(self):
430 """Retrieve the selection. 431 The start or end of this should be the cursor returned by L{_getCursor}. 432 @return: The selection. 433 @rtype: L{textInfos.TextInfo} 434 """ 435 try: 436 return self.obj.makeTextInfo(textInfos.POSITION_SELECTION) 437 except: 438 return self.obj.makeTextInfo(textInfos.POSITION_FIRST)
439
440 - def _setCursor(self, info):
441 """Set the cursor. 442 @param info: The range to which the cursor should be moved. 443 @type info: L{textInfos.TextInfo} 444 """ 445 try: 446 info.updateCaret() 447 except NotImplementedError: 448 log.debugWarning("", exc_info=True)
449
450 - def _getTypeformFromFormatField(self, field):
451 typeform = louis.plain_text 452 if field.get("bold", False): 453 typeform |= louis.bold 454 if field.get("italic", False): 455 typeform |= louis.italic 456 if field.get("underline", False): 457 typeform |= louis.underline 458 return typeform
459
460 - def _addFieldText(self, text):
461 # Separate this field text from the rest of the text. 462 if self.rawText: 463 text = " %s " % text 464 else: 465 text += " " 466 self.rawText += text 467 textLen = len(text) 468 self.rawTextTypeforms.extend((louis.plain_text,) * textLen) 469 # Map this field text to the start of the field's content. 470 self._rawToContentPos.extend((self._currentContentPos,) * textLen)
471
472 - def _addTextWithFields(self, info, formatConfig, isSelection=False):
473 shouldMoveCursorToFirstContent = not isSelection and self.cursorPos is not None 474 ctrlFields = [] 475 typeform = louis.plain_text 476 for command in info.getTextWithFields(formatConfig=formatConfig): 477 if isinstance(command, basestring): 478 if not command: 479 continue 480 if isSelection and self._selectionStart is None: 481 # This is where the content begins. 482 self._selectionStart = len(self.rawText) 483 elif shouldMoveCursorToFirstContent: 484 # This is the first piece of content after the cursor. 485 # Position the cursor here, as it may currently be positioned on control field text. 486 self.cursorPos = len(self.rawText) 487 shouldMoveCursorToFirstContent = False 488 self.rawText += command 489 self.rawTextTypeforms.extend((typeform,) * len(command)) 490 endPos = self._currentContentPos + len(command) 491 self._rawToContentPos.extend(xrange(self._currentContentPos, endPos)) 492 self._currentContentPos = endPos 493 if isSelection: 494 # The last time this is set will be the end of the content. 495 self._selectionEnd = len(self.rawText) 496 elif isinstance(command, textInfos.FieldCommand): 497 cmd = command.command 498 field = command.field 499 if cmd == "formatChange": 500 typeform = self._getTypeformFromFormatField(field) 501 text = getFormatFieldBraille(field) 502 if not text: 503 continue 504 self._addFieldText(text) 505 elif cmd == "controlStart": 506 # Place this field on a stack so we can access it for controlEnd. 507 if self._skipFieldsNotAtStartOfNode and not field.get("_startOfNode"): 508 text = None 509 else: 510 text = info.getControlFieldBraille(field, ctrlFields, True, formatConfig) 511 ctrlFields.append(field) 512 if not text: 513 continue 514 self._addFieldText(text) 515 elif cmd == "controlEnd": 516 field = ctrlFields.pop() 517 text = getControlFieldBraille(field, ctrlFields, False, formatConfig) 518 if not text: 519 continue 520 # Separate this field text from the rest of the text. 521 self.rawText += " %s " % text 522 textLen = len(text) 523 self.rawTextTypeforms.extend((louis.plain_text,) * textLen) 524 # Map this field text to the end of the field's content. 525 self._rawToContentPos.extend((self._currentContentPos - 1,) * textLen) 526 if isSelection and self._selectionStart is None: 527 # There is no selection. This is a cursor. 528 self.cursorPos = len(self.rawText) 529 if not self._skipFieldsNotAtStartOfNode: 530 # We only render fields that aren't at the start of their nodes for the first part of the line. 531 # Otherwise, we'll render fields that have already been rendered. 532 self._skipFieldsNotAtStartOfNode = True
533
534 - def update(self):
535 formatConfig = config.conf["documentFormatting"] 536 # HACK: Some TextInfos only support UNIT_LINE properly if they are based on POSITION_CARET, 537 # so use the original cursor TextInfo for line and copy for cursor. 538 self._line = line = self._getCursor() 539 cursor = line.copy() 540 # Get the line at the cursor. 541 line.expand(textInfos.UNIT_LINE) 542 # Get the selection. 543 sel = self._getSelection() 544 # Restrict the selection to the line at the cursor. 545 if sel.compareEndPoints(line, "startToStart") < 0: 546 sel.setEndPoint(line, "startToStart") 547 if sel.compareEndPoints(line, "endToEnd") > 0: 548 sel.setEndPoint(line, "endToEnd") 549 self.rawText = "" 550 self.rawTextTypeforms = [] 551 self.cursorPos = None 552 # The output includes text representing fields which isn't part of the real content in the control. 553 # Therefore, maintain a map of positions in the output to positions in the content. 554 self._rawToContentPos = [] 555 self._currentContentPos = 0 556 self._selectionStart = self._selectionEnd = None 557 self._skipFieldsNotAtStartOfNode = False 558 559 # Not all text APIs support offsets, so we can't always get the offset of the selection relative to the start of the line. 560 # Therefore, grab the line in three parts. 561 # First, the chunk from the start of the line to the start of the selection. 562 chunk = line.copy() 563 chunk.collapse() 564 chunk.setEndPoint(sel, "endToStart") 565 self._addTextWithFields(chunk, formatConfig) 566 # Now, the selection itself. 567 self._addTextWithFields(sel, formatConfig, isSelection=True) 568 # Finally, get the chunk from the end of the selection to the end of the line. 569 chunk.setEndPoint(line, "endToEnd") 570 chunk.setEndPoint(sel, "startToEnd") 571 self._addTextWithFields(chunk, formatConfig) 572 # Strip line ending characters, but add a space in case the cursor is at the end of the line. 573 self.rawText = self.rawText.rstrip("\r\n\0\v\f") + " " 574 del self.rawTextTypeforms[len(self.rawText) - 1:] 575 self.rawTextTypeforms.append(louis.plain_text) 576 577 # If this is not the first line, hide all previous regions. 578 start = cursor.obj.makeTextInfo(textInfos.POSITION_FIRST) 579 self.hidePreviousRegions = (start.compareEndPoints(line, "startToStart") < 0) 580 # If this is a multiline control, position it at the absolute left of the display when focused. 581 self.focusToHardLeft = self._isMultiline() 582 super(TextInfoRegion, self).update() 583 584 if self._selectionStart is not None: 585 # Mark the selection with dots 7 and 8. 586 if self._selectionEnd >= len(self.rawText): 587 brailleSelEnd = len(self.brailleCells) 588 else: 589 brailleSelEnd = self.rawToBraillePos[self._selectionEnd] 590 for pos in xrange(self.rawToBraillePos[self._selectionStart], brailleSelEnd): 591 self.brailleCells[pos] |= DOT7 | DOT8
592
593 - def routeTo(self, braillePos):
594 pos = self._rawToContentPos[self.brailleToRawPos[braillePos]] 595 # pos is relative to the start of the line. 596 # Therefore, get the start of the line... 597 dest = self._line.copy() 598 dest.collapse() 599 # and move pos characters from there. 600 dest.move(textInfos.UNIT_CHARACTER, pos) 601 self._setCursor(dest)
602
603 - def nextLine(self):
604 dest = self._line.copy() 605 moved = dest.move(textInfos.UNIT_LINE, 1) 606 if not moved: 607 return 608 dest.collapse() 609 self._setCursor(dest)
610
611 - def previousLine(self, start=False):
612 dest = self._line.copy() 613 dest.collapse() 614 # If the end of the line is desired, move to the last character. 615 moved = dest.move(textInfos.UNIT_LINE if start else textInfos.UNIT_CHARACTER, -1) 616 if not moved: 617 return 618 dest.collapse() 619 self._setCursor(dest)
620
621 -class CursorManagerRegion(TextInfoRegion):
622
623 - def _isMultiline(self):
624 return True
625
626 - def _getSelection(self):
627 return self.obj.selection
628
629 - def _setCursor(self, info):
630 self.obj.selection = info
631
632 -class ReviewTextInfoRegion(TextInfoRegion):
633
634 - def _getCursor(self):
635 return api.getReviewPosition().copy()
636 637 _getSelection = _getCursor 638
639 - def _setCursor(self, info):
641
642 -def rindex(seq, item, start, end):
643 for index in xrange(end - 1, start - 1, -1): 644 if seq[index] == item: 645 return index 646 raise ValueError("%r is not in sequence" % item)
647
648 -class BrailleBuffer(baseObject.AutoPropertyObject):
649
650 - def __init__(self, handler):
651 self.handler = handler 652 #: The regions in this buffer. 653 #: @type: [L{Region}, ...] 654 self.regions = [] 655 #: The position of the cursor in L{brailleCells}, C{None} if no region contains the cursor. 656 #: @type: int 657 self.cursorPos = None 658 #: The translated braille representation of the entire buffer. 659 #: @type: [int, ...] 660 self.brailleCells = [] 661 #: The position in L{brailleCells} where the display window starts (inclusive). 662 #: @type: int 663 self.windowStartPos = 0
664
665 - def clear(self):
666 """Clear the entire buffer. 667 This removes all regions and resets the window position to 0. 668 """ 669 self.regions = [] 670 self.cursorPos = None 671 self.brailleCursorPos = None 672 self.brailleCells = [] 673 self.windowStartPos = 0
674
675 - def _get_visibleRegions(self):
676 if not self.regions: 677 return 678 if self.regions[-1].hidePreviousRegions: 679 yield self.regions[-1] 680 return 681 for region in self.regions: 682 yield region
683
684 - def _get_regionsWithPositions(self):
685 start = 0 686 for region in self.visibleRegions: 687 end = start + len(region.brailleCells) 688 yield region, start, end 689 start = end
690
691 - def bufferPosToRegionPos(self, bufferPos):
692 for region, start, end in self.regionsWithPositions: 693 if end > bufferPos: 694 return region, bufferPos - start 695 raise LookupError("No such position")
696
697 - def regionPosToBufferPos(self, region, pos, allowNearest=False):
698 for testRegion, start, end in self.regionsWithPositions: 699 if region == testRegion: 700 if pos < end - start: 701 # The requested position is still valid within the region. 702 return start + pos 703 elif allowNearest: 704 # The position within the region isn't valid, 705 # but the region is valid, so return its start. 706 return start 707 break 708 if allowNearest: 709 # Resort to the start of the last region. 710 return start 711 raise LookupError("No such position")
712
713 - def bufferPosToWindowPos(self, bufferPos):
714 if not (self.windowStartPos <= bufferPos < self.windowEndPos): 715 raise LookupError("Buffer position not in window") 716 return bufferPos - self.windowStartPos
717
718 - def _get_windowEndPos(self):
719 endPos = self.windowStartPos + self.handler.displaySize 720 cellsLen = len(self.brailleCells) 721 if endPos >= cellsLen: 722 return cellsLen 723 try: 724 # Try not to split words across windows. 725 # To do this, break after the furthest possible space. 726 return min(rindex(self.brailleCells, 0, self.windowStartPos, endPos) + 1, 727 endPos) 728 except ValueError: 729 pass 730 return endPos
731
732 - def _set_windowEndPos(self, endPos):
733 startPos = endPos - self.handler.displaySize 734 # Get the last region currently displayed. 735 region, regionPos = self.bufferPosToRegionPos(endPos - 1) 736 if region.focusToHardLeft: 737 # Only scroll to the start of this region. 738 restrictPos = endPos - regionPos - 1 739 else: 740 restrictPos = 0 741 if startPos <= restrictPos: 742 self.windowStartPos = restrictPos 743 return 744 try: 745 # Try not to split words across windows. 746 # To do this, break after the furthest possible block of spaces. 747 startPos = self.brailleCells.index(0, startPos, endPos) 748 # Skip past spaces. 749 for startPos in xrange(startPos, endPos): 750 if self.brailleCells[startPos] != 0: 751 break 752 except ValueError: 753 pass 754 self.windowStartPos = startPos
755
756 - def scrollForward(self):
757 oldStart = self.windowStartPos 758 end = self.windowEndPos 759 if end < len(self.brailleCells): 760 self.windowStartPos = end 761 if self.windowStartPos == oldStart: 762 # The window could not be scrolled, so try moving to the next line. 763 if self.regions: 764 self.regions[-1].nextLine() 765 else: 766 # Scrolling succeeded. 767 self.updateDisplay()
768
769 - def scrollBack(self):
770 start = self.windowStartPos 771 if start > 0: 772 self.windowEndPos = start 773 if self.windowStartPos == start: 774 # The window could not be scrolled, so try moving to the previous line. 775 if self.regions: 776 self.regions[-1].previousLine() 777 else: 778 # Scrolling succeeded. 779 self.updateDisplay()
780
781 - def scrollTo(self, region, pos):
782 pos = self.regionPosToBufferPos(region, pos) 783 if pos >= self.windowEndPos: 784 self.windowEndPos = pos + 1 785 elif pos < self.windowStartPos: 786 self.windowStartPos = pos 787 self.updateDisplay()
788
789 - def focus(self, region):
790 """Bring the specified region into focus. 791 The region is placed at the start of the display. 792 However, if the region has not set L{Region.focusToHardLeft} and there is extra space at the end of the display, the display is scrolled left so that as much as possible is displayed. 793 @param region: The region to focus. 794 @type region: L{Region} 795 """ 796 pos = self.regionPosToBufferPos(region, 0) 797 self.windowStartPos = pos 798 if region.focusToHardLeft: 799 return 800 end = self.windowEndPos 801 if end - pos < self.handler.displaySize: 802 # We can fit more on the display while still keeping pos visible. 803 # Force windowStartPos to be recalculated based on windowEndPos. 804 self.windowEndPos = end
805
806 - def update(self):
807 self.brailleCells = [] 808 self.cursorPos = None 809 start = 0 810 for region in self.visibleRegions: 811 cells = region.brailleCells 812 self.brailleCells.extend(cells) 813 if region.brailleCursorPos is not None: 814 self.cursorPos = start + region.brailleCursorPos 815 start += len(cells)
816
817 - def updateDisplay(self):
818 if self is self.handler.buffer: 819 self.handler.update()
820
821 - def _get_cursorWindowPos(self):
822 if self.cursorPos is None: 823 return None 824 try: 825 return self.bufferPosToWindowPos(self.cursorPos) 826 except LookupError: 827 return None
828
829 - def _get_windowBrailleCells(self):
830 return self.brailleCells[self.windowStartPos:self.windowEndPos]
831
832 - def routeTo(self, windowPos):
833 pos = self.windowStartPos + windowPos 834 if pos >= self.windowEndPos: 835 return 836 region, pos = self.bufferPosToRegionPos(pos) 837 region.routeTo(pos)
838
839 - def saveWindow(self):
840 """Save the current window so that it can be restored after the buffer is updated. 841 The window start position is saved as a position relative to a region. 842 This allows it to be restored even after other regions are added, removed or updated. 843 It can be restored with L{restoreWindow}. 844 @postcondition: The window is saved and can be restored with L{restoreWindow}. 845 """ 846 self._savedWindow = self.bufferPosToRegionPos(self.windowStartPos)
847
848 - def restoreWindow(self):
849 """Restore the window saved by L{saveWindow}. 850 @precondition: L{saveWindow} has been called. 851 @postcondition: If the saved position is valid, the window is restored. 852 Otherwise, the nearest position is restored. 853 """ 854 region, pos = self._savedWindow 855 self.windowStartPos = self.regionPosToBufferPos(region, pos, allowNearest=True)
856 857 _cachedFocusAncestorsEnd = 0
858 -def invalidateCachedFocusAncestors(index):
859 """Invalidate cached focus ancestors from a given index. 860 This will cause regions to be generated for the focus ancestors >= index next time L{getFocusContextRegions} is called, 861 rather than using cached regions for those ancestors. 862 @param index: The index from which cached focus ancestors should be invalidated. 863 @type index: int 864 """ 865 global _cachedFocusAncestorsEnd 866 # There could be multiple calls to this function before getFocusContextRegions() is called. 867 _cachedFocusAncestorsEnd = min(_cachedFocusAncestorsEnd, index)
868
869 -def getFocusContextRegions(obj, oldFocusRegions=None):
870 global _cachedFocusAncestorsEnd 871 # Late import to avoid circular import. 872 from treeInterceptorHandler import TreeInterceptor 873 ancestors = api.getFocusAncestors() 874 875 ancestorsEnd = len(ancestors) 876 if isinstance(obj, TreeInterceptor): 877 obj = obj.rootNVDAObject 878 # We only want the ancestors of the buffer's root NVDAObject. 879 if obj != api.getFocusObject(): 880 # Search backwards through the focus ancestors to find the index of obj. 881 for index, ancestor in itertools.izip(xrange(len(ancestors) - 1, 0, -1), reversed(ancestors)): 882 if obj == ancestor: 883 ancestorsEnd = index 884 break 885 886 if oldFocusRegions: 887 # We have the regions from the previous focus, so use them as a cache to avoid rebuilding regions which are the same. 888 # We need to generate new regions from _cachedFocusAncestorsEnd onwards. 889 # However, we must ensure that it is not beyond the last ancestor we wish to consider. 890 # Also, we don't ever want to fetch ancestor 0 (the desktop). 891 newAncestorsStart = max(min(_cachedFocusAncestorsEnd, ancestorsEnd), 1) 892 # Search backwards through the old regions to find the last common region. 893 for index, region in itertools.izip(xrange(len(oldFocusRegions) - 1, -1, -1), reversed(oldFocusRegions)): 894 ancestorIndex = getattr(region, "_focusAncestorIndex", None) 895 if ancestorIndex is None: 896 continue 897 if ancestorIndex < newAncestorsStart: 898 # This is the last common region. 899 # An ancestor may have been skipped and not have a region, which means that we need to grab new ancestors from this point. 900 newAncestorsStart = ancestorIndex + 1 901 commonRegionsEnd = index + 1 902 break 903 else: 904 # No common regions were found. 905 commonRegionsEnd = 0 906 newAncestorsStart = 1 907 # Yield the common regions. 908 for region in oldFocusRegions[0:commonRegionsEnd]: 909 yield region 910 else: 911 # Fetch all ancestors. 912 newAncestorsStart = 1 913 914 for index, parent in enumerate(ancestors[newAncestorsStart:ancestorsEnd], newAncestorsStart): 915 if not parent.isPresentableFocusAncestor: 916 continue 917 region = NVDAObjectRegion(parent, appendText=" ") 918 region._focusAncestorIndex = index 919 region.update() 920 yield region 921 922 _cachedFocusAncestorsEnd = ancestorsEnd
923
924 -def getFocusRegions(obj, review=False):
925 # Late import to avoid circular import. 926 from treeInterceptorHandler import TreeInterceptor 927 from cursorManager import CursorManager 928 if isinstance(obj, CursorManager): 929 region2 = (ReviewTextInfoRegion if review else CursorManagerRegion)(obj) 930 elif isinstance(obj, TreeInterceptor) or NVDAObjectHasUsefulText(obj): 931 region2 = (ReviewTextInfoRegion if review else TextInfoRegion)(obj) 932 else: 933 region2 = None 934 if isinstance(obj, TreeInterceptor): 935 obj = obj.rootNVDAObject 936 region = (ReviewNVDAObjectRegion if review else NVDAObjectRegion)(obj, appendText=" " if region2 else "") 937 region.update() 938 yield region 939 if region2: 940 region2.update() 941 yield region2
942
943 -class BrailleHandler(baseObject.AutoPropertyObject):
944 TETHER_FOCUS = "focus" 945 TETHER_REVIEW = "review" 946 947 cursorShape = 0xc0 948
949 - def __init__(self):
950 self.display = None 951 self.displaySize = 0 952 self.mainBuffer = BrailleBuffer(self) 953 self.messageBuffer = BrailleBuffer(self) 954 self._messageCallLater = None 955 self.buffer = self.mainBuffer 956 #: Whether braille is enabled. 957 #: @type: bool 958 self.enabled = False 959 self._keyCountForLastMessage=0 960 self._cursorPos = None 961 self._cursorBlinkUp = True 962 self._cells = [] 963 self._cursorBlinkTimer = None
964
965 - def terminate(self):
966 if self._messageCallLater: 967 self._messageCallLater.Stop() 968 self._messageCallLater = None 969 if self._cursorBlinkTimer: 970 self._cursorBlinkTimer.Stop() 971 self._cursorBlinkTimer = None 972 if self.display: 973 self.display.terminate() 974 self.display = None
975
976 - def _get_tether(self):
977 return config.conf["braille"]["tetherTo"]
978
979 - def _set_tether(self, tether):
980 if tether == config.conf["braille"]["tetherTo"]: 981 return 982 config.conf["braille"]["tetherTo"] = tether 983 self.mainBuffer.clear() 984 if tether == self.TETHER_REVIEW: 985 self.handleReviewMove() 986 else: 987 self.handleGainFocus(api.getFocusObject())
988
989 - def setDisplayByName(self, name):
990 if not name: 991 self.display = None 992 self.displaySize = 0 993 return 994 try: 995 newDisplay = _getDisplayDriver(name) 996 if newDisplay == self.display.__class__: 997 # This is the same driver as was already set, so just re-initialise it. 998 self.display.terminate() 999 newDisplay = self.display 1000 newDisplay.__init__() 1001 else: 1002 newDisplay = newDisplay() 1003 if self.display: 1004 try: 1005 self.display.terminate() 1006 except: 1007 log.error("Error terminating previous display driver", exc_info=True) 1008 self.display = newDisplay 1009 self.displaySize = newDisplay.numCells 1010 self.enabled = bool(self.displaySize) 1011 config.conf["braille"]["display"] = name 1012 log.info("Loaded braille display driver %s" % name) 1013 return True 1014 except: 1015 log.error("Error initializing display driver", exc_info=True) 1016 self.setDisplayByName("noBraille") 1017 return False
1018
1019 - def _updateDisplay(self):
1020 if self._cursorBlinkTimer: 1021 self._cursorBlinkTimer.Stop() 1022 self._cursorBlinkTimer = None 1023 self._cursorBlinkUp = True 1024 self._displayWithCursor() 1025 blinkRate = config.conf["braille"]["cursorBlinkRate"] 1026 if blinkRate and self._cursorPos is not None: 1027 self._cursorBlinkTimer = wx.PyTimer(self._blink) 1028 self._cursorBlinkTimer.Start(blinkRate)
1029
1030 - def _displayWithCursor(self):
1031 if not self._cells: 1032 return 1033 cells = list(self._cells) 1034 if self._cursorPos is not None and self._cursorBlinkUp: 1035 cells[self._cursorPos] |= self.cursorShape 1036 self.display.display(cells)
1037 1041
1042 - def update(self):
1043 cells = self.buffer.windowBrailleCells 1044 # cells might not be the full length of the display. 1045 # Therefore, pad it with spaces to fill the display. 1046 self._cells = cells + [0] * (self.displaySize - len(cells)) 1047 self._cursorPos = self.buffer.cursorWindowPos 1048 self._updateDisplay()
1049
1050 - def scrollForward(self):
1051 self.buffer.scrollForward() 1052 if self.buffer is self.messageBuffer: 1053 self._resetMessageTimer()
1054
1055 - def scrollBack(self):
1056 self.buffer.scrollBack() 1057 if self.buffer is self.messageBuffer: 1058 self._resetMessageTimer()
1059
1060 - def routeTo(self, windowPos):
1061 self.buffer.routeTo(windowPos) 1062 if self.buffer is self.messageBuffer: 1063 self._dismissMessage()
1064
1065 - def message(self, text):
1066 """Display a message to the user which times out after a configured interval. 1067 The timeout will be reset if the user scrolls the display. 1068 The message will be dismissed immediately if the user presses a cursor routing key. 1069 If a key is pressed the message will be dismissed by the next text being written to the display 1070 @postcondition: The message is displayed. 1071 """ 1072 if not self.enabled: 1073 return 1074 if self.buffer is self.messageBuffer: 1075 self.buffer.clear() 1076 else: 1077 self.buffer = self.messageBuffer 1078 region = TextRegion(text) 1079 region.update() 1080 self.buffer.regions.append(region) 1081 self.buffer.update() 1082 self.update() 1083 self._resetMessageTimer() 1084 self._keyCountForLastMessage=keyboardHandler.keyCounter
1085
1086 - def _resetMessageTimer(self):
1087 """Reset the message timeout. 1088 @precondition: A message is currently being displayed. 1089 """ 1090 # Configured timeout is in seconds. 1091 timeout = config.conf["braille"]["messageTimeout"] * 1000 1092 if self._messageCallLater: 1093 self._messageCallLater.Restart(timeout) 1094 else: 1095 self._messageCallLater = wx.CallLater(timeout, self._dismissMessage)
1096
1097 - def _dismissMessage(self):
1098 """Dismiss the current message. 1099 @precondition: A message is currently being displayed. 1100 @postcondition: The display returns to the main buffer. 1101 """ 1102 self.buffer.clear() 1103 self.buffer = self.mainBuffer 1104 self._messageCallLater.Stop() 1105 self._messageCallLater = None 1106 self.update()
1107
1108 - def handleGainFocus(self, obj):
1109 if not self.enabled: 1110 return 1111 if self.tether != self.TETHER_FOCUS: 1112 return 1113 self._doNewObject(itertools.chain(getFocusContextRegions(obj, oldFocusRegions=self.mainBuffer.regions), getFocusRegions(obj)))
1114
1115 - def _doNewObject(self, regions):
1116 self.mainBuffer.clear() 1117 for region in regions: 1118 self.mainBuffer.regions.append(region) 1119 self.mainBuffer.update() 1120 # Last region should receive focus. 1121 self.mainBuffer.focus(region) 1122 if region.brailleCursorPos is not None: 1123 self.mainBuffer.scrollTo(region, region.brailleCursorPos) 1124 if self.buffer is self.mainBuffer: 1125 self.update() 1126 elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage: 1127 self._dismissMessage()
1128
1129 - def handleCaretMove(self, obj):
1130 if not self.enabled: 1131 return 1132 if self.tether != self.TETHER_FOCUS: 1133 return 1134 if not self.mainBuffer.regions: 1135 return 1136 region = self.mainBuffer.regions[-1] 1137 if region.obj is not obj: 1138 return 1139 self._doCursorMove(region)
1140
1141 - def _doCursorMove(self, region):
1142 self.mainBuffer.saveWindow() 1143 region.update() 1144 self.mainBuffer.update() 1145 self.mainBuffer.restoreWindow() 1146 if region.brailleCursorPos is not None: 1147 self.mainBuffer.scrollTo(region, region.brailleCursorPos) 1148 if self.buffer is self.mainBuffer: 1149 self.update() 1150 elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage: 1151 self._dismissMessage()
1152
1153 - def handleUpdate(self, obj):
1154 if not self.enabled: 1155 return 1156 # Optimisation: It is very likely that it is the focus object that is being updated. 1157 # If the focus object is in the braille buffer, it will be the last region, so scan the regions backwards. 1158 for region in reversed(list(self.mainBuffer.visibleRegions)): 1159 if hasattr(region, "obj") and region.obj == obj: 1160 break 1161 else: 1162 # No region for this object. 1163 return 1164 self.mainBuffer.saveWindow() 1165 region.update() 1166 self.mainBuffer.update() 1167 self.mainBuffer.restoreWindow() 1168 if self.buffer is self.mainBuffer: 1169 self.update() 1170 elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage: 1171 self._dismissMessage()
1172
1173 - def handleReviewMove(self):
1174 if not self.enabled: 1175 return 1176 if self.tether != self.TETHER_REVIEW: 1177 return 1178 reviewPos = api.getReviewPosition() 1179 region = self.mainBuffer.regions[-1] if self.mainBuffer.regions else None 1180 if region and region.obj == reviewPos.obj: 1181 self._doCursorMove(region) 1182 else: 1183 # We're reviewing a different object. 1184 self._doNewObject(getFocusRegions(reviewPos.obj, review=True))
1185
1186 -def initialize():
1187 global handler 1188 config.addConfigDirsToPythonPackagePath(brailleDisplayDrivers) 1189 log.info("Using liblouis version %s" % louis.version()) 1190 handler = BrailleHandler() 1191 handler.setDisplayByName(config.conf["braille"]["display"]) 1192 1193 # Update the display to the current focus/review position. 1194 if not handler.enabled or not api.getDesktopObject(): 1195 # Braille is disabled or focus/review hasn't yet been initialised. 1196 return 1197 if handler.tether == handler.TETHER_FOCUS: 1198 handler.handleGainFocus(api.getFocusObject()) 1199 else: 1200 handler.handleReviewMove()
1201
1202 -def terminate():
1203 global handler 1204 handler.terminate() 1205 handler = None
1206
1207 -class BrailleDisplayDriver(baseObject.AutoPropertyObject):
1208 """Abstract base braille display driver. 1209 Each braille display driver should be a separate Python module in the root brailleDisplayDrivers directory containing a BrailleDisplayDriver class which inherits from this base class. 1210 1211 At a minimum, drivers must set L{name} and L{description} and override the L{check} method. 1212 To display braille, L{numCells} and L{display} must be implemented. 1213 1214 Drivers should dispatch input such as presses of buttons, wheels or other controls using the L{inputCore} framework. 1215 They should subclass L{BrailleDisplayGesture} and execute instances of those gestures using L{inputCore.manager.executeGesture}. 1216 These gestures can be mapped in L{gestureMap}. 1217 A driver can also inherit L{baseObject.ScriptableObject} to provide display specific scripts. 1218 """ 1219 #: The name of the braille display; must be the original module file name. 1220 #: @type: str 1221 name = "" 1222 #: A description of the braille display. 1223 #: @type: str 1224 description = "" 1225 1226 @classmethod
1227 - def check(cls):
1228 """Determine whether this braille display is available. 1229 The display will be excluded from the list of available displays if this method returns C{False}. 1230 For example, if this display is not present, C{False} should be returned. 1231 @return: C{True} if this display is available, C{False} if not. 1232 @rtype: bool 1233 """ 1234 return False
1235
1236 - def terminate(self):
1237 """Terminate this display driver. 1238 This will be called when NVDA is finished with this display driver. 1239 It should close any open connections, perform cleanup, etc. 1240 Subclasses should call the superclass method first. 1241 @postcondition: This instance can no longer be used unless it is constructed again. 1242 """ 1243 # Clear the display. 1244 try: 1245 self.display([0] * self.numCells) 1246 except: 1247 # The display driver seems to be failing, but we're terminating anyway, so just ignore it. 1248 pass
1249
1250 - def _get_numCells(self):
1251 """Obtain the number of braille cells on this display. 1252 @note: 0 indicates that braille should be disabled. 1253 @return: The number of cells. 1254 @rtype: int 1255 """ 1256 return 0
1257
1258 - def display(self, cells):
1259 """Display the given braille cells. 1260 @param cells: The braille cells to display. 1261 @type cells: [int, ...] 1262 """
1263 1264 #: Global input gesture map for this display driver. 1265 #: @type: L{inputCore.GlobalGestureMap} 1266 gestureMap = None
1267
1268 -class BrailleDisplayGesture(inputCore.InputGesture):
1269 """A button, wheel or other control pressed on a braille display. 1270 Subclasses must provide L{source} and L{id}. 1271 L{routingIndex} should be provided for routing buttons. 1272 If the braille display driver is a L{baseObject.ScriptableObject}, it can provide scripts specific to input gestures from this display. 1273 """ 1274
1275 - def _get_source(self):
1276 """The string used to identify all gestures from this display. 1277 This should generally be the driver name. 1278 This string will be included in the source portion of gesture identifiers. 1279 For example, if this was C{alvaBC6}, 1280 a display specific gesture identifier might be C{br(alvaBC6):etouch1}. 1281 @rtype: str 1282 """ 1283 raise NotImplementedError
1284
1285 - def _get_id(self):
1286 """The unique, display specific id for this gesture. 1287 @rtype: str 1288 """ 1289 raise NotImplementedError
1290 1291 #: The index of the routing key or C{None} if this is not a routing key. 1292 #: @type: int 1293 routingIndex = None 1294
1295 - def _get_identifiers(self):
1296 return (u"br({source}):{id}".format(source=self.source, id=self.id).lower(),)
1297
1298 - def _get_displayName(self):
1299 return self.id
1300
1301 - def _get_scriptableObject(self):
1302 display = handler.display 1303 if isinstance(display, baseObject.ScriptableObject): 1304 return display 1305 return super(BrailleDisplayGesture, self).scriptableObject
1306