[orca] Fix for bgo#523693 - Positioning of the cursor when editing text



commit 3a50cd580c7b6f0f644252c60ee02de8ade2db6c
Author: Willie Walker <william walker sun com>
Date:   Sun Aug 9 14:25:52 2009 -0400

    Fix for bgo#523693 - Positioning of the cursor when editing text
    
    This patch fixes a 'jumping cursor' problem when deleting text in the
    middle of a long text area and also adds a _realignViewport method to
    braille.py.  The method works off new settings:
    
    ALIGN_BRAILLE_BY_EDGE   = 0
    ALIGN_BRAILLE_BY_MARGIN = 1
    ALIGN_BRAILLE_BY_WORD   = 2
    brailleAlignmentMargin  = 3
    brailleMaximumJump      = 8
    brailleAlignmentStyle   = ALIGN_BRAILLE_BY_EDGE
    
    The default alignment style matches what we have today.
    
    The ALIGN_BRAILLE_BY_MARGIN style uses brailleAlignmentMargin and is
    effectively a "push" model - when you get to the edge of the display, the
    viewport is pushed to keep the cursor cell at the margin (until you reach the
    edge of the text).
    
    The ALIGN_BRAILLE_BY_WORD style uses brailleAlignmentMargin in the same push
    model above, but when it pushes the viewport, it pushes it so the edge lands on
    a word boundary.  In the event we hit a really long word, the
    brailleMaximumJump setting limits how far we jump.

 src/orca/braille.py           |  179 +++++++++++++++++++++++++++++++++++++++++
 src/orca/braille_generator.py |    3 +-
 src/orca/orca_prefs.py        |   13 +++
 src/orca/settings.py          |   14 +++
 4 files changed, 208 insertions(+), 1 deletions(-)
---
diff --git a/src/orca/braille.py b/src/orca/braille.py
index 39b14d7..ffbea0b 100644
--- a/src/orca/braille.py
+++ b/src/orca/braille.py
@@ -195,6 +195,15 @@ _lines = []
 #
 _regionWithFocus = None
 
+# The last text information painted.  This has the following fields:
+#
+# lastTextObj = the last accessible
+# lastCaretOffset = the last caret offset of the last text displayed
+# lastLineOffset = the last line offset of the last text displayed
+# lastCursorCell = the last cell on the braille display for the caret
+#
+_lastTextInfo = (None, 0, 0, 0)
+
 # The viewport is a rectangular region of size _displaySize whose upper left
 # corner is defined by the point (x, line number).  As such, the viewport is
 # identified solely by its upper left point.
@@ -1063,6 +1072,124 @@ def setFocus(region, panToFocus=True, getLinkMask=True):
 
     viewport[0] = max(0, offset)
 
