~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/braille.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#braille.py
2
2
#A part of NonVisual Desktop Access (NVDA)
3
 
#Copyright (C) 2006-2008 NVDA Contributors <http://www.nvda-project.org/>
4
3
#This file is covered by the GNU General Public License.
5
4
#See the file COPYING for more details.
 
5
#Copyright (C) 2008-2011 James Teh <jamie@jantrid.net>, Michael Curran <mick@kulgan.net>
6
6
 
7
7
import itertools
8
8
import os
9
9
import pkgutil
10
10
import wx
11
11
import louis
12
 
import globalVars
 
12
import keyboardHandler
13
13
import baseObject
14
14
import config
15
15
from logHandler import log
18
18
import textInfos
19
19
import speech
20
20
import brailleDisplayDrivers
 
21
import inputCore
21
22
 
22
23
#: The directory in which liblouis braille tables are located.
23
24
TABLES_DIR = r"louis\tables"
46
47
        ("Fr-Ca-g2.ctb", _("French (Canada) grade 2")),
47
48
        ("fr-bfu-comp6.utb", _("French (unified) 6 dot computer braille")),
48
49
        ("fr-bfu-comp8.utb", _("French (unified) 8 dot computer braille")),
49
 
        ("fr-bfu-g2.ctb", _("French (unified) Grade 2")),       ("gr-gr-g1.utb", _("Greek (Greece) grade 1")),
 
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")),
50
53
        ("he.ctb", _("Hebrew 8 dot computer braille")),
51
54
        ("hi-in-g1.utb", _("Hindi grade 1")),
52
55
        ("hr.ctb", _("Croatian 8 dot computer braille")),
55
58
        ("Lv-Lv-g1.utb", _("Latvian grade 1")),
56
59
        ("nl-be-g1.utb", _("Dutch (Belgium) grade 1")),
57
60
        ("Nl-Nl-g1.utb", _("Dutch (netherlands) grade 1")),
 
61
        ("no-no.ctb", _("Norwegian 8 dot computer braille")),
58
62
        ("No-No-g0.utb", _("Norwegian grade 0")),
59
63
        ("No-No-g1.ctb", _("Norwegian grade 1")),
60
64
        ("No-No-g2.ctb", _("Norwegian grade 2")),
64
68
        ("ru-ru-g1.utb", _("Russian grade 1")),
65
69
        ("Se-Se-g1.utb", _("Swedish grade 1")),
66
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")),
67
74
        ("UEBC-g1.utb", _("Unified English Braille Code grade 1")),
68
75
        ("UEBC-g2.ctb", _("Unified English Braille Code grade 2")),
69
76
        ("zh-hk.ctb", _("Chinese (Hong Kong, Cantonese)")),
70
77
        ("zh-tw.ctb", _("Chinese (Taiwan, Mandarin)")),
 
78
#       ("nvdajp.ctb", _("Japanese by NVDAJP")),
71
79
)
72
80
 
73
81
roleLabels = {
86
94
}
87
95
 
88
96
positiveStateLabels = {
 
97
        # Translators: Displayed in braille when an object (e.g. a check box) is checked.
89
98
        controlTypes.STATE_CHECKED: _("(x)"),
 
99
        # Translators: Displayed in braille when an object (e.g. a check box) is half checked.
 
100
        controlTypes.STATE_HALFCHECKED: _("(-)"),
 
101
        # Translators: Displayed in braille when an object is selected.
90
102
        controlTypes.STATE_SELECTED: _("sel"),
 
103
        # Translators: Displayed in braille when an object has a popup (usually a sub-menu).
91
104
        controlTypes.STATE_HASPOPUP: _("submnu"),
 
105
        # Translators: Displayed in braille when an object supports autocompletion.
 
106
        controlTypes.STATE_AUTOCOMPLETE: _("..."),
 
107
        # Translators: Displayed in braille when an object (e.g. a tree view item) is expanded.
 
108
        controlTypes.STATE_EXPANDED: _("-"),
 
109
        # Translators: Displayed in braille when an object (e.g. a tree view item) is collapsed.
 
110
        controlTypes.STATE_COLLAPSED: _("+"),
 
111
        # Translators: Displayed in braille when an object (e.g. an editable text field) is read-only.
 
112
        controlTypes.STATE_READONLY: _("ro"),
92
113
}
93
114
negativeStateLabels = {
 
115
        # Translators: Displayed in braille when an object (e.g. a check box) is not checked.
94
116
        controlTypes.STATE_CHECKED: _("( )"),
95
117
}
96
118
 
 
119
def NVDAObjectHasUsefulText(obj):
 
