Index: src/orca/settings.py
===================================================================
--- src/orca/settings.py	(revision 3527)
+++ src/orca/settings.py	(working copy)
@@ -155,7 +155,9 @@
     "enabledBrailledTextAttributes",
     "textAttributesBrailleIndicator",
     "enableProgressBarUpdates",
-    "progressBarUpdateInterval"
+    "progressBarUpdateInterval",
+    "enableContractedBraille",
+    "brailleContractionTable"
 ]
 
 # The name of the module that hold the user interface for the main window
@@ -905,3 +907,11 @@
 # changes.  
 #
 inferLiveRegions = True
+
+# Contracted braille support.
+# 
+enableContractedBraille = False
+
+# Contracted braille table.
+# 
+brailleContractionTable = ''
Index: src/orca/orca-setup.glade
===================================================================
--- src/orca/orca-setup.glade	(revision 3527)
+++ src/orca/orca-setup.glade	(working copy)
@@ -1252,8 +1252,8 @@
 				  <property name="wrap">False</property>
 				  <property name="adjustment">10 1 9999 1 10 10</property>
 				  <accessibility>
+				    <atkrelation target="speakProgressBarUnitsLabel" type="labelled-by"/>
 				    <atkrelation target="speakProgressBarLabel" type="labelled-by"/>
-				    <atkrelation target="speakProgressBarUnitsLabel" type="labelled-by"/>
 				  </accessibility>
 				  <signal name="value_changed" handler="speakProgressBarValueChanged" last_modification_time="Thu, 14 Jun 2007 15:09:00 GMT"/>
 				</widget>
@@ -1525,6 +1525,7 @@
 
 		  <child>
 		    <widget class="GtkFrame" id="contractedBrailleFrame">
+		      <property name="visible">True</property>
 		      <property name="label_xalign">0</property>
 		      <property name="label_yalign">0.5</property>
 		      <property name="shadow_type">GTK_SHADOW_NONE</property>
@@ -1549,7 +1550,7 @@
 			      <property name="spacing">0</property>
 
 			      <child>
-				<widget class="GtkCheckButton" id="checkbutton1">
+				<widget class="GtkCheckButton" id="contractedBrailleCheckbutton">
 				  <property name="visible">True</property>
 				  <property name="can_focus">True</property>
 				  <property name="label" translatable="yes">_Enable Contracted Braille</property>
@@ -1612,6 +1613,7 @@
 				      <accessibility>
 					<atkrelation target="contractionTableLabel" type="labelled-by"/>
 				      </accessibility>
+				      <signal name="changed" handler="contractionTableComboChanged" last_modification_time="Fri, 18 Jan 2008 00:39:59 GMT"/>
 				    </widget>
 				    <packing>
 				      <property name="padding">0</property>
@@ -3504,8 +3506,8 @@
 							  <property name="wrap">False</property>
 							  <property name="adjustment">1 24 256 1 10 10</property>
 							  <accessibility>
+							    <atkrelation target="magCursorSizeLabel" type="labelled-by"/>
 							    <atkrelation target="magCursorSizeUnitsLabel" type="labelled-by"/>
-							    <atkrelation target="magCursorSizeLabel" type="labelled-by"/>
 							  </accessibility>
 							  <signal name="value_changed" handler="magCursorSizeValueChanged" last_modification_time="Wed, 03 May 2006 15:31:48 GMT"/>
 							</widget>
@@ -3857,8 +3859,8 @@
 							  <property name="wrap">False</property>
 							  <property name="adjustment">1 1 256 1 10 10</property>
 							  <accessibility>
+							    <atkrelation target="magCrossHairSizeLabel" type="labelled-by"/>
 							    <atkrelation target="magCrossHairSizeUnitsLabel" type="labelled-by"/>
-							    <atkrelation target="magCrossHairSizeLabel" type="labelled-by"/>
 							  </accessibility>
 							  <signal name="value_changed" handler="magCrossHairSizeValueChanged" last_modification_time="Wed, 03 May 2006 15:39:23 GMT"/>
 							</widget>
@@ -4295,8 +4297,8 @@
 						  <property name="wrap">False</property>
 						  <property name="adjustment">0 0 100 1 10 10</property>
 						  <accessibility>
+						    <atkrelation target="magEdgeMarginUnitsLabel" type="labelled-by"/>
 						    <atkrelation target="magEdgeMarginLabel" type="labelled-by"/>
-						    <atkrelation target="magEdgeMarginUnitsLabel" type="labelled-by"/>
 						  </accessibility>
 						  <signal name="value_changed" handler="magEdgeMarginValueChanged" last_modification_time="Thu, 06 Dec 2007 22:57:50 GMT"/>
 						</widget>
