Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 2736)
+++ src/orca/default.py	(working copy)
@@ -5739,6 +5739,27 @@
 
         return window
 
+    def getContainingRole(self, obj, role):
+        """Returns the object of the specified role which contains the
+        given object, or None if the given object is not contained within
+        an object the specified role.
+
+        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
+    
     def saveOldAppSettings(self):
         """Save a copy of all the existing application specific settings
         (as specified by the settings.userCustomizableSettings dictionary)."""
Index: src/orca/rolenames.py
===================================================================
--- src/orca/rolenames.py	(revision 2736)
+++ src/orca/rolenames.py	(working copy)
@@ -907,6 +907,8 @@
     #
     _("spin button"))
 
+rolenames[ROLE_SPIN_BOX] = rolenames[ROLE_SPIN_BUTTON]
+
 rolenames[ROLE_STATUSBAR] = Rolename(
     ROLE_STATUSBAR,
     # Translators: short braille for the rolename of a statusbar.
Index: src/orca/J2SE-access-bridge.py
===================================================================
--- src/orca/J2SE-access-bridge.py	(revision 2736)
+++ 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
 from orca_i18n import Q_ # to provide qualified translatable strings
@@ -45,11 +47,20 @@
 ########################################################################
 
 class SpeechGenerator(speechgenerator.SpeechGenerator):
-    """Overrides _getSpeechForLabel
-    """
+
     def __init__(self, script):
+        self.debugLevel = debug.LEVEL_SEVERE
+
         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.
 
@@ -100,6 +111,32 @@
 
         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 = []
+        if not already_focused:
+            utterances.extend(self._getSpeechForObjectLabel(obj))
+        utterances.extend(self._getSpeechForObjectName(obj))
+        if not already_focused:
+            utterances.extend(self._getSpeechForObjectRole(obj))
+
+        self._debugGenerator("_getSpeechForSpinBox",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        self._debug("_getSpeechForSpinBox: already_focused=%s, '%s'" % \
+                    (already_focused, 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
@@ -168,6 +205,52 @@
 
         return utterances
 
+class BrailleGenerator(braillegenerator.BrailleGenerator):
+
+    def __init__(self, script):
+        self.debugLevel = debug.LEVEL_SEVERE
+
+        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: '%s'" % text)
+
+        regions = []
+        componentRegion = braille.Component(obj, text)
+        regions.append(componentRegion)
+
+        return [regions, componentRegion]
+
 class Script(default.Script):
 
     def __init__(self, app):
@@ -179,8 +262,12 @@
 
         # Set the debug level for all the methods in this script.
         #
-        self.debugLevel = debug.LEVEL_FINEST
+        self.debugLevel = debug.LEVEL_SEVERE
 
+        self._prevSpinBoxLabel = ""
+        self._prevSpinBoxValue = ""
+        self._prevSpinBoxCaretOffset = -1
+
         default.Script.__init__(self, app)
 
     def _debug(self, msg):
@@ -193,6 +280,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.
 
@@ -205,6 +297,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.
 
@@ -240,7 +342,16 @@
             # A JMenu has always selection.nSelectedChildren > 0
             orca.setLocusOfFocus(event, event.source)
         else:
-            default.Script.onFocus(self, event)
+            spinbox = self.getContainingRole(event.source,
+                                             rolenames.ROLE_SPIN_BOX)
+            if spinbox:
+                # For spin boxes, the text field is the object that gets focus,
+                # not the spin box itself. So, set the locusOfFocus to the
+                # spin box itself.
+                #
+                orca.setLocusOfFocus(event, spinbox)
+            else:
+                default.Script.onFocus(self, event)
 
     def onStateChanged(self, event):
         """Called whenever an object's state changes.  Currently, the
@@ -282,6 +393,7 @@
         # "focus:" event comes before "state-changed:focused" event
         #
         if (event.type == "object:state-changed:focused"):
+            self._debug("ignoring state-changed:focused event")
             return
 
         # Hand state changes when JTree labels become expanded
@@ -375,7 +487,6 @@
 
         default.Script.onValueChanged(self, event)
 