120
        import displayModel
 
121
        return issubclass(obj.TextInfo,displayModel.DisplayModelTextInfo) or obj.role in (controlTypes.ROLE_EDITABLETEXT, controlTypes.ROLE_TERMINAL) or controlTypes.STATE_EDITABLE in obj.states
 
122
 
97
123
def _getDisplayDriver(name):
98
124
        return __import__("brailleDisplayDrivers.%s" % name, globals(), locals(), ("brailleDisplayDrivers",)).BrailleDisplayDriver
99
125
 
154
180
                if config.conf["braille"]["expandAtCursor"] and self.cursorPos is not None:
155
181
                        mode |= louis.compbrlAtCursor
156
182
                text=unicode(self.rawText).replace('\0','')
157
 
                braille, self.brailleToRawPos, self.rawToBraillePos, brailleCursorPos = louis.translate([os.path.join(TABLES_DIR, config.conf["braille"]["translationTable"])], text, mode=mode, cursorPos=self.cursorPos or 0)
 
183
                # nvdajp begin
 
184
                #if config.conf["braille"]["translationTable"] == 'nvdajp.ctb':
 
185
                #       from nvdajptext import Wakach
 
186
                #       text=Wakach.japanese_braille_translate(text)
 
187
                # nvdajp end
 
188
                braille, self.brailleToRawPos, self.rawToBraillePos, brailleCursorPos = louis.translate(
 
189
                        [os.path.join(TABLES_DIR, config.conf["braille"]["translationTable"]),
 
190
                                "braille-patterns.cti"],
 
191
                        text, mode=mode, cursorPos=self.cursorPos or 0)
158
192
                # liblouis gives us back a character string of cells, so convert it to a list of ints.
159
193
                # For some reason, the highest bit is set, so only grab the lower 8 bits.
160
194
                self.brailleCells = [ord(cell) & 255 for cell in braille]
176
210
                @type braillePos: int
177
211
                @note: If routing the cursor, L{brailleToRawPos} can be used to translate L{braillePos} into a position in L{rawText}.
178
212
                """
179
 
                pass
180
213
 
181
214
        def nextLine(self):
182
215
                """Move to the next line if possible.
183
216
                """
184
 
                pass
185
217
 
186
 
        def previousLine(self):
 
218
        def previousLine(self, start=False):
187
219
                """Move to the previous line if possible.
 
220
                @param start: C{True} to move to the start of the line, C{False} to move to the end.
 
221
                @type start: bool
188
222
                """
189
 
                pass
190
223
 
191
224
class TextRegion(Region):
192
225
        """A simple region containing a string of text.
228
261
        keyboardShortcut = propertyValues.get("keyboardShortcut")
229
262
        if keyboardShortcut:
230
263
                textList.append(keyboardShortcut)
231
 
        positionInfo = propertyValues["positionInfo"]
 
264
        positionInfo = propertyValues.get("positionInfo")
232
265
        if positionInfo:
233
 
                if 'indexInGroup' in positionInfo and 'similarItemsInGroup' in positionInfo:
234
 
                        textList.append(_("%s of %s")%(positionInfo['indexInGroup'],positionInfo['similarItemsInGroup']))
235
 
                if 'level' in positionInfo:
236
 
                        textList.append(_('level %s')%positionInfo['level'])
 
266
                indexInGroup = positionInfo.get("indexInGroup")
 
267
                similarItemsInGroup = positionInfo.get("similarItemsInGroup")
 
268
                if indexInGroup and similarItemsInGroup:
 
269
                        textList.append(_("%s of %s") % (indexInGroup, similarItemsInGroup))
 
270
                level = positionInfo.get("level")
 
271
                if level is not None:
 
272
                        # Translators: Displayed in braille when an object (e.g. a tree view item) has a hierarchical level.
 
273
                        # %s is replaced with the level.
 
274
                        textList.append(_('lv %s')%positionInfo['level'])
237
275
        return " ".join([x for x in textList if x])
238
276
 
239
277
class NVDAObjectRegion(Region):
255
293
 
256
294
        def update(self):
257
295
                obj = self.obj
