Index: src/orca/J2SE-access-bridge.py
===================================================================
--- src/orca/J2SE-access-bridge.py	(revision 2588)
+++ src/orca/J2SE-access-bridge.py	(working copy)
@@ -31,9 +31,11 @@
 import keybindings
 import settings
 import speech
+import braille
 import rolenames
 import Accessibility
 import speechgenerator
+import braillegenerator
 
 from orca_i18n import _ # for gettext support
 
@@ -44,11 +46,20 @@
 ########################################################################
 
 class SpeechGenerator(speechgenerator.SpeechGenerator):
-    """Overrides _getSpeechForLabel
-    """
+
     def __init__(self, script):
+        self.debugLevel = debug.LEVEL_FINEST
+
         speechgenerator.SpeechGenerator.__init__(self, script)
 
+        self.speechGenerators[rolenames.ROLE_SPIN_BOX] = \
+            self._getSpeechForSpinBox
+
+    def _debug(self, msg):
+        """ Convenience method for printing debug messages
+        """
+        debug.println(self.debugLevel, "J2SE-access-bridge.py: "+msg)
+
     def _getSpeechForLabel(self, obj, already_focused):
         """Get the speech for a label.
 
@@ -91,6 +102,29 @@
 
         return utterances
 
+    def _getSpeechForSpinBox(self, obj, already_focused):
+        """Get the speech for a spin box.  If the object already has
+        focus, then only the new value is spoken.
+
+        Arguments:
+        - obj: the spin button
+        - already_focused: False if object just received focus
+
+        Returns a list of utterances to be spoken for the object.
+        """
+        utterances = []
+        utterances.extend(self._getSpeechForObjectLabel(obj))
+        utterances.extend(self._getSpeechForObjectName(obj))
+        utterances.extend(self._getSpeechForObjectRole(obj))
+
+        self._debugGenerator("_getSpeechForSpinBox",
+                             obj,
+                             already_focused,
+                             utterances)
+        
+        self._debug("_getSpeechForSpinBox: '%s'" % utterances)
+        return utterances
+
     def getSpeechContext(self, obj, stopAncestor=None):
         """Get the speech that describes the names and role of
         the container hierarchy of the object, stopping at and
@@ -159,8 +193,58 @@
 
         return utterances
 
+class BrailleGenerator(braillegenerator.BrailleGenerator):
+
+    def __init__(self, script):
+        self.debugLevel = debug.LEVEL_FINEST
+
+        braillegenerator.BrailleGenerator.__init__(self, script)
+
+        self.brailleGenerators[rolenames.ROLE_SPIN_BOX]         = \
+             self._getBrailleRegionsForSpinBox
+
+    def _debug(self, msg):
+        """ Convenience method for printing debug messages
+        """
+        debug.println(self.debugLevel, "J2SE-access-bridge.py: "+msg)
+
+    def _getBrailleRegionsForSpinBox(self, obj):
+        """Get the braille for a spin box.  If the object already has
+        focus, then only the new value is displayed.
+
+        Arguments:
+        - obj: the spin button
+
+        Returns a list where the first element is a list of Regions to display
+        and the second element is the Region which should get focus.
+        """
+
+        self._debugGenerator("_getBrailleRegionsForSpinBox", obj)
+
+        regions = []
+
+        text = ""
+        text = self._script.appendString(text,
+                                         self._script.getDisplayedLabel(obj))
+        
+        text = self._script.appendString(text,
+                                         self._script.getDisplayedText(obj))
+
+        text = self._script.appendString(text,
+                                         self._getTextForRole(obj))
+        
+        self._debug("_getBrailleRegionsForSpinBox: text='%s'" % text)
+
+        regions = []
+        componentRegion = braille.Component(obj, text)
+        regions.append(componentRegion)
+
+        return [regions, componentRegion]
+
 class Script(default.Script):
 
