| Trees | Indices | Help |
|---|
|
|
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
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
128 return __import__("brailleDisplayDrivers.%s" % name, globals(), locals(), ("brailleDisplayDrivers",)).BrailleDisplayDriver
129
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
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
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
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
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
227
233
241
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
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
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
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
341
346
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
403
405
409
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
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
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
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
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
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
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
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
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
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
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
631
641
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
649
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
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
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
685 start = 0
686 for region in self.visibleRegions:
687 end = start + len(region.brailleCells)
688 yield region, start, end
689 start = end
690
692 for region, start, end in self.regionsWithPositions:
693 if end > bufferPos:
694 return region, bufferPos - start
695 raise LookupError("No such position")
696
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
714 if not (self.windowStartPos <= bufferPos < self.windowEndPos):
715 raise LookupError("Buffer position not in window")
716 return bufferPos - self.windowStartPos
717
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
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
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
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
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
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
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
820
822 if self.cursorPos is None:
823 return None
824 try:
825 return self.bufferPosToWindowPos(self.cursorPos)
826 except LookupError:
827 return None
828
831
833 pos = self.windowStartPos + windowPos
834 if pos >= self.windowEndPos:
835 return
836 region, pos = self.bufferPosToRegionPos(pos)
837 region.routeTo(pos)
838
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
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
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
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
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
944 TETHER_FOCUS = "focus"
945 TETHER_REVIEW = "review"
946
947 cursorShape = 0xc0
948
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
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
978
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
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
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
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
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
1051 self.buffer.scrollForward()
1052 if self.buffer is self.messageBuffer:
1053 self._resetMessageTimer()
1054
1056 self.buffer.scrollBack()
1057 if self.buffer is self.messageBuffer:
1058 self._resetMessageTimer()
1059
1061 self.buffer.routeTo(windowPos)
1062 if self.buffer is self.messageBuffer:
1063 self._dismissMessage()
1064
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
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
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
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
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
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
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
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
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
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
1206
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
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
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
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
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
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
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
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
1297
1300
1302 display = handler.display
1303 if isinstance(display, baseObject.ScriptableObject):
1304 return display
1305 return super(BrailleDisplayGesture, self).scriptableObject
1306
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Fri Nov 18 17:46:02 2011 | http://epydoc.sourceforge.net |