Index: src/orca/orca_prefs.py
===================================================================
--- src/orca/orca_prefs.py	(revision 3527)
+++ src/orca/orca_prefs.py	(working copy)
@@ -687,6 +687,8 @@
             elif key == "textAttributesBrailleIndicator":
                 value = self._getTextAttributesBrailleIndicatorString( \
                                                               prefsDict[key])
+            elif key == "brailleContractionTable":
+                value = "'%s'" % prefsDict[key]
             else:
                 value = prefsDict[key]
 
Index: src/orca/braille.py
===================================================================
--- src/orca/braille.py	(revision 3527)
+++ src/orca/braille.py	(working copy)
@@ -38,6 +38,11 @@
 
 import pyatspi
 
+try:
+    import louis
+except ImportError:
+    louis = None
+
 # We'll use the official BrlAPI pythons (as of BrlTTY 3.8) if they
 # are available.  Otherwise, we'll fall back to our own bindings.
 #
@@ -78,6 +83,14 @@
 #
 monitor = None
 
+# Enable contracted braille.
+#
+_contracted = False
+
+# Braille contraction tables in use.
+#
+_contractionTable = None
+
 # Each of these maps to BrlAPI's brldefs.h file.
 #
 CMD_NOOP              = 0x00
@@ -276,13 +289,20 @@
         if not string:
             string = ""
 
+        global _contracted
+        global _contractionTable
+
         string = string.decode("UTF-8")
-        if string[-1:] == "\n":
-            string = string[:-1]
-        self.string = string.encode("UTF-8")
+        string.strip('\n')
+        self.rawLine = string.encode("UTF-8")
 
-        self.cursorOffset = cursorOffset
-
+        if _contracted:
+           self.string, self.inPos, self.outPos, self.cursorOffset = \
+                        self.contractLine(self.rawLine, cursorOffset)
+        else:
+            self.string = self.rawLine
+            self.cursorOffset = cursorOffset
+            
     def processRoutingKey(self, offset):
         """Processes a cursor routing key press on this Component.  The offset
         is 0-based, where 0 represents the leftmost character of string
@@ -300,6 +320,133 @@
         mask = ['\x00'] * len(self.string)
         return "".join(mask)
 
+    def _divideLine(self, line, offset):
+        """Isolates a word the cursor is on and returns the prefix,
+        the cursored word, and the suffix. This is a temporary method
+        that avoids a liblouis bug.
+
+        Arguments:
+        - line: The line to process.
+        - offset: The offset of the cursor.
+        """
+        if len(line) <= offset:
+            return line, '', ''
+        firstHalf = line[:offset].rpartition(' ')
+        secondHalf = line[offset:].partition(' ')
+        return firstHalf[0] + firstHalf[1], \
+               firstHalf[2] + secondHalf[0], \
+               secondHalf[1] + secondHalf[2]
+
+    def _mushPosArrays(self, prefixPos, uncontractedLen, suffixPos):
+        try:
+            uncontractedPos = range(prefixPos[-1] + 1,
+                                    uncontractedLen+prefixPos[-1] + 1)
+        except IndexError:
+            uncontractedPos = range(uncontractedLen)
+
+        pos = prefixPos + \
+              uncontractedPos
+        
+        try:
+            suffixOffset = pos[-1] + 1
+        except IndexError:
+            suffixOffset = 0
+            
+        pos += [offs + suffixOffset for offs in suffixPos]
+
+        return pos
+
+    def _expandAtCursor(self, line, cursorOffset):
+        """Contracts a line, leaving the word under the cursor expanded.
+        This is a temporary method that should disappear once liblouis
+        get it right.
+
+        Arguments:
+        - line: Line to contract.
+        - cursorOffset: Offset of cursor.
+        """
+        prefix, uncontractedWord, suffix  = \
+                self._divideLine(line, cursorOffset)
+        
+        cursorInWord = cursorOffset - len(prefix)
+
+        # Contract prefix
+        # 
+        prefixContracted, prefixInPos, prefixOutPos, cursorPos = \
+                          louis.translate([_contractionTable],
+                                          prefix.decode())
+
+        # Contract suffix
+        # 
+        suffixContracted, suffixInPos, suffixOutPos, cursorPos = \
+                          louis.translate([_contractionTable],
+                                          suffix.decode())
+
+        cursorPos = len(prefixContracted) + cursorInWord
+
+        contracted = prefixContracted + \
+                          uncontractedWord + \
+                          suffixContracted
+
+        inPos = self._mushPosArrays(prefixInPos,
+                                    len(uncontractedWord),
+                                    suffixInPos)
+
+        outPos = self._mushPosArrays(prefixOutPos,
+                                     len(uncontractedWord),
+                                     suffixOutPos)
+        
+        return contracted, inPos, outPos, cursorPos
+    
+    def contractLine(self, line, cursorOffset=0, expandOnCursor=False):
+        """Contract the given line. Returns the contracted line, and the
+        cursor position in the contracted line.
+
+        Arguments:
+        - line: Line to contract.
+        - cursorOffset: Offset of cursor,defaults to 0.
+        - expandOnCursor: Expand word under cursor, False by default.
+        """
+#        print 'line=%s offset=%s expand=%s' % \
+#              (line, cursorOffset, expandOnCursor)
+        try:
+            cursorOnSpace = line[cursorOffset] == ' '
+        except IndexError:
+            cursorOnSpace = False
+            
+        if not expandOnCursor or cursorOnSpace:
+            contracted, inPos, outPos, cursorPos = \
+                             louis.translate([_contractionTable],
+                                             line.decode(),
+                                             cursorPos=cursorOffset)
+            self.string = contracted
+        else:
+            contracted, inPos, outPos, cursorPos,  = \
+                             self._expandAtCursor(line, cursorOffset)
+            
+            # The liblouis expand at cursor functionality is not perfect yet.
+            #contracted, self.inPos, self.outPos, cursorPos = \
+            #                 louis.translate([_contractionTable],
+            #                                 self.rawLine.decode(),
+            #                                 cursorPos=cursorOffset,
+            #                                 mode=louis.MODE.compbrlAtCursor)
+        return contracted, inPos, outPos, cursorPos
+    
+    def displayToBufferOffset(self, display_offset):
+        global _contracted
+        
+        if not _contracted:
+            return display_offset
+        
+        offset = -1
+        while offset < 0:
+            try:
+                offset = self.inPos[display_offset]
+            except IndexError:
+                offset = len(self.rawLine)
+            display_offset += 1
+        return offset
+
 class Component(Region):
     """A subclass of Region backed by an accessible.  This Region will react
     to any cursor routing key events and perform the default action on the