258
 
                text = getBrailleTextForProperties(name=obj.name, role=obj.role, value=obj.value, states=obj.states, description=obj.description, keyboardShortcut=obj.keyboardShortcut, positionInfo=obj.positionInfo)
 
296
                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)
259
297
                self.rawText = text + self.appendText
260
298
                super(NVDAObjectRegion, self).update()
261
299
 
277
315
                self.obj = obj
278
316
 
279
317
        def _isMultiline(self):
280
 
                #A regions object can either be an NVDAObject or a virtualBuffer
281
 
                #virtualBuffers should always be multiline
282
 
                import virtualBuffers
283
 
                if isinstance(self.obj,virtualBuffers.VirtualBuffer):
 
318
                # A region's object can either be an NVDAObject or a tree interceptor.
 
319
                # Tree interceptors should always be multiline.
 
320
                from treeInterceptorHandler import TreeInterceptor
 
321
                if isinstance(self.obj, TreeInterceptor):
284
322
                        return True
285
323
                # Terminals are inherently multiline, so they don't have the multiline state.
286
324
                return (self.obj.role == controlTypes.ROLE_TERMINAL or controlTypes.STATE_MULTILINE in self.obj.states)
295
333
                except:
296
334
                        return self.obj.makeTextInfo(textInfos.POSITION_FIRST)
297
335
 
298
 
        def _setSelection(self, info):
299
 
                """Set the selection.
300
 
                @param info: The range to which the selection should be moved.
 
336
        def _setCursor(self, info):
 
337
                """Set the cursor.
 
338
                @param info: The range to which the cursor should be moved.
301
339
                @type info: L{textInfos.TextInfo}
302
340
                """
303
341
                try:
304
 
                        info.updateSelection()
 
342
                        info.updateCaret()
305
343
                except NotImplementedError:
306
344
                        log.debugWarning("", exc_info=True)
307
345
 
342
380
                dest.collapse()
343
381
                # and move pos characters from there.
344
382
                dest.move(textInfos.UNIT_CHARACTER, pos)
345
 
                self._setSelection(dest)
 
383
                self._setCursor(dest)
346
384
 
347
385
        def nextLine(self):
348
386
                dest = self._line.copy()
350
388
                if not moved:
351
389
                        return
352
390
                dest.collapse()
353
 
                self._setSelection(dest)
 
391
                self._setCursor(dest)
354
392
 
355
 
        def previousLine(self):
 
393
        def previousLine(self, start=False):
356
394
                dest = self._line.copy()
357
395
                dest.collapse()
358
 
                # Move to the last character of the previous line.
359
 
                moved = dest.move(textInfos.UNIT_CHARACTER, -1)
 
396
                # If the end of the line is desired, move to the last character.
 
397
                moved = dest.move(textInfos.UNIT_LINE if start else textInfos.UNIT_CHARACTER, -1)
360
398
                if not moved:
361
399
                        return
362
400
                dest.collapse()
363
 
                self._setSelection(dest)
 
401
                self._setCursor(dest)
364
402
 
365
403
class CursorManagerRegion(TextInfoRegion):
366
404
 
370
408
        def _getSelection(self):
371
409
                return self.obj.selection
372
410
 
373
 
        def _setSelection(self, info):
 
411
        def _setCursor(self, info):
374
412
                self.obj.selection = info
375
413
 
376
414
class ReviewTextInfoRegion(TextInfoRegion):
378
416
        def _getSelection(self):
379
417
                return api.getReviewPosition().copy()
380
418
 
381
 
        def _setSelection(self, info):
 
419
        def _setCursor(self, info):
382
420
                api.setReviewPosition(info)
383
421
 
384
422
class BrailleBuffer(baseObject.AutoPropertyObject):
430
468
                                return region, bufferPos - start
431
469
                raise LookupError("No such position")
432
470
 
433
 
        def regionPosToBufferPos(self, region, pos):
 
471
        def regionPosToBufferPos(self, region, pos, allowNearest=False):
434
472
                for testRegion, start, end in self.regionsWithPositions:
435
473
                        if region == testRegion:
436
 
                                if pos < end:
 
474
                                if pos < end - start:
 
475
                                        # The requested position is still valid within the region.
437
476
                                        return start + pos
 
477
                                elif allowNearest:
 
478
                                        # The position within the region isn't valid,
 
479
                                        # but the region is valid, so return its start.
 
480
                                        return start
 
481
                                break
 
482
                if allowNearest:
 
483
                        # Resort to the start of the last region.
 
484
                        return start
438
485
                raise LookupError("No such position")
439
486
 
440
487
        def bufferPosToWindowPos(self, bufferPos):
551
598
                """
