home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / var / lib / python-support / python2.6 / orca / flat_review.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  41.3 KB  |  1,504 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Provides the default implementation for flat review for Orca.'''
  5. __id__ = '$Id: flat_review.py 4471 2009-01-28 17:52:43Z wwalker $'
  6. __version__ = '$Revision: 4471 $'
  7. __date__ = '$Date: 2009-01-28 12:52:43 -0500 (Wed, 28 Jan 2009) $'
  8. __copyright__ = 'Copyright (c) 2005-2008 Sun Microsystems Inc.'
  9. __license__ = 'LGPL'
  10. import re
  11. import pyatspi
  12. import braille
  13. import debug
  14. import eventsynthesizer
  15. import orca_state
  16. import rolenames
  17. import settings
  18. from orca_i18n import _
  19. from orca_i18n import C_
  20. whitespace_re = re.compile('(\\s+)', re.DOTALL | re.IGNORECASE | re.M)
  21.  
  22. class Char:
  23.     '''Represents a single char of an Accessibility_Text object.'''
  24.     
  25.     def __init__(self, word, index, string, x, y, width, height):
  26.         '''Creates a new char.
  27.  
  28.         Arguments:
  29.         - word: the Word instance this belongs to
  30.         - index: the index of this char in the word
  31.         - string: the actual char
  32.         - x, y, width, height: the extents of this Char on the screen
  33.         '''
  34.         self.word = word
  35.         self.string = string
  36.         self.index = index
  37.         self.x = x
  38.         self.y = y
  39.         self.width = width
  40.         self.height = height
  41.  
  42.  
  43.  
  44. class Word:
  45.     '''Represents a single word of an Accessibility_Text object, or
  46.     the entire name of an Image or Component if the associated object
  47.     does not implement the Accessibility_Text interface.  As a rule of
  48.     thumb, all words derived from an Accessibility_Text interface will
  49.     start with the word and will end with all chars up to the
  50.     beginning of the next word.  That is, whitespace and punctuation
  51.     will usually be tacked on to the end of words.'''
  52.     
  53.     def __init__(self, zone, index, startOffset, string, x, y, width, height):
  54.         '''Creates a new Word.
  55.  
  56.         Arguments:
  57.         - zone: the Zone instance this belongs to
  58.         - index: the index of this word in the Zone
  59.         - string: the actual string
  60.         - x, y, width, height: the extents of this Char on the screen'''
  61.         self.zone = zone
  62.         self.index = index
  63.         self.startOffset = startOffset
  64.         self.string = string
  65.         self.length = len(string.decode('UTF-8'))
  66.         self.x = x
  67.         self.y = y
  68.         self.width = width
  69.         self.height = height
  70.  
  71.     
  72.     def __getattr__(self, attr):
  73.         '''Used for lazily determining the chars of a word.  We do
  74.         this to reduce the total number of round trip calls to the app,
  75.         and to also spread the round trip calls out over the lifetime
  76.         of a flat review context.
  77.  
  78.         Arguments:
  79.         - attr: a string indicating the attribute name to retrieve
  80.  
  81.         Returns the value of the given attribute.
  82.         '''
  83.         if attr == 'chars':
  84.             if isinstance(self.zone, TextZone):
  85.                 text = self.zone.accessible.queryText()
  86.                 self.chars = []
  87.                 i = 0
  88.                 while i < self.length:
  89.                     (char, startOffset, endOffset) = text.getTextAtOffset(self.startOffset + i, pyatspi.TEXT_BOUNDARY_CHAR)
  90.                     if len(char):
  91.                         char = char.decode('UTF-8')[0].encode('UTF-8')
  92.                     
  93.                     (x, y, width, height) = text.getRangeExtents(startOffset, startOffset + 1, 0)
  94.                     self.chars.append(Char(self, i, char, x, y, width, height))
  95.                     i += 1
  96.             else:
  97.                 self.chars = None
  98.             return self.chars
  99.         if attr.startswith('__') and attr.endswith('__'):
  100.             raise AttributeError, attr
  101.         attr.endswith('__')
  102.         return self.__dict__[attr]
  103.  
  104.  
  105.  
  106. class Zone:
  107.     '''Represents text that is a portion of a single horizontal line.'''
  108.     
  109.     def __init__(self, accessible, string, x, y, width, height, role = None):
  110.         """Creates a new Zone, which is a horizontal region of text.
  111.  
  112.         Arguments:
  113.         - accessible: the Accessible associated with this Zone
  114.         - string: the string being displayed for this Zone
  115.         - extents: x, y, width, height in screen coordinates
  116.         - role: Role to override accesible's role.
  117.         """
  118.         self.accessible = accessible
  119.         self.string = string
  120.         self.length = len(string.decode('UTF-8'))
  121.         self.x = x
  122.         self.y = y
  123.         self.width = width
  124.         self.height = height
  125.         if not role:
  126.             pass
  127.         self.role = accessible.getRole()
  128.  
  129.     
  130.     def __getattr__(self, attr):
  131.         '''Used for lazily determining the words in a Zone.
  132.  
  133.         Arguments:
  134.         - attr: a string indicating the attribute name to retrieve
  135.  
  136.         Returns the value of the given attribute.
  137.         '''
  138.         if attr == 'words':
  139.             self.words = []
  140.             return self.words
  141.         if attr.startswith('__') and attr.endswith('__'):
  142.             raise AttributeError, attr
  143.         attr.endswith('__')
  144.         return self.__dict__[attr]
  145.  
  146.     
  147.     def onSameLine(self, zone):
  148.         '''Returns True if this Zone is on the same horiztonal line as
  149.         the given zone.'''
  150.         highestBottom = min(self.y + self.height, zone.y + zone.height)
  151.         lowestTop = max(self.y, zone.y)
  152.         if lowestTop < highestBottom:
  153.             overlapAmount = highestBottom - lowestTop
  154.             shortestHeight = min(self.height, zone.height)
  155.             return 1 * overlapAmount / shortestHeight > 0.25
  156.         return False
  157.  
  158.     
  159.     def getWordAtOffset(self, charOffset):
  160.         wordAtOffset = None
  161.         offset = 0
  162.         for word in self.words:
  163.             nextOffset = offset + len(word.string.decode('UTF-8'))
  164.             wordAtOffset = word
  165.             if nextOffset > charOffset:
  166.                 return [
  167.                     wordAtOffset,
  168.                     charOffset - offset]
  169.             offset = nextOffset
  170.         
  171.         return [
  172.             wordAtOffset,
  173.             offset]
  174.  
  175.  
  176.  
  177. class TextZone(Zone):
  178.     '''Represents Accessibility_Text that is a portion of a single
  179.     horizontal line.'''
  180.     
  181.     def __init__(self, accessible, startOffset, string, x, y, width, height):
  182.         '''Creates a new Zone, which is a horizontal region of text.
  183.  
  184.         Arguments:
  185.         - accessible: the Accessible associated with this Zone
  186.         - startOffset: the index of the char in the Accessibility_Text
  187.                        interface where this Zone starts
  188.         - string: the string being displayed for this Zone
  189.         - extents: x, y, width, height in screen coordinates
  190.         '''
  191.         Zone.__init__(self, accessible, string, x, y, width, height)
  192.         self.startOffset = startOffset
  193.  
  194.     
  195.     def __getattr__(self, attr):
  196.         '''Used for lazily determining the words in a Zone.  The words
  197.         will either be all whitespace (interword boundaries) or actual
  198.         words.  To determine if a Word is whitespace, use
  199.         word.string.isspace()
  200.  
  201.         Arguments:
  202.         - attr: a string indicating the attribute name to retrieve
  203.  
  204.         Returns the value of the given attribute.
  205.         '''
  206.         if attr == 'words':
  207.             text = self.accessible.queryText()
  208.             self.words = []
  209.             wordIndex = 0
  210.             offset = self.startOffset
  211.             for string in whitespace_re.split(self.string):
  212.                 if len(string):
  213.                     endOffset = offset + len(string.decode('UTF-8'))
  214.                     (x, y, width, height) = text.getRangeExtents(offset, endOffset, 0)
  215.                     word = Word(self, wordIndex, offset, string, x, y, width, height)
  216.                     self.words.append(word)
  217.                     wordIndex += 1
  218.                     offset = endOffset
  219.                     continue
  220.             
  221.             return self.words
  222.         if attr.startswith('__') and attr.endswith('__'):
  223.             raise AttributeError, attr
  224.         attr.endswith('__')
  225.         return self.__dict__[attr]
  226.  
  227.  
  228.  
  229. class StateZone(Zone):
  230.     '''Represents a Zone for an accessible that shows a state using
  231.     a graphical indicator, such as a checkbox or radio button.'''
  232.     
  233.     def __init__(self, accessible, x, y, width, height, role = None):
  234.         Zone.__init__(self, accessible, '', x, y, width, height, role)
  235.         del self.string
  236.  
  237.     
  238.     def __getattr__(self, attr):
  239.         if attr in ('string', 'length', 'brailleString'):
  240.             stateset = self.accessible.getState()
  241.             if stateset.contains(pyatspi.STATE_INDETERMINATE):
  242.                 stateCount = 2
  243.             elif stateset.contains(pyatspi.STATE_CHECKED):
  244.                 stateCount = 1
  245.             else:
  246.                 stateCount = 0
  247.             if self.role in [
  248.                 pyatspi.ROLE_CHECK_BOX,
  249.                 pyatspi.ROLE_CHECK_MENU_ITEM,
  250.                 pyatspi.ROLE_TABLE_CELL]:
  251.                 if stateCount == 2:
  252.                     speechState = _('partially checked')
  253.                 elif stateCount == 1:
  254.                     speechState = _('checked')
  255.                 else:
  256.                     speechState = _('not checked')
  257.                 brailleState = settings.brailleCheckBoxIndicators[stateCount]
  258.             elif self.role == pyatspi.ROLE_TOGGLE_BUTTON:
  259.                 if stateCount:
  260.                     speechState = _('pressed')
  261.                 else:
  262.                     speechState = _('not pressed')
  263.                 brailleState = settings.brailleRadioButtonIndicators[stateCount]
  264.             elif stateCount:
  265.                 speechState = C_('radiobutton', 'selected')
  266.             else:
  267.                 speechState = C_('radiobutton', 'not selected')
  268.             brailleState = settings.brailleRadioButtonIndicators[stateCount]
  269.             if attr == 'string':
  270.                 return speechState
  271.             if attr == 'length':
  272.                 return len(speechState)
  273.             if attr == 'brailleString':
  274.                 return brailleState
  275.         else:
  276.             return Zone.__getattr__(self, attr)
  277.         return attr == 'brailleString'
  278.  
  279.  
  280.  
  281. class ValueZone(Zone):
  282.     '''Represents a Zone for an accessible that shows a value using
  283.     a graphical indicator, such as a progress bar or slider.'''
  284.     
  285.     def __init__(self, accessible, x, y, width, height, role = None):
  286.         Zone.__init__(self, accessible, '', x, y, width, height, role)
  287.         del self.string
  288.  
  289.     
  290.     def __getattr__(self, attr):
  291.         if attr in ('string', 'length', 'brailleString'):
  292.             orientation = None
  293.             if self.role in [
  294.                 pyatspi.ROLE_SLIDER,
  295.                 pyatspi.ROLE_SCROLL_BAR]:
  296.                 stateset = self.accessible.getState()
  297.                 if stateset.contains(pyatspi.STATE_HORIZONTAL):
  298.                     orientation = _('horizontal')
  299.                 elif stateset.contains(pyatspi.STATE_VERTICAL):
  300.                     orientation = _('vertical')
  301.                 
  302.             
  303.             value = self.accessible.queryValue()
  304.             percentValue = int((value.currentValue / (value.maximumValue - value.minimumValue)) * 100)
  305.             if orientation:
  306.                 speechValue = orientation + ' ' + rolenames.getSpeechForRoleName(self.accessible)
  307.             else:
  308.                 speechValue = rolenames.getSpeechForRoleName(self.accessible)
  309.             speechValue = speechValue + ' ' + _('%d percent.') % percentValue
  310.             if orientation:
  311.                 brailleValue = '%s %s %d%%' % (orientation, rolenames.getBrailleForRoleName(self.accessible), percentValue)
  312.             else:
  313.                 brailleValue = '%s %d%%' % (rolenames.getBrailleForRoleName(self.accessible), percentValue)
  314.             if attr == 'string':
  315.                 return speechValue
  316.             if attr == 'length':
  317.                 return len(speechValue)
  318.             if attr == 'brailleString':
  319.                 return brailleValue
  320.         else:
  321.             return Zone.__getattr__(self, attr)
  322.         return attr == 'brailleString'
  323.  
  324.  
  325.  
  326. class Line:
  327.     '''A Line is a single line across a window and is composed of Zones.'''
  328.     
  329.     def __init__(self, index, zones):
  330.         '''Creates a new Line, which is a horizontal region of text.
  331.  
  332.         Arguments:
  333.         - index: the index of this Line in the window
  334.         - zones: the Zones that make up this line
  335.         '''
  336.         self.index = index
  337.         self.zones = zones
  338.         self.brailleRegions = None
  339.  
  340.     
  341.     def __getattr__(self, attr):
  342.         if attr in ('string', 'length', 'x', 'y', 'width', 'height'):
  343.             bounds = None
  344.             string = ''
  345.             for zone in self.zones:
  346.                 if not bounds:
  347.                     bounds = [
  348.                         zone.x,
  349.                         zone.y,
  350.                         zone.x + zone.width,
  351.                         zone.y + zone.height]
  352.                 else:
  353.                     bounds[0] = min(bounds[0], zone.x)
  354.                     bounds[1] = min(bounds[1], zone.y)
  355.                     bounds[2] = max(bounds[2], zone.x + zone.width)
  356.                     bounds[3] = max(bounds[3], zone.y + zone.height)
  357.                 if len(zone.string):
  358.                     if len(string):
  359.                         string += ' '
  360.                     
  361.                     string += zone.string
  362.                     continue
  363.             
  364.             if not bounds:
  365.                 bounds = [
  366.                     -1,
  367.                     -1,
  368.                     -1,
  369.                     -1]
  370.             
  371.             if attr == 'string':
  372.                 return string
  373.             if attr == 'length':
  374.                 return len(string)
  375.             if attr == 'x':
  376.                 return bounds[0]
  377.             if attr == 'y':
  378.                 return bounds[1]
  379.             if attr == 'width':
  380.                 return bounds[2] - bounds[0]
  381.             if attr == 'height':
  382.                 return bounds[3] - bounds[1]
  383.         elif attr.startswith('__') and attr.endswith('__'):
  384.             raise AttributeError, attr
  385.         else:
  386.             return self.__dict__[attr]
  387.         return attr == 'width'
  388.  
  389.     
  390.     def getBrailleRegions(self):
  391.         if True or not (self.brailleRegions):
  392.             self.brailleRegions = []
  393.             brailleOffset = 0
  394.             for zone in self.zones:
  395.                 if isinstance(zone, TextZone):
  396.                     if zone.accessible.getRole() in (pyatspi.ROLE_TEXT, pyatspi.ROLE_PASSWORD_TEXT, pyatspi.ROLE_TERMINAL) or zone.accessible.getRole() in (pyatspi.ROLE_PARAGRAPH, pyatspi.ROLE_HEADING, pyatspi.ROLE_LINK):
  397.                         region = braille.ReviewText(zone.accessible, zone.string, zone.startOffset, zone)
  398.                     else:
  399.                         
  400.                         try:
  401.                             brailleString = zone.brailleString
  402.                         except:
  403.                             brailleString = zone.string
  404.  
  405.                         region = braille.ReviewComponent(zone.accessible, brailleString, 0, zone)
  406.                 if len(self.brailleRegions):
  407.                     pad = braille.Region(' ')
  408.                     pad.brailleOffset = brailleOffset
  409.                     self.brailleRegions.append(pad)
  410.                     brailleOffset += 1
  411.                 
  412.                 zone.brailleRegion = region
  413.                 region.brailleOffset = brailleOffset
  414.                 self.brailleRegions.append(region)
  415.                 brailleOffset += len(region.string.decode('UTF-8'))
  416.             
  417.             if not settings.disableBrailleEOL:
  418.                 if len(self.brailleRegions):
  419.                     pad = braille.Region(' ')
  420.                     pad.brailleOffset = brailleOffset
  421.                     self.brailleRegions.append(pad)
  422.                     brailleOffset += 1
  423.                 
  424.                 eol = braille.Region('$l')
  425.                 eol.brailleOffset = brailleOffset
  426.                 self.brailleRegions.append(eol)
  427.             
  428.         
  429.         return self.brailleRegions
  430.  
  431.  
  432.  
  433. class Context:
  434.     '''Information regarding where a user happens to be exploring
  435.     right now.
  436.     '''
  437.     ZONE = 0
  438.     CHAR = 1
  439.     WORD = 2
  440.     LINE = 3
  441.     WINDOW = 4
  442.     WRAP_NONE = 0
  443.     WRAP_LINE = 1
  444.     WRAP_TOP_BOTTOM = 2
  445.     WRAP_ALL = WRAP_LINE | WRAP_TOP_BOTTOM
  446.     
  447.     def __init__(self, script):
  448.         '''Create a new Context that will be used for handling flat
  449.         review mode.
  450.         '''
  451.         self.script = script
  452.         if not (orca_state.locusOfFocus) or orca_state.locusOfFocus.getApplication() != self.script.app:
  453.             self.lines = []
  454.         else:
  455.             obj = orca_state.locusOfFocus
  456.             while obj and obj.parent and obj.parent.getRole() != pyatspi.ROLE_APPLICATION and obj != obj.parent:
  457.                 obj = obj.parent
  458.             if obj:
  459.                 self.lines = self.clusterZonesByLine(self.getShowingZones(obj))
  460.             else:
  461.                 self.lines = []
  462.         currentLineIndex = 0
  463.         currentZoneIndex = 0
  464.         currentWordIndex = 0
  465.         currentCharIndex = 0
  466.         if orca_state.locusOfFocus and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
  467.             searchZone = orca_state.activeScript.getRealActiveDescendant(orca_state.locusOfFocus)
  468.         else:
  469.             searchZone = orca_state.locusOfFocus
  470.         foundZoneWithFocus = False
  471.         while currentLineIndex < len(self.lines):
  472.             line = self.lines[currentLineIndex]
  473.             currentZoneIndex = 0
  474.             while currentZoneIndex < len(line.zones):
  475.                 zone = line.zones[currentZoneIndex]
  476.                 if self.script.isSameObject(zone.accessible, searchZone):
  477.                     foundZoneWithFocus = True
  478.                     break
  479.                     continue
  480.                 currentZoneIndex += 1
  481.             if foundZoneWithFocus:
  482.                 break
  483.                 continue
  484.             currentLineIndex += 1
  485.         if not foundZoneWithFocus:
  486.             currentLineIndex = 0
  487.             currentZoneIndex = 0
  488.         elif isinstance(zone, TextZone):
  489.             accessible = zone.accessible
  490.             lineIndex = currentLineIndex
  491.             zoneIndex = currentZoneIndex
  492.             
  493.             try:
  494.                 caretOffset = zone.accessible.queryText().caretOffset
  495.             except NotImplementedError:
  496.                 caretOffset = -1
  497.  
  498.             foundZoneWithCaret = False
  499.             checkForEOF = False
  500.             while lineIndex < len(self.lines):
  501.                 line = self.lines[lineIndex]
  502.                 while zoneIndex < len(line.zones):
  503.                     zone = line.zones[zoneIndex]
  504.                     if zone.accessible == accessible:
  505.                         if caretOffset >= zone.startOffset:
  506.                             if caretOffset < zone.startOffset + zone.length:
  507.                                 foundZoneWithCaret = True
  508.                                 break
  509.                             elif caretOffset == zone.startOffset + zone.length:
  510.                                 checkForEOF = True
  511.                                 lineToCheck = lineIndex
  512.                                 zoneToCheck = zoneIndex
  513.                             
  514.                         
  515.                     
  516.                     zoneIndex += 1
  517.                 if foundZoneWithCaret:
  518.                     currentLineIndex = lineIndex
  519.                     currentZoneIndex = zoneIndex
  520.                     currentWordIndex = 0
  521.                     currentCharIndex = 0
  522.                     offset = zone.startOffset
  523.                     while currentWordIndex < len(zone.words):
  524.                         word = zone.words[currentWordIndex]
  525.                         if word.length + offset > caretOffset:
  526.                             currentCharIndex = caretOffset - offset
  527.                             break
  528.                             continue
  529.                         currentWordIndex += 1
  530.                         offset += word.length
  531.                     break
  532.                     continue
  533.                 zoneIndex = 0
  534.                 lineIndex += 1
  535.             if not foundZoneWithCaret:
  536.                 pass
  537.             atEOF = checkForEOF
  538.             if atEOF:
  539.                 line = self.lines[lineToCheck]
  540.                 zone = line.zones[zoneToCheck]
  541.                 currentLineIndex = lineToCheck
  542.                 currentZoneIndex = zoneToCheck
  543.                 if caretOffset and zone.words:
  544.                     currentWordIndex = len(zone.words) - 1
  545.                     currentCharIndex = zone.words[currentWordIndex].length - 1
  546.                 
  547.             
  548.         
  549.         self.lineIndex = currentLineIndex
  550.         self.zoneIndex = currentZoneIndex
  551.         self.wordIndex = currentWordIndex
  552.         self.charIndex = currentCharIndex
  553.         self.targetCharInfo = None
  554.  
  555.     
  556.     def clip(self, ax, ay, awidth, aheight, bx, by, bwidth, bheight):
  557.         """Clips region 'a' by region 'b' and returns the new region as
  558.         a list: [x, y, width, height].
  559.         """
  560.         x = max(ax, bx)
  561.         x2 = min(ax + awidth, bx + bwidth)
  562.         width = x2 - x
  563.         y = max(ay, by)
  564.         y2 = min(ay + aheight, by + bheight)
  565.         height = y2 - y
  566.         return [
  567.             x,
  568.             y,
  569.             width,
  570.             height]
  571.  
  572.     
  573.     def splitTextIntoZones(self, accessible, string, startOffset, cliprect):
  574.         """Traverses the string, splitting it up into separate zones if the
  575.         string contains the EMBEDDED_OBJECT_CHARACTER, which is used by apps
  576.         such as Firefox to handle containment of things such as links in
  577.         paragraphs.
  578.  
  579.         Arguments:
  580.         - accessible: the accessible
  581.         - string: a substring from the accessible's text specialization
  582.         - startOffset: the starting character offset of the string
  583.         - cliprect: the extents that the Zones must fit inside.
  584.  
  585.         Returns a list of Zones for the visible text or None if nothing is
  586.         visible.
  587.         """
  588.         anyVisible = False
  589.         zones = []
  590.         text = accessible.queryText()
  591.         substringStartOffset = startOffset
  592.         substringEndOffset = startOffset
  593.         unicodeStartOffset = 0
  594.         unicodeString = string.decode('UTF-8')
  595.         for i in range(0, len(unicodeString) + 1):
  596.             if i != len(unicodeString) and unicodeString[i] != orca_state.activeScript.EMBEDDED_OBJECT_CHARACTER:
  597.                 substringEndOffset += 1
  598.                 continue
  599.             if substringEndOffset == substringStartOffset:
  600.                 substringStartOffset += 1
  601.                 substringEndOffset = substringStartOffset
  602.                 unicodeStartOffset = i + 1
  603.                 continue
  604.             (x, y, width, height) = text.getRangeExtents(substringStartOffset, substringEndOffset, 0)
  605.             if self.script.visible(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height):
  606.                 anyVisible = True
  607.                 clipping = self.clip(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  608.                 substring = unicodeString[unicodeStartOffset:i]
  609.                 zones.append(TextZone(accessible, substringStartOffset, substring.encode('UTF-8'), clipping[0], clipping[1], clipping[2], clipping[3]))
  610.                 substringStartOffset = substringEndOffset + 1
  611.                 substringEndOffset = substringStartOffset
  612.                 unicodeStartOffset = i + 1
  613.                 continue
  614.         
  615.         if anyVisible:
  616.             return zones
  617.         return None
  618.  
  619.     
  620.     def getZonesFromText(self, accessible, cliprect):
  621.         '''Gets a list of Zones from an object that implements the
  622.         AccessibleText specialization.
  623.  
  624.         Arguments:
  625.         - accessible: the accessible
  626.         - cliprect: the extents that the Zones must fit inside.
  627.  
  628.         Returns a list of Zones.
  629.         '''
  630.         debug.println(debug.LEVEL_FINEST, '  looking at text:')
  631.         
  632.         try:
  633.             text = accessible.queryText()
  634.         except NotImplementedError:
  635.             return []
  636.  
  637.         zones = []
  638.         offset = 0
  639.         lastEndOffset = -1
  640.         upperMax = lowerMax = text.characterCount
  641.         upperMid = lowerMid = upperMax / 2
  642.         upperMin = lowerMin = 0
  643.         upperY = lowerY = 0
  644.         oldMid = 0
  645.         while oldMid != upperMid:
  646.             oldMid = upperMid
  647.             (x, y, width, height) = text.getRangeExtents(upperMid, upperMid + 1, 0)
  648.             upperY = y
  649.             if y > cliprect.y:
  650.                 upperMax = upperMid
  651.             else:
  652.                 upperMin = upperMid
  653.             upperMid = (upperMax - upperMin) / 2 + upperMin
  654.         oldMid = 0
  655.         limit = cliprect.y + cliprect.height
  656.         while oldMid != lowerMid:
  657.             oldMid = lowerMid
  658.             (x, y, width, height) = text.getRangeExtents(lowerMid, lowerMid + 1, 0)
  659.             lowerY = y
  660.             if y > limit:
  661.                 lowerMax = lowerMid
  662.             else:
  663.                 lowerMin = lowerMid
  664.             lowerMid = (lowerMax - lowerMin) / 2 + lowerMin
  665.         offset = upperMin
  666.         length = lowerMax
  667.         while offset < length:
  668.             (string, startOffset, endOffset) = text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_LINE_START)
  669.             debug.println(debug.LEVEL_FINEST, "    line at %d is (start=%d end=%d): '%s'" % (offset, startOffset, endOffset, string))
  670.             if startOffset < 0 and endOffset < 0 and startOffset > offset and endOffset < offset and startOffset > endOffset or abs(endOffset - startOffset) > 666000:
  671.                 debug.println(debug.LEVEL_WARNING, "flat_review:getZonesFromText detected garbage from getTextAtOffset for accessible name='%s' role'='%s': offset used=%d, start/end offset returned=(%d,%d), string='%s'" % (accessible.name, accessible.getRoleName(), offset, startOffset, endOffset, string))
  672.                 break
  673.             
  674.             if endOffset == lastEndOffset:
  675.                 offset = max(offset + 1, lastEndOffset + 1)
  676.                 lastEndOffset = endOffset
  677.                 continue
  678.             else:
  679.                 offset = endOffset
  680.                 lastEndOffset = endOffset
  681.             textZones = self.splitTextIntoZones(accessible, string, startOffset, cliprect)
  682.             if endOffset == length and string[-1:] == '\n':
  683.                 (x, y, width, height) = text.getRangeExtents(startOffset, endOffset, 0)
  684.                 if not textZones:
  685.                     textZones = []
  686.                 
  687.                 textZones.append(TextZone(accessible, endOffset, '', x, y + height, 0, height))
  688.             
  689.             if textZones:
  690.                 zones.extend(textZones)
  691.                 continue
  692.             if len(zones):
  693.                 break
  694.                 continue
  695.         if len(zones) == 0:
  696.             if accessible.getRole() == pyatspi.ROLE_TEXT and accessible.getRole() == pyatspi.ROLE_ENTRY or accessible.getRole() == pyatspi.ROLE_PASSWORD_TEXT:
  697.                 extents = accessible.queryComponent().getExtents(0)
  698.                 zones.append(TextZone(accessible, 0, '', extents.x, extents.y, extents.width, extents.height))
  699.             
  700.         
  701.         return zones
  702.  
  703.     
  704.     def _insertStateZone(self, zones, accessible, role = None):
  705.         '''If the accessible presents non-textual state, such as a
  706.         checkbox or radio button, insert a StateZone representing
  707.         that state.'''
  708.         zone = None
  709.         stateOnLeft = True
  710.         if not role:
  711.             pass
  712.         role = accessible.getRole()
  713.         if role in [
  714.             pyatspi.ROLE_CHECK_BOX,
  715.             pyatspi.ROLE_CHECK_MENU_ITEM,
  716.             pyatspi.ROLE_RADIO_BUTTON,
  717.             pyatspi.ROLE_RADIO_MENU_ITEM]:
  718.             extents = accessible.queryComponent().getExtents(0)
  719.             stateX = extents.x
  720.             stateY = extents.y
  721.             stateWidth = 1
  722.             stateHeight = extents.height
  723.             
  724.             try:
  725.                 text = accessible.queryText()
  726.             except NotImplementedError:
  727.                 pass
  728.  
  729.             (x, y, width, height) = text.getRangeExtents(0, text.characterCount, 0)
  730.             textToLeftEdge = x - extents.x
  731.             textToRightEdge = extents.x + extents.width - (x + width)
  732.             stateOnLeft = textToLeftEdge > 20
  733.             if stateOnLeft:
  734.                 stateWidth = textToLeftEdge
  735.             else:
  736.                 stateX = x + width
  737.                 stateWidth = textToRightEdge
  738.             zone = StateZone(accessible, stateX, stateY, stateWidth, stateHeight)
  739.         elif role == pyatspi.ROLE_TOGGLE_BUTTON:
  740.             extents = accessible.queryComponent().getExtents(0)
  741.             zone = StateZone(accessible, extents.x, extents.y, 1, extents.height)
  742.         elif role == pyatspi.ROLE_TABLE_CELL:
  743.             
  744.             try:
  745.                 action = accessible.queryAction()
  746.             except NotImplementedError:
  747.                 action = None
  748.  
  749.             if action:
  750.                 hasToggle = False
  751.                 for i in range(0, action.nActions):
  752.                     if action.getName(i) in [
  753.                         'toggle',
  754.                         _('toggle')]:
  755.                         hasToggle = True
  756.                         break
  757.                         continue
  758.                 
  759.                 if hasToggle:
  760.                     self._insertStateZone(zones, accessible, pyatspi.ROLE_CHECK_BOX)
  761.                 
  762.             
  763.         
  764.         if zone:
  765.             if stateOnLeft:
  766.                 zones.insert(0, zone)
  767.             else:
  768.                 zones.append(zone)
  769.         
  770.  
  771.     
  772.     def getZonesFromAccessible(self, accessible, cliprect):
  773.         '''Returns a list of Zones for the given accessible.
  774.  
  775.         Arguments:
  776.         - accessible: the accessible
  777.         - cliprect: the extents that the Zones must fit inside.
  778.         '''
  779.         icomponent = accessible.queryComponent()
  780.         if not icomponent:
  781.             return []
  782.         extents = icomponent.getExtents(0)
  783.         if not self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height):
  784.             return []
  785.         debug.println(debug.LEVEL_FINEST, 'flat_review.getZonesFromAccessible (name=%s role=%s)' % (accessible.name, accessible.getRoleName()))
  786.         
  787.         try:
  788.             accessible.queryText()
  789.         except NotImplementedError:
  790.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  791.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  792.             icomponent
  793.             zones = []
  794.         except:
  795.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  796.  
  797.         zones = self.getZonesFromText(accessible, cliprect)
  798.         
  799.         try:
  800.             iimage = accessible.queryImage()
  801.         except NotImplementedError:
  802.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  803.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  804.             icomponent
  805.             iimage = None
  806.         except:
  807.             self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  808.  
  809.         if len(zones) == 0 and iimage:
  810.             imageName = ''
  811.             if accessible.name and len(accessible.name):
  812.                 imageName = accessible.name
  813.             elif accessible.description and len(accessible.description):
  814.                 imageName = accessible.description
  815.             elif iimage.imageDescription and len(iimage.imageDescription):
  816.                 imageName = iimage.imageDescription
  817.             
  818.             (x, y) = iimage.getImagePosition(0)
  819.             (width, height) = iimage.getImageSize()
  820.             if width != 0 and height != 0 and self.script.visible(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height):
  821.                 clipping = self.clip(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  822.                 if clipping[2] != 0 or clipping[3] != 0:
  823.                     zones.append(Zone(accessible, imageName, clipping[0], clipping[1], clipping[2], clipping[3]))
  824.                 
  825.             
  826.         
  827.         clipping = self.clip(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height)
  828.         role = accessible.getRole()
  829.         if len(zones) == 0 and role in [
  830.             pyatspi.ROLE_SCROLL_BAR,
  831.             pyatspi.ROLE_SLIDER,
  832.             pyatspi.ROLE_PROGRESS_BAR]:
  833.             zones.append(ValueZone(accessible, clipping[0], clipping[1], clipping[2], clipping[3]))
  834.         elif role != pyatspi.ROLE_COMBO_BOX and role != pyatspi.ROLE_EMBEDDED and role != pyatspi.ROLE_LABEL and role != pyatspi.ROLE_MENU and role != pyatspi.ROLE_PAGE_TAB and accessible.childCount > 0:
  835.             pass
  836.         elif len(zones) == 0:
  837.             string = ''
  838.             if role == pyatspi.ROLE_COMBO_BOX:
  839.                 
  840.                 try:
  841.                     selection = accessible[0].querySelection()
  842.                 except:
  843.                     string = self.script.getDisplayedText(accessible[0])
  844.  
  845.                 item = selection.getSelectedChild(0)
  846.                 if item:
  847.                     string = item.name
  848.                 
  849.             
  850.             if not string and accessible.name and len(accessible.name):
  851.                 string = accessible.name
  852.             elif accessible.description and len(accessible.description):
  853.                 string = accessible.description
  854.             
  855.             if string == '' and role != pyatspi.ROLE_TABLE_CELL:
  856.                 string = accessible.getRoleName()
  857.             
  858.             if len(string):
  859.                 pass
  860.             None if clipping[2] != 0 or clipping[3] != 0 else clipping[3] != 0
  861.         
  862.         self._insertStateZone(zones, accessible)
  863.         return zones
  864.  
  865.     
  866.     def getShowingZones(self, root):
  867.         """Returns a list of all interesting, non-intersecting, regions
  868.         that are drawn on the screen.  Each element of the list is the
  869.         Accessible object associated with a given region.  The term
  870.         'zone' here is inherited from OCR algorithms and techniques.
  871.  
  872.         The Zones are returned in no particular order.
  873.  
  874.         Arguments:
  875.         - root: the Accessible object to traverse
  876.  
  877.         Returns: a list of Zones under the specified object
  878.         """
  879.         if not root:
  880.             return []
  881.         zones = []
  882.         
  883.         try:
  884.             rootexts = root.queryComponent().getExtents(0)
  885.         except:
  886.             root
  887.             return []
  888.  
  889.         rootrole = root.getRole()
  890.         if root.childCount <= 0:
  891.             return self.getZonesFromAccessible(root, rootexts)
  892.         stateset = root.getState()
  893.         if (root.parent or root.parent.getRole() == pyatspi.ROLE_MENU_BAR) and rootrole == pyatspi.ROLE_COMBO_BOX and rootrole == pyatspi.ROLE_EMBEDDED and rootrole == pyatspi.ROLE_TEXT or rootrole == pyatspi.ROLE_SCROLL_BAR:
  894.             return self.getZonesFromAccessible(root, rootexts)
  895.         if rootrole == pyatspi.ROLE_PAGE_TAB:
  896.             zones.extend(self.getZonesFromAccessible(root, rootexts))
  897.         
  898.         
  899.         try:
  900.             root.queryText()
  901.             if len(zones) == 0:
  902.                 zones = self.getZonesFromAccessible(root, rootexts)
  903.         except NotImplementedError:
  904.             pass
  905.  
  906.         showingDescendants = self.script.getShowingDescendants(root)
  907.         if len(showingDescendants):
  908.             for child in showingDescendants:
  909.                 zones.extend(self.getShowingZones(child))
  910.             
  911.         else:
  912.             for i in range(0, root.childCount):
  913.                 child = root.getChildAtIndex(i)
  914.                 if child == root:
  915.                     debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD == PARENT!!!')
  916.                     continue
  917.                 elif not child:
  918.                     debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD IS NONE!!!')
  919.                     continue
  920.                 elif child.parent != root:
  921.                     debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD.PARENT != PARENT!!!')
  922.                 
  923.                 if self.script.pursueForFlatReview(child):
  924.                     zones.extend(self.getShowingZones(child))
  925.                     continue
  926.             
  927.         return zones
  928.  
  929.     
  930.     def clusterZonesByLine(self, zones):
  931.         '''Given a list of interesting accessible objects (the Zones),
  932.         returns a list of lines in order from the top to bottom, where
  933.         each line is a list of accessible objects in order from left
  934.         to right.
  935.         '''
  936.         if len(zones) == 0:
  937.             return []
  938.         numZones = len(zones)
  939.         for i in range(0, numZones):
  940.             for j in range(0, numZones - 1 - i):
  941.                 a = zones[j]
  942.                 b = zones[j + 1]
  943.                 if b.y < a.y:
  944.                     zones[j] = b
  945.                     zones[j + 1] = a
  946.                     continue
  947.                 len(zones) == 0
  948.             
  949.         
  950.         lineClusters = []
  951.         for clusterCandidate in zones:
  952.             addedToCluster = False
  953.             for lineCluster in lineClusters:
  954.                 inCluster = True
  955.                 for zone in lineCluster:
  956.                     if not zone.onSameLine(clusterCandidate):
  957.                         inCluster = False
  958.                         break
  959.                         continue
  960.                 
  961.                 if inCluster:
  962.                     i = 0
  963.                     while i < len(lineCluster):
  964.                         zone = lineCluster[i]
  965.                         if clusterCandidate.x < zone.x:
  966.                             break
  967.                             continue
  968.                         i += 1
  969.                     lineCluster.insert(i, clusterCandidate)
  970.                     addedToCluster = True
  971.                     break
  972.                     continue
  973.             
  974.             if not addedToCluster:
  975.                 lineClusters.append([
  976.                     clusterCandidate])
  977.                 continue
  978.         
  979.         lines = []
  980.         lineIndex = 0
  981.         for lineCluster in lineClusters:
  982.             lines.append(Line(lineIndex, lineCluster))
  983.             zoneIndex = 0
  984.             for zone in lineCluster:
  985.                 zone.line = lines[lineIndex]
  986.                 zone.index = zoneIndex
  987.                 zoneIndex += 1
  988.             
  989.             lineIndex += 1
  990.         
  991.         return lines
  992.  
  993.     
  994.     def _dumpCurrentState(self):
  995.         print 'line=%d, zone=%d, word=%d, char=%d' % (self.lineIndex, self.zoneIndex, self.wordIndex, self.zoneIndex)
  996.         zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  997.         text = zone.accessible.queryText()
  998.         if not text:
  999.             print '  Not Accessibility_Text'
  1000.             return None
  1001.         print '  getTextBeforeOffset: %d' % text.caretOffset
  1002.         (string, startOffset, endOffset) = text.getTextBeforeOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
  1003.         print "    WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1004.         (string, startOffset, endOffset) = text.getTextBeforeOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END)
  1005.         print "    WORD_END:   start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1006.         print '  getTextAtOffset: %d' % text.caretOffset
  1007.         (string, startOffset, endOffset) = text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
  1008.         print "    WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1009.         (string, startOffset, endOffset) = text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END)
  1010.         print "    WORD_END:   start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1011.         print '  getTextAfterOffset: %d' % text.caretOffset
  1012.         (string, startOffset, endOffset) = text.getTextAfterOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
  1013.         print "    WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1014.         (string, startOffset, endOffset) = text.getTextAfterOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END)
  1015.         print "    WORD_END:   start=%d end=%d string='%s'" % (startOffset, endOffset, string)
  1016.  
  1017.     
  1018.     def setCurrent(self, lineIndex, zoneIndex, wordIndex, charIndex):
  1019.         '''Sets the current character of interest.
  1020.  
  1021.         Arguments:
  1022.         - lineIndex: index into lines
  1023.         - zoneIndex: index into lines[lineIndex].zones
  1024.         - wordIndex: index into lines[lineIndex].zones[zoneIndex].words
  1025.         - charIndex: index lines[lineIndex].zones[zoneIndex].words[wordIndex].chars
  1026.         '''
  1027.         self.lineIndex = lineIndex
  1028.         self.zoneIndex = zoneIndex
  1029.         self.wordIndex = wordIndex
  1030.         self.charIndex = charIndex
  1031.         self.targetCharInfo = self.getCurrent(Context.CHAR)
  1032.  
  1033.     
  1034.     def clickCurrent(self, button = 1):
  1035.         '''Performs a mouse click on the current accessible.'''
  1036.         if not (self.lines) or not (self.lines[self.lineIndex].zones):
  1037.             return None
  1038.         (string, x, y, width, height) = self.getCurrent(Context.CHAR)
  1039.         
  1040.         try:
  1041.             x = max(x, x + width / 2 - 1)
  1042.             eventsynthesizer.clickPoint(x, y + height / 2, button)
  1043.         except:
  1044.             not (self.lines[self.lineIndex].zones)
  1045.             debug.printException(debug.LEVEL_SEVERE)
  1046.  
  1047.  
  1048.     
  1049.     def getCurrentAccessible(self):
  1050.         '''Returns the accessible associated with the current locus of
  1051.         interest.
  1052.         '''
  1053.         if not (self.lines) or not (self.lines[self.lineIndex].zones):
  1054.             return [
  1055.                 None,
  1056.                 -1,
  1057.                 -1,
  1058.                 -1,
  1059.                 -1]
  1060.         zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1061.         return zone.accessible
  1062.  
  1063.     
  1064.     def getCurrent(self, flatReviewType = ZONE):
  1065.         '''Gets the string, offset, and extent information for the
  1066.         current locus of interest.
  1067.  
  1068.         Arguments:
  1069.         - flatReviewType: one of ZONE, CHAR, WORD, LINE
  1070.  
  1071.         Returns: [string, x, y, width, height]
  1072.         '''
  1073.         if not (self.lines) or not (self.lines[self.lineIndex].zones):
  1074.             return [
  1075.                 None,
  1076.                 -1,
  1077.                 -1,
  1078.                 -1,
  1079.                 -1]
  1080.         zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1081.         if flatReviewType == Context.ZONE:
  1082.             return [
  1083.                 zone.string,
  1084.                 zone.x,
  1085.                 zone.y,
  1086.                 zone.width,
  1087.                 zone.height]
  1088.         if flatReviewType == Context.CHAR:
  1089.             return self.getCurrent(Context.ZONE)
  1090.         if flatReviewType == Context.WORD:
  1091.             return self.getCurrent(Context.ZONE)
  1092.         if flatReviewType == Context.LINE:
  1093.             line = self.lines[self.lineIndex]
  1094.             return [
  1095.                 line.string,
  1096.                 line.x,
  1097.                 line.y,
  1098.                 line.width,
  1099.                 line.height]
  1100.         raise Exception('Invalid type: %d' % flatReviewType)
  1101.  
  1102.     
  1103.     def getCurrentBrailleRegions(self):
  1104.         '''Gets the braille for the entire current line.
  1105.  
  1106.         Returns [regions, regionWithFocus]
  1107.         '''
  1108.         if not (self.lines) or not (self.lines[self.lineIndex].zones):
  1109.             return [
  1110.                 None,
  1111.                 None]
  1112.         regionWithFocus = None
  1113.         line = self.lines[self.lineIndex]
  1114.         regions = line.getBrailleRegions()
  1115.         for zone in line.zones:
  1116.             if zone.index == self.zoneIndex:
  1117.                 regionWithFocus = zone.brailleRegion
  1118.                 regionWithFocus.cursorOffset = 0
  1119.                 regionWithFocus.cursorOffset += self.charIndex
  1120.                 regionWithFocus.repositionCursor()
  1121.                 break
  1122.                 continue
  1123.             regionWithFocus
  1124.         
  1125.         return [
  1126.             regions,
  1127.             regionWithFocus]
  1128.  
  1129.     
  1130.     def goBegin(self, flatReviewType = WINDOW):
  1131.         """Moves this context's locus of interest to the first char
  1132.         of the first relevant zone.
  1133.  
  1134.         Arguments:
  1135.         - flatReviewType: one of ZONE, LINE or WINDOW
  1136.  
  1137.         Returns True if the locus of interest actually changed.
  1138.         """
  1139.         if flatReviewType == Context.LINE or flatReviewType == Context.ZONE:
  1140.             lineIndex = self.lineIndex
  1141.         elif flatReviewType == Context.WINDOW:
  1142.             lineIndex = 0
  1143.         else:
  1144.             raise Exception('Invalid type: %d' % flatReviewType)
  1145.         if flatReviewType == Context.ZONE == Context.ZONE:
  1146.             zoneIndex = self.zoneIndex
  1147.         else:
  1148.             zoneIndex = 0
  1149.         wordIndex = 0
  1150.         charIndex = 0
  1151.         if not self.lineIndex != lineIndex and self.zoneIndex != zoneIndex and self.wordIndex != wordIndex:
  1152.             pass
  1153.         moved = self.charIndex != charIndex
  1154.         if moved:
  1155.             self.lineIndex = lineIndex
  1156.             self.zoneIndex = zoneIndex
  1157.             self.wordIndex = wordIndex
  1158.             self.charIndex = charIndex
  1159.             self.targetCharInfo = self.getCurrent(Context.CHAR)
  1160.         
  1161.         return moved
  1162.  
  1163.     
  1164.     def goEnd(self, flatReviewType = WINDOW):
  1165.         """Moves this context's locus of interest to the last char
  1166.         of the last relevant zone.
  1167.  
  1168.         Arguments:
  1169.         - flatReviewType: one of ZONE, LINE, or WINDOW
  1170.  
  1171.         Returns True if the locus of interest actually changed.
  1172.         """
  1173.         if flatReviewType == Context.LINE or flatReviewType == Context.ZONE:
  1174.             lineIndex = self.lineIndex
  1175.         elif flatReviewType == Context.WINDOW:
  1176.             lineIndex = len(self.lines) - 1
  1177.         else:
  1178.             raise Exception('Invalid type: %d' % flatReviewType)
  1179.         if flatReviewType == Context.ZONE == Context.ZONE:
  1180.             zoneIndex = self.zoneIndex
  1181.         else:
  1182.             zoneIndex = len(self.lines[lineIndex].zones) - 1
  1183.         zone = self.lines[lineIndex].zones[zoneIndex]
  1184.         if zone.words:
  1185.             wordIndex = len(zone.words) - 1
  1186.             chars = zone.words[wordIndex].chars
  1187.             if chars:
  1188.                 charIndex = len(chars) - 1
  1189.             else:
  1190.                 charIndex = 0
  1191.         else:
  1192.             wordIndex = 0
  1193.             charIndex = 0
  1194.         if not self.lineIndex != lineIndex and self.zoneIndex != zoneIndex and self.wordIndex != wordIndex:
  1195.             pass
  1196.         moved = self.charIndex != charIndex
  1197.         if moved:
  1198.             self.lineIndex = lineIndex
  1199.             self.zoneIndex = zoneIndex
  1200.             self.wordIndex = wordIndex
  1201.             self.charIndex = charIndex
  1202.             self.targetCharInfo = self.getCurrent(Context.CHAR)
  1203.         
  1204.         return moved
  1205.  
  1206.     
  1207.     def goPrevious(self, flatReviewType = ZONE, wrap = WRAP_ALL, omitWhitespace = True):
  1208.         """Moves this context's locus of interest to the first char
  1209.         of the previous type.
  1210.  
  1211.         Arguments:
  1212.         - flatReviewType: one of ZONE, CHAR, WORD, LINE
  1213.         - wrap: if True, will cross boundaries, including top and
  1214.                 bottom; if False, will stop on boundaries.
  1215.  
  1216.         Returns True if the locus of interest actually changed.
  1217.         """
  1218.         moved = False
  1219.         if flatReviewType == Context.ZONE:
  1220.             if self.zoneIndex > 0:
  1221.                 self.zoneIndex -= 1
  1222.                 self.wordIndex = 0
  1223.                 self.charIndex = 0
  1224.                 moved = True
  1225.             elif wrap & Context.WRAP_LINE:
  1226.                 if self.lineIndex > 0:
  1227.                     self.lineIndex -= 1
  1228.                     self.zoneIndex = len(self.lines[self.lineIndex].zones) - 1
  1229.                     self.wordIndex = 0
  1230.                     self.charIndex = 0
  1231.                     moved = True
  1232.                 elif wrap & Context.WRAP_TOP_BOTTOM:
  1233.                     self.lineIndex = len(self.lines) - 1
  1234.                     self.zoneIndex = len(self.lines[self.lineIndex].zones) - 1
  1235.                     self.wordIndex = 0
  1236.                     self.charIndex = 0
  1237.                     moved = True
  1238.                 
  1239.             
  1240.         elif flatReviewType == Context.CHAR:
  1241.             if self.charIndex > 0:
  1242.                 self.charIndex -= 1
  1243.                 moved = True
  1244.             else:
  1245.                 moved = self.goPrevious(Context.WORD, wrap, False)
  1246.                 if moved:
  1247.                     zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1248.                     if zone.words:
  1249.                         chars = zone.words[self.wordIndex].chars
  1250.                         if chars:
  1251.                             self.charIndex = len(chars) - 1
  1252.                         
  1253.                     
  1254.                 
  1255.         elif flatReviewType == Context.WORD:
  1256.             zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1257.             accessible = zone.accessible
  1258.             lineIndex = self.lineIndex
  1259.             zoneIndex = self.zoneIndex
  1260.             wordIndex = self.wordIndex
  1261.             charIndex = self.charIndex
  1262.             if self.wordIndex > 0:
  1263.                 self.wordIndex -= 1
  1264.                 self.charIndex = 0
  1265.                 moved = True
  1266.             else:
  1267.                 moved = self.goPrevious(Context.ZONE, wrap)
  1268.                 if moved:
  1269.                     zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1270.                     if zone.words:
  1271.                         self.wordIndex = len(zone.words) - 1
  1272.                     
  1273.                 
  1274.             zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1275.             if omitWhitespace and moved:
  1276.                 if (len(zone.string) == 0 or len(zone.words)) and zone.words[self.wordIndex].string.isspace():
  1277.                     if zone.accessible == accessible:
  1278.                         moved = self.goPrevious(Context.WORD, wrap)
  1279.                     else:
  1280.                         wordIndex = self.wordIndex - 1
  1281.                         while wordIndex >= 0:
  1282.                             if not (zone.words[wordIndex].string) and not len(zone.words[wordIndex].string) or zone.words[wordIndex].string.isspace():
  1283.                                 wordIndex -= 1
  1284.                                 continue
  1285.                             break
  1286.                         if wordIndex >= 0:
  1287.                             self.wordIndex = wordIndex
  1288.                         
  1289.                 
  1290.             if not moved:
  1291.                 self.lineIndex = lineIndex
  1292.                 self.zoneIndex = zoneIndex
  1293.                 self.wordIndex = wordIndex
  1294.                 self.charIndex = charIndex
  1295.             
  1296.         elif flatReviewType == Context.LINE:
  1297.             if wrap & Context.WRAP_LINE:
  1298.                 if self.lineIndex > 0:
  1299.                     self.lineIndex -= 1
  1300.                     self.zoneIndex = 0
  1301.                     self.wordIndex = 0
  1302.                     self.charIndex = 0
  1303.                     moved = True
  1304.                 elif wrap & Context.WRAP_TOP_BOTTOM and len(self.lines) != 1:
  1305.                     self.lineIndex = len(self.lines) - 1
  1306.                     self.zoneIndex = 0
  1307.                     self.wordIndex = 0
  1308.                     self.charIndex = 0
  1309.                     moved = True
  1310.                 
  1311.             
  1312.         else:
  1313.             raise Exception('Invalid type: %d' % flatReviewType)
  1314.         if flatReviewType == Context.ZONE and flatReviewType != Context.LINE:
  1315.             self.targetCharInfo = self.getCurrent(Context.CHAR)
  1316.         
  1317.         return moved
  1318.  
  1319.     
  1320.     def goNext(self, flatReviewType = ZONE, wrap = WRAP_ALL, omitWhitespace = True):
  1321.         """Moves this context's locus of interest to first char of
  1322.         the next type.
  1323.  
  1324.         Arguments:
  1325.         - flatReviewType: one of ZONE, CHAR, WORD, LINE
  1326.         - wrap: if True, will cross boundaries, including top and
  1327.                 bottom; if False, will stop on boundaries.
  1328.         """
  1329.         moved = False
  1330.         if flatReviewType == Context.ZONE:
  1331.             if self.zoneIndex < len(self.lines[self.lineIndex].zones) - 1:
  1332.                 self.zoneIndex += 1
  1333.                 self.wordIndex = 0
  1334.                 self.charIndex = 0
  1335.                 moved = True
  1336.             elif wrap & Context.WRAP_LINE:
  1337.                 if self.lineIndex < len(self.lines) - 1:
  1338.                     self.lineIndex += 1
  1339.                     self.zoneIndex = 0
  1340.                     self.wordIndex = 0
  1341.                     self.charIndex = 0
  1342.                     moved = True
  1343.                 elif wrap & Context.WRAP_TOP_BOTTOM:
  1344.                     self.lineIndex = 0
  1345.                     self.zoneIndex = 0
  1346.                     self.wordIndex = 0
  1347.                     self.charIndex = 0
  1348.                     moved = True
  1349.                 
  1350.             
  1351.         elif flatReviewType == Context.CHAR:
  1352.             zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1353.             if zone.words:
  1354.                 chars = zone.words[self.wordIndex].chars
  1355.                 if chars:
  1356.                     if self.charIndex < len(chars) - 1:
  1357.                         self.charIndex += 1
  1358.                         moved = True
  1359.                     else:
  1360.                         moved = self.goNext(Context.WORD, wrap, False)
  1361.                 else:
  1362.                     moved = self.goNext(Context.WORD, wrap)
  1363.             else:
  1364.                 moved = self.goNext(Context.ZONE, wrap)
  1365.         elif flatReviewType == Context.WORD:
  1366.             zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1367.             accessible = zone.accessible
  1368.             lineIndex = self.lineIndex
  1369.             zoneIndex = self.zoneIndex
  1370.             wordIndex = self.wordIndex
  1371.             charIndex = self.charIndex
  1372.             if zone.words:
  1373.                 if self.wordIndex < len(zone.words) - 1:
  1374.                     self.wordIndex += 1
  1375.                     self.charIndex = 0
  1376.                     moved = True
  1377.                 else:
  1378.                     moved = self.goNext(Context.ZONE, wrap)
  1379.             else:
  1380.                 moved = self.goNext(Context.ZONE, wrap)
  1381.             zone = self.lines[self.lineIndex].zones[self.zoneIndex]
  1382.             if omitWhitespace and moved:
  1383.                 if (len(zone.string) == 0 or len(zone.words)) and zone.words[self.wordIndex].string.isspace():
  1384.                     if zone.accessible == accessible:
  1385.                         moved = self.goNext(Context.WORD, wrap)
  1386.                     else:
  1387.                         wordIndex = self.wordIndex + 1
  1388.                         while wordIndex < len(zone.words):
  1389.                             if not (zone.words[wordIndex].string) and not len(zone.words[wordIndex].string) or zone.words[wordIndex].string.isspace():
  1390.                                 wordIndex += 1
  1391.                                 continue
  1392.                             break
  1393.                         if wordIndex < len(zone.words):
  1394.                             self.wordIndex = wordIndex
  1395.                         
  1396.                 
  1397.             if not moved:
  1398.                 self.lineIndex = lineIndex
  1399.                 self.zoneIndex = zoneIndex
  1400.                 self.wordIndex = wordIndex
  1401.                 self.charIndex = charIndex
  1402.             
  1403.         elif flatReviewType == Context.LINE:
  1404.             if wrap & Context.WRAP_LINE:
  1405.                 if self.lineIndex < len(self.lines) - 1:
  1406.                     self.lineIndex += 1
  1407.                     self.zoneIndex = 0
  1408.                     self.wordIndex = 0
  1409.                     self.charIndex = 0
  1410.                     moved = True
  1411.                 elif wrap & Context.WRAP_TOP_BOTTOM and self.lineIndex != 0:
  1412.                     self.lineIndex = 0
  1413.                     self.zoneIndex = 0
  1414.                     self.wordIndex = 0
  1415.                     self.charIndex = 0
  1416.                     moved = True
  1417.                 
  1418.             
  1419.         else:
  1420.             raise Exception('Invalid type: %d' % flatReviewType)
  1421.         if flatReviewType == Context.ZONE and flatReviewType != Context.LINE:
  1422.             self.targetCharInfo = self.getCurrent(Context.CHAR)
  1423.         
  1424.         return moved
  1425.  
  1426.     
  1427.     def goAbove(self, flatReviewType = LINE, wrap = WRAP_ALL):
  1428.         """Moves this context's locus of interest to first char
  1429.         of the type that's closest to and above the current locus of
  1430.         interest.
  1431.  
  1432.         Arguments:
  1433.         - flatReviewType: LINE
  1434.         - wrap: if True, will cross top/bottom boundaries; if False, will
  1435.                 stop on top/bottom boundaries.
  1436.  
  1437.         Returns: [string, startOffset, endOffset, x, y, width, height]
  1438.         """
  1439.         moved = False
  1440.         if flatReviewType == Context.CHAR:
  1441.             if not self.targetCharInfo:
  1442.                 self.targetCharInfo = self.getCurrent(Context.CHAR)
  1443.             
  1444.             target = self.targetCharInfo
  1445.             (string, x, y, width, height) = target
  1446.             middleTargetX = x + width / 2
  1447.             moved = self.goPrevious(Context.LINE, wrap)
  1448.             if moved:
  1449.                 while True:
  1450.                     (string, bx, by, bwidth, bheight) = self.getCurrent(Context.CHAR)
  1451.                     if bx + width >= middleTargetX:
  1452.                         break
  1453.                         continue
  1454.                     if not self.goNext(Context.CHAR, Context.WRAP_NONE):
  1455.                         break
  1456.                         continue
  1457.             
  1458.             self.targetCharInfo = target
  1459.         elif flatReviewType == Context.LINE:
  1460.             return self.goPrevious(flatReviewType, wrap)
  1461.         raise Exception('Invalid type: %d' % flatReviewType)
  1462.         return moved
  1463.  
  1464.     
  1465.     def goBelow(self, flatReviewType = LINE, wrap = WRAP_ALL):
  1466.         """Moves this context's locus of interest to the first
  1467.         char of the type that's closest to and below the current
  1468.         locus of interest.
  1469.  
  1470.         Arguments:
  1471.         - flatReviewType: one of WORD, LINE
  1472.         - wrap: if True, will cross top/bottom boundaries; if False, will
  1473.                 stop on top/bottom boundaries.
  1474.  
  1475.         Returns: [string, startOffset, endOffset, x, y, width, height]
  1476.         """
  1477.         moved = False
  1478.         if flatReviewType == Context.CHAR:
  1479.             if not self.targetCharInfo:
  1480.                 self.targetCharInfo = self.getCurrent(Context.CHAR)
  1481.             
  1482.             target = self.targetCharInfo
  1483.             (string, x, y, width, height) = target
  1484.             middleTargetX = x + width / 2
  1485.             moved = self.goNext(Context.LINE, wrap)
  1486.             if moved:
  1487.                 while True:
  1488.                     (string, bx, by, bwidth, bheight) = self.getCurrent(Context.CHAR)
  1489.                     if bx + width >= middleTargetX:
  1490.                         break
  1491.                         continue
  1492.                     if not self.goNext(Context.CHAR, Context.WRAP_NONE):
  1493.                         break
  1494.                         continue
  1495.             
  1496.             self.targetCharInfo = target
  1497.         elif flatReviewType == Context.LINE:
  1498.             moved = self.goNext(flatReviewType, wrap)
  1499.         else:
  1500.             raise Exception('Invalid type: %d' % flatReviewType)
  1501.         return flatReviewType == Context.CHAR
  1502.  
  1503.  
  1504.