+    _prevSpinBoxValue = ""
+
     def __init__(self, app):
         """Creates a new script for Java applications.
 
@@ -184,6 +268,11 @@
         """
         return SpeechGenerator(self)
 
+    def getBrailleGenerator(self):
+        """Returns the braille generator for this script.
+        """
+        return BrailleGenerator(self)
+
     def consumesKeyboardEvent(self, keyboardEvent):
         """Called when a key is pressed on the keyboard.
 
@@ -196,6 +285,16 @@
         keyboardEvent.hw_code = keybindings.getKeycode(keysym)
         return default.Script.consumesKeyboardEvent(self, keyboardEvent)
 
+    def onWindowActivated(self, event):
+        """Called whenever a toplevel window is activated.
+
+        Arguments:
+        - event: the Event
+        """
+        self._debug("onWindowActivated: %s" % event.source.role)
+        self._prevSpinBoxValue = ""
+        default.Script.onWindowActivated(self, event)
+
     def onFocus(self, event):
         """Called whenever an object gets focus.
 
@@ -231,7 +330,14 @@
             # A JMenu has always selection.nSelectedChildren > 0
             orca.setLocusOfFocus(event, event.source)
         else:
-            default.Script.onFocus(self, event)
+            # Ignore spin boxes since the spin box text field also fires
+            # a caret-moved event when the text field gets focus.
+            spinbox = self._getAncestorWithRole(event.source,
+                                                rolenames.ROLE_SPIN_BOX)
+            if spinbox:
+                return
+            else:
+                default.Script.onFocus(self, event)
 
     def onStateChanged(self, event):
         """Called whenever an object's state changes.  Currently, the
@@ -394,15 +500,6 @@
                 speech.speakUtterances(self.speechGenerator.getSpeech(orca_state.locusOfFocus, True))
                 return
 
-        if obj.role == rolenames.ROLE_SPIN_BOX:
-            # Check for the spinbox text object being null. There appears
-            # to be a bug where the text object for some text fields
-            # is null. 
-            if orca_state.locusOfFocus.role == rolenames.ROLE_TEXT and \
-               orca_state.locusOfFocus.text == None:
-                # Set the locusOfFocus to the spinbox itself.
-                orca.setLocusOfFocus(event, obj)
-
         default.Script.visualAppearanceChanged(self, event, obj)
 
     def onCaretMoved(self, event):
@@ -415,6 +512,24 @@
         self._debug("onCaretMoved: %s '%s'" % \
                     (event.source.role, event.source.name))
 
+        # Check whether the event source is in a spin box. If so,
+        # the label is the spin box name and the text is the
+        # spin box value.
+        spinbox = self._getAncestorWithRole(event.source,
+                                            rolenames.ROLE_SPIN_BOX)
+        if spinbox:
+            self._debug("  spinbox label='%s', value='%s'" % \
+                        (self.getDisplayedLabel(spinbox),
+                         self.getDisplayedText(spinbox)))
+            
+            value = self.getDisplayedText(spinbox)
+            if value != self._prevSpinBoxValue:
+                self._prevSpinBoxValue = value
+                self.updateBraille(spinbox)
+                utterances = self.speechGenerator.getSpeech(spinbox, False)
+                speech.speakUtterances(utterances)
+            return
+        
         # Check for bogus events containing null text objects.
         if event.source.text == None:
             self._debug("bogus event: text object is null. Discarding event")
@@ -473,3 +588,23 @@
                         return labelFor.rstrip(":")
 
         return label
+
+    def _getAncestorWithRole(self, obj, role):
+        """Returns an object's ancestor with the specified role if it
+        exists. Otherwise, returns None.
+
+        Arguments:
+        - obj: the Accessible object
+        - role: the role to look for
+        """
+        while obj \
+              and (obj != obj.parent) \
+              and (obj.role != role):
+            obj = obj.parent
+
+        if obj and (obj.role == role):
+            pass
+        else:
+            obj = None
+
+        return obj