552
599
                self._savedWindow = self.bufferPosToRegionPos(self.windowStartPos)
553
600
 
554
 
        def restoreWindow(self, ignoreErrors=False):
 
601
        def restoreWindow(self):
555
602
                """Restore the window saved by L{saveWindow}.
556
 
                @param ignoreErrors: Whether to ignore errors.
557
 
                @type ignoreErrors: bool
558
603
                @precondition: L{saveWindow} has been called.
559
604
                @postcondition: If the saved position is valid, the window is restored.
560
 
                @raise LookupError: If C{ignoreErrors} is C{False} and the saved region position is invalid.
 
605
                        Otherwise, the nearest position is restored.
561
606
                """
562
 
                try:
563
 
                        self.windowStartPos = self.regionPosToBufferPos(*self._savedWindow)
564
 
                except LookupError:
565
 
                        if not ignoreErrors:
566
 
                                raise
 
607
                region, pos = self._savedWindow
 
608
                self.windowStartPos = self.regionPosToBufferPos(region, pos, allowNearest=True)
567
609
 
568
610
_cachedFocusAncestorsEnd = 0
569
611
def invalidateCachedFocusAncestors(index):
580
622
def getFocusContextRegions(obj, oldFocusRegions=None):
581
623
        global _cachedFocusAncestorsEnd
582
624
        # Late import to avoid circular import.
583
 
        from virtualBuffers import VirtualBuffer
 
625
        from treeInterceptorHandler import TreeInterceptor
584
626
        ancestors = api.getFocusAncestors()
585
627
 
586
628
        ancestorsEnd = len(ancestors)
587
 
        if isinstance(obj, VirtualBuffer):
 
629
        if isinstance(obj, TreeInterceptor):
588
630
                obj = obj.rootNVDAObject
589
631
                # We only want the ancestors of the buffer's root NVDAObject.
590
632
                if obj != api.getFocusObject():
634
676
 
635
677
def getFocusRegions(obj, review=False):
636
678
        # Late import to avoid circular import.
637
 
        from virtualBuffers import VirtualBuffer
 
679
        from treeInterceptorHandler import TreeInterceptor
638
680
        from cursorManager import CursorManager
639
681
        if isinstance(obj, CursorManager):
640
682
                region2 = (ReviewTextInfoRegion if review else CursorManagerRegion)(obj)
641
 
        elif (obj.role in (controlTypes.ROLE_EDITABLETEXT, controlTypes.ROLE_TERMINAL) or controlTypes.STATE_EDITABLE in obj.states):
 
683
        elif isinstance(obj, TreeInterceptor) or NVDAObjectHasUsefulText(obj): 
642
684
                region2 = (ReviewTextInfoRegion if review else TextInfoRegion)(obj)
643
685
        else:
644
686
                region2 = None
645
 
        if isinstance(obj, VirtualBuffer):
 
687
        if isinstance(obj, TreeInterceptor):
646
688
                obj = obj.rootNVDAObject
647
689
        region = (ReviewNVDAObjectRegion if review else NVDAObjectRegion)(obj, appendText=" " if region2 else "")
648
690
        region.update()
655
697
        TETHER_FOCUS = "focus"
656
698
        TETHER_REVIEW = "review"
657
699
 
 
700
        cursorShape = 0xc0
 
701
 
658
702
        def __init__(self):
659
703
                self.display = None
660
704
                self.displaySize = 0
662
706
                self.messageBuffer = BrailleBuffer(self)
663
707
                self._messageCallLater = None
664
708
                self.buffer = self.mainBuffer
665
 
                #config.conf["braille"]["tetherTo"] = self.TETHER_FOCUS
666
709
                #: Whether braille is enabled.
667
710
                #: @type: bool
668
711
                self.enabled = False
669
 
                self._keyCounterForLastMessage=0
 
712
                self._keyCountForLastMessage=0
 
713
                self._cursorPos = None
 
714
                self._cursorBlinkUp = True
 
715
                self._cells = []
 
716
                self._cursorBlinkTimer = None
 
717
 
 
718
        def terminate(self):
 
719
                if self._messageCallLater:
 
720
                        self._messageCallLater.Stop()
 
721
                        self._messageCallLater = None
 