-
     def visualAppearanceChanged(self, event, obj):
         """Called when the visual appearance of an object changes.  This
         method should not be called for objects whose visual appearance
@@ -403,15 +514,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):
@@ -420,33 +522,108 @@
         Arguments:
         - event: the Event
         """
+        obj = event.source
+        self._debug("onCaretMoved: %s name='%s'" %  (obj.role, obj.name))
 
-        self._debug("onCaretMoved: %s '%s'" % \
-                    (event.source.role, event.source.name))
+        # Check whether the event source is inside a spin box. If so, it's
+        # the text field that contains the spin box value.
+        spinbox = self.getContainingRole(obj, rolenames.ROLE_SPIN_BOX)
+        if spinbox:
+            label = self.getDisplayedLabel(spinbox)
+            value = self.getDisplayedText(spinbox)
+            self._debug("  spin box label='%s', value='%s'" % (label, value))
 
+            caretOffset = -1
+            if spinbox.text: # May be None. Bug 350742.
+                caretOffset = spinbox.text.caretOffset
+
+            if label != self._prevSpinBoxLabel or \
+               value != self._prevSpinBoxValue:
+                # Label or value changed.
+                self._prevSpinBoxLabel = label
+                self._prevSpinBoxValue = value
+                # orca.visualAppearanceChanged(event, spinbox)
+                orca.setLocusOfFocus(event, spinbox)
+
+            elif caretOffset != -1 and \
+                 caretOffset != self._prevSpinBoxCaretOffset:
+                # Label and value are the same, but the caret moved.
+                default.Script.onCaretMoved(self, event)
+
+            # Remember the spin box already got focus
+            self._prevSpinBoxCaretOffset = caretOffset
+            return
+
         # Check for bogus events containing null text objects.
-        if event.source.text == None:
+        if obj.text == None:
             self._debug("bogus event: text object is null. Discarding event")
             return;
 
         default.Script.onCaretMoved(self, event)
 
+    def onSelectionChanged(self, event):
+        """Called when an object's selection changes.
+
+        Arguments:
+        - event: the Event
+        """
+        if not event or not event.source:
+            return
+
+        self._debug("onSelectionChanged: %s '%s'" % \
+                    (event.source.role, event.source.name))
+
+        # Ignore spin boxes since the spin box text field also fires
+        # a caret-moved event when the text field gets focus.
+        if self.getContainingRole(event.source, rolenames.ROLE_SPIN_BOX):
+            self._debug("ignoring spinbox onSelectionChanged event")
+            return
+
+        default.Script.onSelectionChanged(self, event)
+
+    def onTextDeleted(self, event):
+        """Called whenever the text is deleted.
+
+        Arguments:
+        - event: the Event
+        """
+        self._debug("onTextDeleted: %s '%s'" % \
+                    (event.source.role, event.source.name))
+
+        # Ignore spin boxes since the spin box text field also fires
+        # a caret-moved event when the text field gets focus.
+        if self.getContainingRole(event.source, rolenames.ROLE_SPIN_BOX):
+            self._debug("ignoring spinbox onTextDeleted event")
+            return
+
+        # Check for bogus events containing null text objects.
+        if event.source.text == None:
+            self._debug("bogus event: text object is null. Discarding event")
+            return
+
+        default.Script.onTextDeleted(self, event)
+
     def onTextInserted(self, event):
         """Called whenever the text is inserted.
 
         Arguments:
         - event: the Event
         """
-
         self._debug("onTextInserted: %s '%s'" % \
                     (event.source.role, event.source.name))
 
+        # Ignore spin boxes since the spin box text field also fires
+        # a caret-moved event when the text field gets focus.
+        if self.getContainingRole(event.source, rolenames.ROLE_SPIN_BOX):
+            self._debug("ignoring spinbox onTextInserted event")
+            return
+
         # Check for bogus events containing null text objects.
         if event.source.text == None:
             self._debug("bogus event: text object is null. Discarding event")
-            return;
+            return
 
-        default.Script.onTextInserted(self, event)        
+        default.Script.onTextInserted(self, event)
 
     def getDisplayedLabel(self, object):
         """If there is an object labelling the given object, return the