+def _realignViewport(string, focusOffset, cursorOffset):
+    """Realigns the braille display to account for braille alignment
+    preferences.  By the time this method is called, if there is a
+    cursor cell to be displayed, it should already be somewhere in
+    the viewport.  All we're going to do is adjust the viewport a
+    little to align the viewport edge according to the
+    settings.brailleAlignmentStyle.
+
+    Arguments:
+    - string: the entire string to be presented
+    - focusOffset: where in string the focused region begins
+    - cursorOffset: where in the string the cursor should be
+
+    Returns: the viewport[0] value is potentially modified.
+    """
+
+    # pylint complains we don't set viewport, which in fact we do if
+    # 'jump' ends up being set.
+    #
+    # pylint: disable-msg=W0602
+    #
+    global viewport
+
+    jump = 0
+
+    # If there's no cursor to show or we're doing
+    # ALIGN_BRAILLE_BY_EDGE, the viewport should already be where it
+    # belongs.  Otherwise, we may need to do some adjusting of the
+    # viewport.
+    #
+    if (cursorOffset < 0) \
+       or (settings.brailleAlignmentStyle == settings.ALIGN_BRAILLE_BY_EDGE):
+        pass
+    else:
+        # The left and right margin values are absolute values in the
+        # string and represent where in the string the margins of the
+        # current viewport lie.  Note these are margins and not the
+        # actual edges of the viewport.
+        #
+        leftMargin = viewport[0] + settings.brailleAlignmentMargin
+        rightMargin = (viewport[0] + _displaySize[0]) \
+                      - settings.brailleAlignmentMargin
+
+        # This represents how far left in the string we want to search
+        # and also how far left we'll realign the viewport. Setting it
+        # to focusOffset means we won't move the viewport further left
+        # than the beginning of the current region with focus.
+        #
+        leftMostEdge = max(0, focusOffset)
+
+        # If we align by margin, we just want to keep the cursor at or
+        # in between the margins.  The only time we go outside the
+        # margins are when we are at the ends of the string.
+        #
+        if settings.brailleAlignmentStyle == settings.ALIGN_BRAILLE_BY_MARGIN:
+            if cursorOffset < leftMargin:
+                jump = cursorOffset - leftMargin
+            elif cursorOffset > rightMargin:
+                jump = cursorOffset - rightMargin
+        elif settings.brailleAlignmentStyle == settings.ALIGN_BRAILLE_BY_WORD:
+            # When we align by word, we want to try to show complete
+            # words at the edges of the braille display.  When we're
+            # near the left edge, we'll try to start a word at the
+            # left edge.  When we're near the right edge, we'll try to
+            # end a word at the right edge.
+            #
+            if cursorOffset < leftMargin:
+                # Find the index of the character that is the first
+                # letter of the word prior to left edge of the
+                # viewport.
+                #
+                inWord = False
+                leftWordEdge = viewport[0] - 1
+                while leftWordEdge >= leftMostEdge:
+                    if not string[leftWordEdge] in ' \t\n\r\v\f':
+                        inWord = True
+                    elif inWord:
+                        leftWordEdge += 1
+                        break
+                    leftWordEdge -= 1
+                leftWordEdge = max(leftMostEdge, leftWordEdge)
+                jump = leftWordEdge - viewport[0]
+            elif cursorOffset > rightMargin:
+                # Find the index of the character that is the last
+                # letter of the word after the right edge of the
+                # viewport.
+                #
+                inWord = False
+                rightWordEdge = viewport[0] + _displaySize[0]
+                while rightWordEdge < len(string):
+                    if not string[rightWordEdge] in ' \t\n\r\v\f':
+                        inWord = True
+                    elif inWord:
+                        break
+                    rightWordEdge += 1
+                rightWordEdge = min(len(string), rightWordEdge)
+                jump = max(0, rightWordEdge - (viewport[0] + _displaySize[0]))
+
+            # We use the brailleMaximumJump to help us handle really
+            # long words.  The (jump/abs(jump)) stuff is a quick and
+            # dirty way to retain the sign (i.e., +1 or -1).
+            #
+            if abs(jump) > settings.brailleMaximumJump:
+                jump = settings.brailleMaximumJump * (jump/abs(jump))
+
+    if jump:
+        # Set the viewport's left edge based upon the jump, making
+        # sure we don't go any farther left than the leftMostEdge.
+        #
+        viewport[0] = max(leftMostEdge, viewport[0] + jump)
+
+        # Now, make sure we don't scroll too far to the right.  That
+        # is, avoid showing blank spaces to the right if there is more
+        # of the string that can be shown.
+        #
+        viewport[0] = min(viewport[0],
+                          max(leftMostEdge, len(string) - _displaySize[0]))
+
 def refresh(panToCursor=True,
             targetCursorCell=0,
             getLinkMask=True,
@@ -1094,6 +1221,21 @@ def refresh(panToCursor=True,
     global beginningIsShowing
     global cursorCell
     global _monitor
+    global _lastTextInfo
+
+    # Check out what we were displaying the last time - it might be
+    # the same text object we are displaying now.
+    #
+    (lastTextObj, lastCaretOffset, lastLineOffset, lastCursorCell) = \
+        _lastTextInfo
+    if _regionWithFocus and isinstance(_regionWithFocus, Text):
+        currentTextObj = _regionWithFocus.accessible
+        currentCaretOffset = _regionWithFocus.caretOffset
+        currentLineOffset = _regionWithFocus.lineOffset
+    else:
+        currentTextObj = None
+        currentCaretOffset = 0
+        currentLineOffset = 0
 
     if stopFlash:
         killFlash(restoreSaved=False)
@@ -1109,6 +1251,7 @@ def refresh(panToCursor=True,
                               "BrlTTY seems to have disappeared:")
                 debug.printException(debug.LEVEL_WARNING)
                 shutdown()
+        _lastTextInfo = (None, 0, 0, 0)
         return
 
     # Now determine the location of the cursor.  First, we'll figure
@@ -1119,6 +1262,26 @@ def refresh(panToCursor=True,
     if targetCursorCell < 0:
         targetCursorCell = _displaySize[0] + targetCursorCell + 1
 
+    # If there is no target cursor cell, then try to set one.  We
+    # currently only do this for text objects, and we do so by looking
+    # at the last position of the caret offset and cursor cell.  The
+    # primary goal here is to keep the cursor movement on the display
+    # somewhat predictable.
+    #
+    if (targetCursorCell == 0) \
+       and currentTextObj and (currentTextObj == lastTextObj) \
+       and (currentLineOffset == lastLineOffset):
+        if lastCaretOffset == currentCaretOffset:
+            targetCursorCell = lastCursorCell
+        elif lastCaretOffset < currentCaretOffset:
+            targetCursorCell = min(_displaySize[0],
+                                   lastCursorCell \
+                                   + (currentCaretOffset - lastCaretOffset))
+        elif lastCaretOffset > currentCaretOffset:
+            targetCursorCell = max(1,
+                                   lastCursorCell \
+                                   - (lastCaretOffset - currentCaretOffset))
+
     # Now, we figure out the 0-based offset for where the cursor
     # actually is in the string.
     #
@@ -1143,6 +1306,12 @@ def refresh(panToCursor=True,
         elif cursorOffset >= (viewport[0] + _displaySize[0]):
             viewport[0] = max(0, cursorOffset - _displaySize[0] + 1)
 
+    # The cursorOffset should be somewhere in the viewport right now.
+    # Let's try to realign the viewport so that the cursor shows up
+    # according to the settings.brailleAlignmentStyle setting.
+    #
+    _realignViewport(string, focusOffset, cursorOffset)
+
     startPos = viewport[0]
     endPos = startPos + _displaySize[0]
 
@@ -1223,6 +1392,16 @@ def refresh(panToCursor=True,
     beginningIsShowing = startPos == 0
     endIsShowing = endPos >= len(string)
 
+    # Remember the text information we were presenting (if any)
+    #
+    if _regionWithFocus and isinstance(_regionWithFocus, Text):
+        _lastTextInfo = (_regionWithFocus.accessible,
+                         _regionWithFocus.caretOffset,
+                         _regionWithFocus.lineOffset,
+                         cursorCell)
+    else:
+        _lastTextInfo = (None, 0, 0, 0)
+
 def _flashCallback():
     global _lines
     global _regionWithFocus
diff --git a/src/orca/braille_generator.py b/src/orca/braille_generator.py
index 6a1e2e0..8f721a2 100644
--- a/src/orca/braille_generator.py
+++ b/src/orca/braille_generator.py
@@ -297,7 +297,8 @@ class BrailleGenerator(generator.Generator):
             text = obj.queryText()
         except NotImplementedError:
             text = None
-        if text and self._script.isTextArea(obj):
+        if text and (self._script.isTextArea(obj) \
+                     or (obj.getRole() in [pyatspi.ROLE_LABEL])):
             [lineString, startOffset, endOffset] = text.getTextAtOffset(
                 text.caretOffset,
                 pyatspi.TEXT_BOUNDARY_LINE_START)
diff --git a/src/orca/orca_prefs.py b/src/orca/orca_prefs.py
index 6d34df9..67d42e3 100644
--- a/src/orca/orca_prefs.py
+++ b/src/orca/orca_prefs.py
@@ -313,6 +313,17 @@ class OrcaPrefs:
         else:
             return "orca.settings.BRAILLE_ROLENAME_STYLE_LONG"
 
+    def _getBrailleAlignmentStyleString(self, brailleAlignmentStyle):
+        """Returns a string that represents the brailleAlignmentStyle
+         passed in."""
+
+        if brailleAlignmentStyle == settings.ALIGN_BRAILLE_BY_WORD:
+            return "orca.settings.ALIGN_BRAILLE_BY_WORD"
+        if brailleAlignmentStyle == settings.ALIGN_BRAILLE_BY_MARGIN:
+            return "orca.settings.ALIGN_BRAILLE_BY_MARGIN"
+        else:
+            return "orca.settings.ALIGN_BRAILLE_BY_EDGE"
+
     def _getVerbalizePunctuationStyleString(self, punctuationStyle):
         """Returns a string that represents the punctuation style passed in."""
 
@@ -702,6 +713,8 @@ class OrcaPrefs:
                 value = self._getBrailleSelectionIndicatorString(prefsDict[key])
             elif key == "brailleLinkIndicator":
                 value = self._getBrailleLinkIndicatorString(prefsDict[key])
+            elif key == "brailleAlignmentStyle":
+                value = self._getBrailleAlignmentStyleString(prefsDict[key])
             elif key == "verbalizePunctuationStyle":
                 value = self._getVerbalizePunctuationStyleString(prefsDict[key])
             elif key == "sayAllStyle":
diff --git a/src/orca/settings.py b/src/orca/settings.py
index a8e352f..ce186ff 100644
--- a/src/orca/settings.py
+++ b/src/orca/settings.py
@@ -119,6 +119,7 @@ userCustomizableSettings = [
     "brailleRolenameStyle",
     "brailleSelectorIndicator",
     "brailleLinkIndicator",
+    "brailleAlignmentStyle",
     "enableBrailleMonitor",
     "enableMagnifier",
     "enableMagLiveUpdating",
@@ -272,6 +273,19 @@ BRAILLE_LINK_8    = 0x80 # 10000000
 BRAILLE_LINK_BOTH = 0xc0 # 11000000
 brailleLinkIndicator = BRAILLE_LINK_BOTH
 
+# Braille alignment styles.  These say how to align text on the
+# edges of the braille display.  The brailleAlignmentMargin value
+# says how close to the edge of the braille the display the cursor
+# cell can get.  The brailleMaximumJump says how far we can jump
+# the display when aligning by word.
+#
+ALIGN_BRAILLE_BY_EDGE   = 0
+ALIGN_BRAILLE_BY_MARGIN = 1
+ALIGN_BRAILLE_BY_WORD   = 2
+brailleAlignmentMargin  = 3
+brailleMaximumJump      = 8
+brailleAlignmentStyle   = ALIGN_BRAILLE_BY_EDGE
+
 # Speech punctuation levels (see verbalizePunctuationStyle).
 #
 PUNCTUATION_STYLE_NONE = 3



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]