722
                if self._cursorBlinkTimer:
 
723
                        self._cursorBlinkTimer.Stop()
 
724
                        self._cursorBlinkTimer = None
 
725
                if self.display:
 
726
                        self.display.terminate()
 
727
                        self.display = None
670
728
 
671
729
        def _get_tether(self):
672
730
                return config.conf["braille"]["tetherTo"]
696
754
                        else:
697
755
                                newDisplay = newDisplay()
698
756
                                if self.display:
699
 
                                        self.display.terminate()
 
757
                                        try:
 
758
                                                self.display.terminate()
 
759
                                        except:
 
760
                                                log.error("Error terminating previous display driver", exc_info=True)
700
761
                                self.display = newDisplay
701
762
                        self.displaySize = newDisplay.numCells
702
763
                        self.enabled = bool(self.displaySize)
703
764
                        config.conf["braille"]["display"] = name
704
765
                        log.info("Loaded braille display driver %s" % name)
705
 
                        self.configDisplay()
706
766
                        return True
707
767
                except:
708
768
                        log.error("Error initializing display driver", exc_info=True)
709
769
                        self.setDisplayByName("noBraille")
710
770
                        return False
711
771
 
712
 
        def configDisplay(self):
713
 
                """Configure the braille display driver based on the user's configuration.
714
 
                @precondition: L{display} has been set.
715
 
                """
716
 
                self.display.cursorBlinkRate = config.conf["braille"]["cursorBlinkRate"]
717
 
                self.display.cursorShape = 0xc0
 
772
        def _updateDisplay(self):
 
773
                if self._cursorBlinkTimer:
 
774
                        self._cursorBlinkTimer.Stop()
 
775
                        self._cursorBlinkTimer = None
 
776
                self._cursorBlinkUp = True
 
777
                self._displayWithCursor()
 
778
                blinkRate = config.conf["braille"]["cursorBlinkRate"]
 
779
                if blinkRate and self._cursorPos is not None:
 
780
                        self._cursorBlinkTimer = wx.PyTimer(self._blink)
 
781
                        self._cursorBlinkTimer.Start(blinkRate)
 
782
 
 
783
        def _displayWithCursor(self):
 
784
                if not self._cells:
 
785
                        return
 
786
                cells = list(self._cells)
 
787
                if self._cursorPos is not None and self._cursorBlinkUp:
 
788
                        cells[self._cursorPos] |= self.cursorShape
 
789
                self.display.display(cells)
 
790
 
 
791
        def _blink(self):
 
792
                self._cursorBlinkUp = not self._cursorBlinkUp
 
793
                self._displayWithCursor()
718
794
 
719
795
        def update(self):
720
 
                self.display.display(self.buffer.windowBrailleCells)
721
 
                self.display.cursorPos = self.buffer.cursorWindowPos
 
796
                cells = self.buffer.windowBrailleCells
 
797
                # cells might not be the full length of the display.
 
798
                # Therefore, pad it with spaces to fill the display.
 
799
                self._cells = cells + [0] * (self.displaySize - len(cells))
 
800
                self._cursorPos = self.buffer.cursorWindowPos
 
801
                self._updateDisplay()
722
802
 
723
803
        def scrollForward(self):
724
804
                self.buffer.scrollForward()
754
834
                self.buffer.update()
755
835
                self.update()
756
836
                self._resetMessageTimer()
757
 
                self._keyCountForLastMessage=globalVars.keyCounter
 
837
                self._keyCountForLastMessage=keyboardHandler.keyCounter
758
838
 
759
839
        def _resetMessageTimer(self):