@@ -356,7 +503,8 @@
         - accessible: the accessible that implements AccessibleText
         - label: an optional label to display
         """
-
+        global _contracted
+        
         self.accessible = accessible
         if orca_state.activeScript:
             [string, self.caretOffset, self.lineOffset] = \
@@ -381,34 +529,46 @@
         self._maxCaretOffset = self.lineOffset + len(string.decode("UTF-8"))
 
         self.eol = eol
-        string = string + self.eol
-
         self.label = label
-        if self.label:
-            string = self.label + " " + string
-            cursorOffset += len(self.label.decode("UTF-8")) + 1
-
+        self.labelOffset = 0
+        
         Region.__init__(self, string, cursorOffset)
 
+        if not self.string.endswith(self.eol):
+            # EOL was not appended yet.
+            self.string += self.eol
+
+        if not _contracted and self.label:
+            self.string = self.label + ' '+ self.string
+            self.labelOffset = len(self.label) + 1
+
+        self.cursorOffset += self.labelOffset
+            
     def repositionCursor(self):
         """Attempts to reposition the cursor in response to a new
         caret position.  If it is possible (i.e., the caret is on
         the same line as it was), reposition the cursor and return
         True.  Otherwise, return False.
         """
+        global _contractionTable
+        global _contracted
 
         [string, caretOffset, lineOffset] = \
                  orca_state.activeScript.getTextLineAtCaret(self.accessible)
         cursorOffset = min(caretOffset - lineOffset, len(string))
-        if self.label:
-            cursorOffset += len(self.label.decode("UTF-8")) + 1
-
+        
         if lineOffset != self.lineOffset:
             return False
+
         else:
             self.caretOffset = caretOffset
             self.lineOffset = lineOffset
-            self.cursorOffset = cursorOffset
+                        
+            if _contracted:
+                self.string, self.inPos, self.outPos, cursorOffset = \
+                           self.contractLine(self.rawLine, cursorOffset, True)
+                
+            self.cursorOffset = cursorOffset + self.labelOffset
 
         return True
 
@@ -417,11 +577,12 @@
         is 0-based, where 0 represents the leftmost character of text
         associated with this region.  Note that the zeroeth character may have
         been scrolled off the display."""
+        
+        offset -= self.labelOffset
+        if offset < 0:
+            return
 
-        if self.label:
-            offset = offset - len(self.label.decode("UTF-8")) - 1
-            if offset < 0:
-                return
+        offset = self.displayToBufferOffset(offset)
 
         newCaretOffset = min(self.lineOffset + offset, self._maxCaretOffset)
         self.accessible.queryText().setCaretOffset(newCaretOffset)