760
840
                """Reset the message timeout.
796
876
                        self.mainBuffer.scrollTo(region, region.brailleCursorPos)
797
877
                if self.buffer is self.mainBuffer:
798
878
                        self.update()
799
 
                elif self.buffer is self.messageBuffer and globalVars.keyCounter>self._keyCountForLastMessage:
 
879
                elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage:
800
880
                        self._dismissMessage()
801
881
 
802
882
        def handleCaretMove(self, obj):
815
895
                self.mainBuffer.saveWindow()
816
896
                region.update()
817
897
                self.mainBuffer.update()
818
 
                self.mainBuffer.restoreWindow(ignoreErrors=True)
 
898
                self.mainBuffer.restoreWindow()
819
899
                if region.brailleCursorPos is not None:
820
900
                        self.mainBuffer.scrollTo(region, region.brailleCursorPos)
821
901
                if self.buffer is self.mainBuffer:
822
902
                        self.update()
823
 
                elif self.buffer is self.messageBuffer and globalVars.keyCounter>self._keyCountForLastMessage:
 
903
                elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage:
824
904
                        self._dismissMessage()
825
905
 
826
906
        def handleUpdate(self, obj):
837
917
                self.mainBuffer.saveWindow()
838
918
                region.update()
839
919
                self.mainBuffer.update()
840
 
                self.mainBuffer.restoreWindow(ignoreErrors=True)
 
920
                self.mainBuffer.restoreWindow()
841
921
                if self.buffer is self.mainBuffer:
842
922
                        self.update()
843
 
                elif self.buffer is self.messageBuffer and globalVars.keyCounter>self._keyCountForLastMessage:
 
923
                elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage:
844
924
                        self._dismissMessage()
845
925
 
846
926
        def handleReviewMove(self):
863
943
        handler = BrailleHandler()
864
944
        handler.setDisplayByName(config.conf["braille"]["display"])
865
945
 
 
946
        # Update the display to the current focus/review position.
 
947
        if not handler.enabled or not api.getDesktopObject():
 
948
                # Braille is disabled or focus/review hasn't yet been initialised.
 
949
                return
 
950
        if handler.tether == handler.TETHER_FOCUS:
 
951
                handler.handleGainFocus(api.getFocusObject())
 
952
        else:
 
953
                handler.handleReviewMove()
 
954
 
866
955
def terminate():
867
956
        global handler
868
 
        if handler.display:
869
 
                handler.display.terminate()
 
957
        handler.terminate()
870
958
        handler = None
871
959
 
872
960
class BrailleDisplayDriver(baseObject.AutoPropertyObject):
874
962
        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.
875
963
        
876
964
        At a minimum, drivers must set L{name} and L{description} and override the L{check} method.
 
965
        To display braille, L{numCells} and L{display} must be implemented.
 
966
        
 
967
        Drivers should dispatch input such as presses of buttons, wheels or other controls using the L{inputCore} framework.
 
968
        They should subclass L{BrailleDisplayGesture} and execute instances of those gestures using L{inputCore.manager.executeGesture}.
 
969
        These gestures can be mapped in L{gestureMap}.
 
970
        A driver can also inherit L{baseObject.ScriptableObject} to provide display specific scripts.
877
971
        """
878
972
        #: The name of the braille display; must be the original module file name.
879
973
        #: @type: str
900
994
                @postcondition: This instance can no longer be used unless it is constructed again.
901
995
                """
902
996
                # Clear the display.
903
 
                self.cursorPos = None
904
 
                self.cursorBlinkRate = 0
905
 
                self.display([])
 
997
                try:
 
998
                        self.display([0] * self.numCells)
 
999
                except:
 
1000
                        # The display driver seems to be failing, but we're terminating anyway, so just ignore it.
 
1001
                        pass
906
1002
 
907
1003
        def _get_numCells(self):
908
1004
                """Obtain the number of braille cells on this  display.
917
1013
                @param cells: The braille cells to display.
918
1014
                @type cells: [int, ...]
919
1015
                """
920
 
                pass
921
 
 
922
 
        def _get_cursorPos(self):
923
 
                return None
924
 
 
925
 
        def _set_cursorPos(self, pos):
926
 
                pass
927
 
 
928
 
        def _get_cursorShape(self):
929
 
                return None
930
 
 
931
 
        def _set_cursorShape(self, shape):
932
 
                pass
933
 
 
934
 
        def _get_cursorBlinkRate(self):
935
 
                return 0
936
 
 
937
 
        def _set_cursorBlinkRate(self, rate):
938
 
                pass
939
 
 
940
 
class BrailleDisplayDriverWithCursor(BrailleDisplayDriver):
941
 
        """Abstract base braille display driver which manages its own cursor.
942
 
        This should be used by braille display drivers where the display or underlying driver does not provide support for a cursor.
943
 
        Instead of overriding L{display}, subclasses should override L{_display}.
 
1016
 
 
1017
        #: Global input gesture map for this display driver.
 
1018
        #: @type: L{inputCore.GlobalGestureMap}
 
1019
        gestureMap = None
 
1020
 
 
1021
class BrailleDisplayGesture(inputCore.InputGesture):
 
1022
        """A button, wheel or other control pressed on a braille display.
 
1023
        Subclasses must provide L{source} and L{id}.
 
1024
        L{routingIndex} should be provided for routing buttons.
 
1025
        If the braille display driver is a L{baseObject.ScriptableObject}, it can provide scripts specific to input gestures from this display.
944
1026
        """
945
1027
 
946
 
        def __init__(self):
947
 
                self._cursorPos = None
948
 
                self._cursorBlinkRate = 0
949
 
                self._cursorBlinkUp = True
950
 
                self._cursorShape = 0
951
 
                self._cells = []
952
 
                self._cursorBlinkTimer = None
953
 
                self._initCursor()
954
 
 
955
 
        def _initCursor(self):
956
 
                if self._cursorBlinkTimer:
957
 
                        self._cursorBlinkTimer.Stop()
958
 
                        self._cursorBlinkTimer = None
959
 
                self._cursorBlinkUp = True
960
 
                self._displayWithCursor()
961
 
                if self._cursorBlinkRate and self._cursorPos is not None:
962
 
                        self._cursorBlinkTimer = wx.PyTimer(self._blink)
963
 
                        self._cursorBlinkTimer.Start(self._cursorBlinkRate)
964
 
 
965
 
        def _blink(self):
966
 
                self._cursorBlinkUp = not self._cursorBlinkUp
967
 
                self._displayWithCursor()
968
 
 
969
 
        def _get_cursorPos(self):
970
 
                return self._cursorPos
971
 
 
972
 
        def _set_cursorPos(self, pos):
973
 
                self._cursorPos = pos
974
 
                self._initCursor()
975
 
 
976
 
        def _get_cursorBlinkRate(self):
977
 
                return self._cursorBlinkRate
978
 
 
979
 
        def _set_cursorBlinkRate(self, rate):
980
 
                self._cursorBlinkRate = rate
981
 
                self._initCursor()
982
 
 
983
 
        def _get_cursorShape(self):
984
 
                return self._cursorShape
985
 
 
986
 
        def _set_cursorShape(self, shape):
987
 
                self._cursorShape = shape
988
 
                self._initCursor()
989
 
 
990
 
        def display(self, cells):
991
 
                # cells might not be the full length of the display.
992
 
                # Therefore, pad it with spaces to fill the display so that the cursor can lie beyond it.
993
 
                self._cells = cells + [0] * (self.numCells - len(cells))
994
 
                self._displayWithCursor()
995
 
 
996
 
        def _displayWithCursor(self):
997
 
                if not self._cells:
998
 
                        return
999
 
                cells = list(self._cells)
1000
 
                if self._cursorPos is not None and self._cursorBlinkUp:
1001
 
                        cells[self._cursorPos] |= self._cursorShape
1002
 
                self._display(cells)
1003
 
 
1004
 
        def _display(self, cells):
1005
 
                """Actually display the given cells to the display.
1006
 
                L{display} calls methods to handle the cursor representation as appropriate.
1007
 
                However, this method (L{_display}) is called to actually display the final cells.
1008
 
                """
1009
 
                pass
 
1028
        def _get_source(self):
 
1029
                """The string used to identify all gestures from this display.
 
1030
                This should generally be the driver name.
 
1031
                This string will be included in the source portion of gesture identifiers.
 
1032
                For example, if this was C{alvaBC6},
 
1033
                a display specific gesture identifier might be C{br(alvaBC6):etouch1}.
 
1034
                @rtype: str
 
1035
                """
 
1036
                raise NotImplementedError
 
1037
 
 
1038
        def _get_id(self):
 
1039
                """The unique, display specific id for this gesture.
 
1040
                @rtype: str
 
1041
                """
 
1042
                raise NotImplementedError
 
1043
 
 
1044
        #: The index of the routing key or C{None} if this is not a routing key.
 
1045
        #: @type: int
 
1046
        routingIndex = None
 
1047
 
 
1048
        def _get_identifiers(self):
 
1049
                return (u"br({source}):{id}".format(source=self.source, id=self.id).lower(),)
 
1050
 
 
1051
        def _get_displayName(self):
 
1052
                return self.id
 
1053
 
 
1054
        def _get_scriptableObject(self):
 
1055
                display = handler.display
 
1056
                if isinstance(display, baseObject.ScriptableObject):
 
1057
                        return display
 
1058
                return super(BrailleDisplayGesture, self).scriptableObject