@@ -528,6 +689,20 @@
 
         return mask
 
+    def contractLine(self, line, cursorOffset=0, expandOnCursor=True):
+        contractedLabel = ''
+        if self.label:
+            contractedLabel, inPos, outPos, cursorPos = Region.contractLine(
+                self, self.label, 0, False)
+            contractedLabel += ' '
+            self.labelOffset = len(contractedLabel)
+
+        contracted, inPos, outPos, cursorPos = Region.contractLine(
+            self, line, cursorOffset, expandOnCursor)
+
+        return contractedLabel + contracted + self.eol, \
+               inPos, outPos, cursorPos
+    
 class ReviewComponent(Component):
     """A subclass of Component that is to be used for flat review mode."""
 
@@ -573,6 +748,7 @@
         associated with this region.  Note that the zeroeth character may have
         been scrolled off the display."""
 
+        offset = self.displayToBufferOffset(offset)
         newCaretOffset = self.lineOffset + offset
         self.accessible.queryText().setCaretOffset(newCaretOffset)
 
@@ -1157,12 +1333,25 @@
     global _initialized
     global _displaySize
     global _callback
+    global _contracted
+    global _contractionTable
 
     if _initialized:
         return False
 
     _callback = callback
 
+    if settings.enableContractedBraille and louis is None:
+        debug.println(
+            debug.LEVEL_WARNING,
+            "Contracted Braille enabled but louis module is not installed")
+    else:
+        _contracted = settings.enableContractedBraille
+        
+        if _contracted:
+            _contractionTable = settings.brailleContractionTable or \
+                                louis.getDefaultTable()
+
     if useBrlAPIBindings:
         try:
             global brlAPI
Index: src/orca/orca_gui_prefs.py
===================================================================
--- src/orca/orca_gui_prefs.py	(revision 3527)
+++ src/orca/orca_gui_prefs.py	(working copy)
@@ -49,6 +49,11 @@
 import speech
 import speechserver
 
+try:
+    import louis
+except ImportError:
+    louis = None
+
 from orca_i18n import _  # for gettext support
 
 (HANDLER, DESCRIP, MOD_MASK1, MOD_USED1, KEY1, OLDTEXT1, TEXT1, \
@@ -947,7 +952,13 @@
     def contractedBrailleToggled(self, checkbox):
         hbox = self.widgets.get_widget('contractionTablesHBox')
         hbox.set_sensitive(checkbox.get_active())
+        self.prefsDict["enableContractedBraille"] = checkbox.get_active()
 
+    def contractionTableComboChanged(self, combobox):
+        model = combobox.get_model()
+        iter = combobox.get_active_iter()
+        self.prefsDict["brailleContractionTable"] = model[iter][1]
+        
     def textAttributeSpokenToggled(self, cell, path, model):
         """The user has toggled the state of one of the text attribute
         checkboxes to be spoken. Update our model to reflect this, then
@@ -1431,6 +1442,36 @@
         state = prefs["brailleRolenameStyle"] == \
                             settings.BRAILLE_ROLENAME_STYLE_SHORT
         self.get_widget("abbrevRolenames").set_active(state)
+        if louis is None:
+            self.get_widget( \
+                "contractedBrailleCheckbutton").set_sensitive(False)
+        else:
+            self.get_widget("contractedBrailleCheckbutton").set_active( \
+                prefs["enableContractedBraille"])
+            # Set up contraction table combo box and set it to the
+            # currently used one.
+            # 
+            tablesCombo = self.get_widget("contractionTableCombo")
+            tableDict = louis.listTables()
+            selectedTableIter = None
+            selectedTable = prefs["brailleContractionTable"] or \
+                             louis.getDefaultTable()
+            if tableDict:
+                tablesModel = gtk.ListStore(str, str)
+                for name, fname in tableDict.items():
+                    it = tablesModel.append([name, fname])
+                    if os.path.join(louis.TABLES_DIR, fname) == selectedTable:
+                        selectedTable = it
+                cell = gtk.CellRendererText()
+                tablesCombo.pack_start(cell, True)
+                tablesCombo.add_attribute(cell, 'text', 0)
+                tablesCombo.set_model(tablesModel)
+                if selectedTable:
+                    tablesCombo.set_active_iter(selectedTable)
+                else:
+                    tablesCombo.set_active(0)
+            else:
+                tablesCombo.set_sensitive(False)
         if prefs["brailleVerbosityLevel"] == settings.VERBOSITY_LEVEL_BRIEF:
             self.get_widget("brailleBriefButton").set_active(True)
         else:
