Index: src/orca/scripts/toolkits/J2SE-access-bridge.py
===================================================================
--- src/orca/scripts/toolkits/J2SE-access-bridge.py	(revision 4007)
+++ src/orca/scripts/toolkits/J2SE-access-bridge.py	(working copy)
@@ -25,27 +25,53 @@
 
 import pyatspi
 
+import orca.braille as braille
+import orca.braillegenerator as braillegenerator
 import orca.default as default
-import orca.debug as debug
+import orca.input_event as input_event
 import orca.orca as orca
 import orca.orca_state as orca_state
 import orca.keybindings as keybindings
-import orca.speech as speech
 import orca.rolenames as rolenames
 import orca.speechgenerator as speechgenerator
+import orca.where_am_I as where_am_I
 
 from orca.orca_i18n import _ # for gettext support
-from orca.orca_i18n import Q_ # to provide qualified translatable strings
 
 ########################################################################
 #                                                                      #
-# The Java script class.                                               #
+# Custom WhereAmI                                                      #
 #                                                                      #
 ########################################################################
 
+class WhereAmI(where_am_I.WhereAmI):
+    def __init__(self, script):
+        where_am_I.WhereAmI.__init__(self, script)
+
+    def whereAmI(self, obj, basicOnly):
+        """Calls the base class method for basic information and Java
+        specific presentation methods for detailed/custom information.
+        """
+
+        # If we're in the text area of a spin button, then we'll do the
+        # where am I for the spin button.
+        #
+        if obj and obj.getRole() == pyatspi.ROLE_TEXT:
+            spinbox = self._script.getAncestor(obj,
+                                               [pyatspi.ROLE_SPIN_BUTTON],
+                                               None)
+            if spinbox:
+                obj = spinbox
+
+        where_am_I.WhereAmI.whereAmI(self, obj, basicOnly)
+
+########################################################################
+#                                                                      #
+# Speech Generator                                                     #
+#                                                                      #
+########################################################################
+
 class SpeechGenerator(speechgenerator.SpeechGenerator):
-    """Overrides _getSpeechForLabel
-    """
     def __init__(self, script):
         speechgenerator.SpeechGenerator.__init__(self, script)
 
@@ -61,9 +87,15 @@
 
         utterances = []
         if (not already_focused):
-            utterances = self._getDefaultSpeech(obj, already_focused)
+            text = self._script.getDisplayedText(obj)
+            if not text:
+                text = rolenames.getSpeechForRoleName(obj)
+            if text:
+                utterances.append(text)
 
-        obj.was_selected = False
+        # In Java, tree objects are labels, so we need to look at their
+        # states in order to tell whether they are expanded or collapsed.
+        #
         state = obj.getState()
         if state.contains(pyatspi.STATE_EXPANDED):
             # Translators: this represents the state of a node in a tree.
@@ -78,22 +110,8 @@
             # 'collapsed' means the children are not showing.
             #
             utterances.append(_("collapsed"))
-        elif state.contains(pyatspi.STATE_SELECTED):
-            # Translators: this represents the state of a node in a tree
-            # or list.
-            #
-            # ONLY TRANSLATE THE PART AFTER THE PIPE CHARACTER |
-            utterances.append(Q_("listitem|selected"))
-            obj.was_selected = True
-        elif not state.count(pyatspi.STATE_SELECTED) and \
-                 state.count(pyatspi.STATE_SELECTABLE):
-            # Translators: this represents the state of a node in a tree
-            # or list.
-            #
-            # ONLY TRANSLATE THE PART AFTER THE PIPE CHARACTER |
-            utterances.append(Q_("listitem|unselected"))
 
-        self._debugGenerator("_getSpeechForLabel",
+        self._debugGenerator("J2SE-access-bridge:_getSpeechForLabel",
                              obj,
                              already_focused,
                              utterances)
@@ -101,33 +119,22 @@
         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
-        not including the stopAncestor.
-
-        This method is identical to speechgeneratior.getSpeechContext
+        """This method is identical to speechgeneratior.getSpeechContext
         with one exception. The following test in
         speechgenerator.getSpeechContext:
 
-            if not text and parent.text:
+            if not text and 'Text' in pyatspi.listInterfaces(parent):
                 text = self._script.getDisplayedText(parent)
 
         has be replaced by
 
-            if not text:
-                text = self._script.getDisplayedText(parent)
+           if not text:
+               text = self._script.getDisplayedText(parent)
 
-        The Swing toolkit has components that do not implement the
+        The Swing toolkit has labelled panels that do not implement the
         AccessibleText interface, but getDisplayedText returns
         a meaningful string that needs to be used if getDisplayedLabel
         returns None.
-
-        Arguments:
-        - obj: the object
-        - stopAncestor: the anscestor to stop at and not include (None
-          means include all ancestors)
-
-        Returns a list of utterances to be spoken.
         """
 
         utterances = []
@@ -135,7 +142,7 @@
         if not obj:
             return utterances
 
-        if obj is stopAncestor:
+        if obj == stopAncestor:
             return utterances
 
         parent = obj.parent
@@ -149,12 +156,12 @@
                 break
             if not self._script.isLayoutOnly(parent):
                 text = self._script.getDisplayedLabel(parent)
-                if not text:  # changed from speechgenerator method
+                if not text and (parent.getRole() == pyatspi.ROLE_PANEL):
                     text = self._script.getDisplayedText(parent)
                 if text and len(text.strip()):
                     # Push announcement of cell to the end
                     #
-                    if not parent.getRole() in [pyatspi.ROLE_TABLE_CELL,
+                    if parent.getRole() not in [pyatspi.ROLE_TABLE_CELL,
                                                 pyatspi.ROLE_FILLER]:
                         utterances.append(\
                             rolenames.getSpeechForRoleName(parent))
@@ -168,6 +175,70 @@
 
         return utterances
 
+########################################################################
+#                                                                      #
+# Braille Generator                                                    #
+#                                                                      #
+########################################################################
+
+class BrailleGenerator(braillegenerator.BrailleGenerator):
+    def __init__(self, script):
+        braillegenerator.BrailleGenerator.__init__(self, script)
+
+    def _getBrailleRegionsForLabel(self, obj):
+        """Get the braille for a label.
+
+        Arguments:
+        - obj: the label
+
+        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("J2SE-access-bridge:_getBrailleRegionsForLabel",
+                             obj)
+
+        regions = []
+
+        text = self._script.getDisplayedText(obj)
+
+        # In Java, tree objects are labels, so we need to look at their
+        # states in order to tell whether they are expanded or collapsed.
+        #
+        state = obj.getState()
+        if state.contains(pyatspi.STATE_EXPANDABLE):
+            if state.contains(pyatspi.STATE_EXPANDED):
+                # Translators: this represents the state of a node in a tree.
+                # 'expanded' means the children are showing.
+                # 'collapsed' means the children are not showing.
+                #
+                text = self._script.appendString(text, _('expanded'))
+            else:
+                # Translators: this represents the state of a node in a tree.
+                # 'expanded' means the children are showing.
+                # 'collapsed' means the children are not showing.
+                #
+                text = self._script.appendString(text, _('collapsed'))
+
+        level = self._script.getNodeLevel(obj)
+        if level >= 0:
+            # Translators: this represents the depth of a node in a tree
+            # view (i.e., how many ancestors a node has).
+            #
+            text = self._script.appendString(text,
+                                             _("TREE LEVEL %d") % (level + 1))
+
+        region = braille.Component(obj, text)
+        regions.append(region)
+
+        return [regions, region]
+
+########################################################################
+#                                                                      #
+# The Java script class.                                               #
+#                                                                      #
+########################################################################
+
 class Script(default.Script):
 
     def __init__(self, app):
@@ -176,20 +247,20 @@
         Arguments:
         - app: the application to create a script for.
         """
-
-        # Set the debug level for all the methods in this script.
-        #
-        self.debugLevel = debug.LEVEL_FINEST
-
         default.Script.__init__(self, app)
 
-    def _debug(self, msg):
-        """ Convenience method for printing debug messages
+    def getWhereAmI(self):
+        """Returns the "where am I" class for this script.
         """
-        debug.println(self.debugLevel, "J2SE-access-bridge.py: "+msg)
+        return WhereAmI(self)
 
+    def getBrailleGenerator(self):
+        """Returns the braille generator for this script.
+        """
+        return BrailleGenerator(self)
+
     def getSpeechGenerator(self):
-        """Returns the speech generator for this script.
+        """Returns the braille generator for this script.
         """
         return SpeechGenerator(self)
 
@@ -218,6 +289,39 @@
         keyboardEvent.hw_code = keybindings.getKeycode(keysym)
         return default.Script.consumesKeyboardEvent(self, keyboardEvent)
 
+    def getNodeLevel(self, obj):
+        """Determines the node level of this object if it is in a tree
+        relation, with 0 being the top level node.  If this object is
+        not in a tree relation, then -1 will be returned.
+
+        Arguments:
+        -obj: the Accessible object
+        """
+
+        if not obj:
+            return -1
+
+        treeLikeThing = self.getAncestor(obj,
+                                         [pyatspi.ROLE_TREE,
+                                          pyatspi.ROLE_TABLE,
+                                          pyatspi.ROLE_TREE_TABLE],
+                                         None)
+        if not treeLikeThing:
+            return -1
+
+        count = 0
+        while True:
+            state = obj.getState()
+            if state.contains(pyatspi.STATE_EXPANDABLE) \
+               or state.contains(pyatspi.STATE_COLLAPSED):
+                if state.contains(pyatspi.STATE_VISIBLE):
+                    count += 1
+                obj = obj.parent
+            else:
+                break
+
+        return count - 1
+
     def onFocus(self, event):
         """Called whenever an object gets focus.
 
@@ -225,51 +329,108 @@
         - event: the Event
         """
 
-        self._debug("onFocus: %s '%s'" % \
-                    (event.source.getRole(), event.source.name))
-
         role = event.source.getRole()
-        if role == pyatspi.ROLE_LIST:
-            selectedItem = None
-            try:
-                selection = event.source.querySelection()
-            except:
-                selection = None
 
-            if selection and selection.nSelectedChildren > 0:
-                selectedItem = selection.getSelectedChild(0)
-            elif event.source.childCount > 0:
-                selectedItem = event.getChildAtIndex(0)
-            if selectedItem:
-                orca.setLocusOfFocus(event, selectedItem)
-            else:
-                # if impossible to get selection or list has 0 items,
-                # present list
-                orca.setLocusOfFocus(event, event.source)
-        elif role == pyatspi.ROLE_LABEL:
-            # In FileChooserDemo, when enter in a new folder, a focus event for
-            # the top combo box selected item (not SHOWING item) is received.
-            # Should this check be more specific ?
+        if role == pyatspi.ROLE_MENU:
+            # Override default.py's onFocus decision to ignore focus
+            # events on MENU items with selected children.  This is
+            # because JMenu's pop up without their children selected,
+            # but for some reason they always have
+            # selection.nSelectedChildren > 0.  I suspect this is a
+            # bug in JMenu.java:getAccessibleSelectionCount, but the
+            # details of Swing's MenuSelectionManager are foreign to
+            # me.  So, for now, we'll just be happy knowing that
+            # Java's menu items will give us focus events when they
+            # are selected.
             #
-            if not event.source.getState().contains(pyatspi.STATE_SHOWING):
-                return
-        elif role == pyatspi.ROLE_MENU:
-            # A JMenu has always selection.nSelectedChildren > 0
             orca.setLocusOfFocus(event, event.source)
-        else:
-            default.Script.onFocus(self, event)
+            return
 
+        default.Script.onFocus(self, event)
+
+    def onActiveDescendantChanged(self, event):
+        """Called when an object who manages its own descendants detects a
+        change in one of its children.
+
+        Arguments:
+        - event: the Event
+        """
+
+        # In Java comboboxes, when the list of options is popped up via
+        # an up or down action, control (but not focus) goes to a LIST
+        # object that manages the descendants.  So, we detect that here
+        # and keep focus on the combobox.
+        #
+        if event.source.getRole() == pyatspi.ROLE_COMBO_BOX:
+            orca.visualAppearanceChanged(event, event.source)
+            return
+
+        if event.source.getRole() == pyatspi.ROLE_LIST:
+            combobox = self.getAncestor(event.source,
+                                        [pyatspi.ROLE_COMBO_BOX],
+                                        [pyatspi.ROLE_PANEL])
+            if combobox:
+                orca.visualAppearanceChanged(event, combobox)
+                return
+
+        default.Script.onActiveDescendantChanged(self, event)
+
+    def onCaretMoved(self, event):
+        # Java's SpinButtons are the most caret movement happy thing
+        # I've seen to date.  If you Up or Down on the keyboard to
+        # change the value, they typically emit three caret movement
+        # events, first to the beginning, then to the end, and then
+        # back to the beginning.  It's a very excitable little widget.
+        # Luckily, it only issues one value changed event.  So, we'll
+        # ignore caret movement events caused by value changes and
+        # just process the single value changed event.
+        #
+        isSpinBox = self.isDesiredFocusedItem(event.source,
+                                              [pyatspi.ROLE_TEXT,
+                                               pyatspi.ROLE_PANEL,
+                                               pyatspi.ROLE_SPIN_BUTTON])
+        if isSpinBox:
+            if isinstance(orca_state.lastInputEvent,
+                          input_event.KeyboardEvent):
+                eventStr = orca_state.lastNonModifierKeyEvent.event_string
+            else:
+                eventStr = None
+            if (eventStr in ["Up", "Down"]) \
+               or isinstance(orca_state.lastInputEvent,
+                             input_event.MouseButtonEvent):
+                return
+
+        default.Script.onCaretMoved(self, event)
+
+    def onSelectionChanged(self, event):
+        """Called when an object's selection changes.
+
+        Arguments:
+        - event: the Event
+        """
+
+        # Ignore selection in TREE and TABLE objects since they send us
+        # an active descendant changed event.
+        #
+        if event.source.getRole() in [pyatspi.ROLE_TREE, pyatspi.ROLE_TABLE]:
+            return
+
+        default.Script.onSelectionChanged(self, event)
+
     def onStateChanged(self, event):
-        """Called whenever an object's state changes.  Currently, the
-        state changes for non-focused objects are ignored.
+        """Called whenever an object's state changes.
 
         Arguments:
         - event: the Event
         """
 
-        self._debug("onStateChanged: %s: %s '%s' (%d,%d)" % \
-                    (event.type, event.source.getRole(), 
-                     event.source.name, event.detail1, event.detail2))
+        # Handle state changes when JTree labels become expanded
+        # or collapsed.
+        #
+        if (event.source.getRole() == pyatspi.ROLE_LABEL) and \
+            event.type.startswith("object:state-changed:expanded"):
+            orca.visualAppearanceChanged(event, event.source)
+            return
 
         # This is a workaround for a java-access-bridge bug (Bug 355011)
         # where popup menu events are not sent to Orca.
@@ -285,32 +446,17 @@
             for child in event.source:
                 # search the layered pane for a popup menu
                 if child.getRole() == pyatspi.ROLE_LAYERED_PANE:
-                    popup = self.findByRole(child, 
+                    popup = self.findByRole(child,
                                             pyatspi.ROLE_POPUP_MENU, False)
                     if len(popup) > 0:
                         # set the locus of focus to the armed menu item
-                        items = self.findByRole(popup[0], 
+                        items = self.findByRole(popup[0],
                                                 pyatspi.ROLE_MENU_ITEM, False)
                         for item in items:
                             if item.getState().contains(pyatspi.STATE_ARMED):
                                 orca.setLocusOfFocus(event, item)
                                 return
 
-
-        # In java applications the events are comming in other order:
-        # "focus:" event comes before "state-changed:focused" event
-        #
-        if event.type.startswith("object:state-changed:focused"):
-            return
-
-        # Hand state changes when JTree labels become expanded
-        # or collapsed.
-        #
-        if (event.source.getRole() == pyatspi.ROLE_LABEL) and \
-            event.type.startswith("object:state-changed:expanded"):
-            orca.visualAppearanceChanged(event, event.source)
-            return
-
         # Present a value change in case of an focused popup menu.
         # Fix for Swing file chooser.
         #
@@ -322,197 +468,36 @@
 
         default.Script.onStateChanged(self, event)
 
-    def onSelectionChanged(self, event):
-        """Called when an object's selection changes.
-
-        Arguments:
-        - event: the Event
-        """
-
-        self._debug("onSelectionChanged: %s '%s'" % \
-                    (event.source.getRole(), event.source.name))
-
-        if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
-            return
-
-        if event.source.getRole() == pyatspi.ROLE_TABLE:
-            return
-
-        if event.source.getRole() == pyatspi.ROLE_TREE:
-            return
-
-        if event.source.getRole() == pyatspi.ROLE_LIST:
-            selection = event.source.querySelection()
-            if selection.nSelectedChildren <= 0:
-                if event.source.childCount > 0:
-                    child = event.source[0]
-                    if child.getState().contains(pyatspi.STATE_ACTIVE):
-                        return
-                else:
-                    orca.setLocusOfFocus(event, event.source)
-                    return
-            else:
-                selectedItem = selection.getSelectedChild(0)
-
-                # If the selected list item is the same with the last 
-                # focused object, present it only if the item was not 
-                # selected and has SELECTED state or if the item doesn't 
-                # have SELECTED state, but SELECTABLE and it was selected.
-                #
-                if orca_state.activeScript and \
-                   orca_state.activeScript.isSameObject(selectedItem,  \
-                                                    orca_state.locusOfFocus):
-                    state = orca_state.locusOfFocus.getState()
-                    if (state.contains(pyatspi.STATE_SELECTED) \
-                              and not orca_state.locusOfFocus.was_selected)   \
-                        or (not state.contains(pyatspi.STATE_SELECTED) \
-                          and state.contains(pyatspi.STATE_SELECTABLE) \
-                                and orca_state.locusOfFocus.was_selected):
-                        orca.visualAppearanceChanged(event, selectedItem)
-                    return
-
-        default.Script.onSelectionChanged(self, event)
-
     def onValueChanged(self, event):
-        """Called whenever an object's value changes.  Currently, the
-        value changes for non-focused objects are ignored.
+        """Called whenever an object's value changes.
 
         Arguments:
         - event: the Event
         """
 
-        self._debug("onValueChanged: %s '%s' value=%d" % \
-                    (event.source.getRole(), event.source.name,
-                     event.source.queryValue().currentValue))
-
-        # We'll let state-changed:checked event to be used to
-        # manage check boxes in java application
+        # We'll ignore value changed events for Java's toggle buttons since
+        # they also send a redundant object:state-changed:checked event.
         #
-        if event.source.getRole() == pyatspi.ROLE_CHECK_BOX and \
-               event.type.startswith("object:property-change:accessible-value"):
+        if event.source.getRole() == pyatspi.ROLE_TOGGLE_BUTTON:
             return
 
-        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
-        changes solely because of focus -- setLocusOfFocus is used for that.
-        Instead, it is intended mostly for objects whose notional 'value' has
-        changed, such as a checkbox changing state, a progress bar advancing,
-        a slider moving, text inserted, caret moved, etc.
-
-        Arguments:
-        - event: if not None, the Event that caused this to happen
-        - obj: the Accessible whose visual appearance changed.
-        """
-
-        self._debug("visualAppearanceChanged: %s '%s', locusOfFocus=%s" % \
-                    (obj.getRole(), obj.name, 
-                     orca_state.locusOfFocus.getRole()))
-
-        # Present to Braille and Speech an object that is the same
-        # with the last focused object, only if it is a label.
-        # This case is for LISTs which contain labels as items
-        # (see Open dialog lists).
+        # Java's SpinButtons are the most caret movement happy thing
+        # I've seen to date.  If you Up or Down on the keyboard to
+        # change the value, they typically emit three caret movement
+        # events, first to the beginning, then to the end, and then
+        # back to the beginning.  It's a very excitable little widget.
+        # Luckily, it only issues one value changed event.  So, we'll
+        # ignore caret movement events caused by value changes and
+        # just process the single value changed event.
         #
-        if orca_state.activeScript.isSameObject(obj, orca_state.locusOfFocus):
-            if obj.getRole() == pyatspi.ROLE_LABEL:
-                self.updateBraille(orca_state.locusOfFocus)
-                speech.speakUtterances( \
-                  self.speechGenerator.getSpeech(orca_state.locusOfFocus, True))
-                return
-
-        if obj.getRole() == 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 event.source.getRole() == pyatspi.ROLE_SPIN_BUTTON:
             try:
-                text = orca_state.locusOfFocus.queryText()
+                thisBox = orca_state.locusOfFocus.parent.parent == event.source
             except:
-                text = None
-            if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TEXT and \
-               text == None:
-                # Set the locusOfFocus to the spinbox itself.
-                orca.setLocusOfFocus(event, obj)
+                thisBox = False
+            if thisBox:
+                self._presentTextAtNewCaretPosition(event,
+                                                    orca_state.locusOfFocus)
+                return
 
-        default.Script.visualAppearanceChanged(self, event, obj)
-
-    def onCaretMoved(self, event):
-        """Called whenever the caret moves.
-
-        Arguments:
-        - event: the Event
-        """
-
-        self._debug("onCaretMoved: %s '%s'" % \
-                    (event.source.getRole(), event.source.name))
-
-        # Check for bogus events containing null text objects.
-        try:
-            text = event.source.queryText()
-        except:
-            text = None
-
-        if text == None:
-            self._debug("bogus event: text object is null. Discarding event")
-            return
-
-        default.Script.onCaretMoved(self, event)
-
-    def onTextInserted(self, event):
-        """Called whenever the text is inserted.
-
-        Arguments:
-        - event: the Event
-        """
-
-        self._debug("onTextInserted: %s '%s'" % \
-                    (event.source.getRole(), event.source.name))
-
-        # Check for bogus events containing null text objects.
-        try:
-            text = event.source.queryText()
-        except:
-            text = None
-        if text == None:
-            self._debug("bogus event: text object is null. Discarding event")
-            return
-
-        default.Script.onTextInserted(self, event)        
-
-    def getDisplayedLabel(self, obj):
-        """If there is an object labelling the given object, return the
-        text being displayed for the object labelling this object.
-        Otherwise, return None.
-
-        Argument:
-        - obj: the object in question
-
-        Returns the string of the object labelling this object, or None
-        if there is nothing of interest here.
-        """
-        label = default.Script.getDisplayedLabel(self, obj)
-        if not label and \
-           (obj.getRole() == pyatspi.ROLE_TEXT or \
-            obj.getRole() == pyatspi.ROLE_COMBO_BOX):
-            # This is a workaround for a common Java accessibility
-            # bug where a label next to a component is used to
-            # label the component. This relation is only obvious to
-            # a sighted person. The LABEL_FOR property is used to
-            # indicate the label refers to the component next to it.
-            # The workaround is to assume the label refers to the
-            # component if the label is the previous component and
-            # ends with a colon. We only do this for text and combo
-            # boxes since they are the most common Java component with
-            # this bug.
-            if obj.getIndexInParent() > 0:
-                prevSibling = obj.parent[obj.getIndexInParent()-1]
-                if prevSibling and prevSibling.name and \
-                   prevSibling.getRole() == pyatspi.ROLE_LABEL:
-                    labelFor = prevSibling.name.rstrip(" ")
-                    if (labelFor.endswith(":")):
-                        return labelFor.rstrip(":")
-
-        return label
+        default.Script.onValueChanged(self, event)
Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 4007)
+++ src/orca/default.py	(working copy)
@@ -2677,15 +2677,21 @@
             # We also keep track of tree level depth and only announce
             # that if it changes.
             #
+            # Note that Java Swing allows things like ROLE_LABEL objects
+            # in trees and tables, so we'll check the parent's role to 
+            # see if it is a table.
+            #
             oldNodeLevel = -1
             newNodeLevel = -1
-            if newLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
+            if (newLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
+               or (newParent.getRole() == pyatspi.ROLE_TABLE):
                 try:
                     table = oldParent.queryTable()
                 except:
                     table = None
                 if table and \
-                      oldLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
+                      ((oldLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                       or (oldParent.getRole() == pyatspi.ROLE_TABLE)):
                     index = self.getCellIndex(oldLocusOfFocus)
                     oldRow = table.getRowAtIndex(index)
                     oldCol = table.getColumnAtIndex(index)
@@ -2703,7 +2709,22 @@
                     newCol = table.getColumnAtIndex(index)
 
                     if (newRow != oldRow) or (oldParent != newParent):
+                        # Get the header information.  In Java Swing, the
+                        # information is not exposed via the description
+                        # but is instead a header object, so we fall back
+                        # to that if it exists.
+                        #
+                        # [[[TODO: WDW - the more correct thing to do, I 
+                        # think, is to look at the row header object.
+                        # We've been looking at the description for so 
+                        # long, though, that we'll give the description 
+                        # preference for now.]]]
+                        #
                         desc = table.getRowDescription(newRow)
+                        if not desc:
+                            header = table.getRowHeader(newRow)
+                            if header:
+                                desc = self.getDisplayedText(header)
                         if desc and len(desc):
                             text = desc
                             if settings.speechVerbosityLevel \
@@ -2717,7 +2738,22 @@
                         # it's not possible to navigate across a row.
                         topName = self.getTopLevelName(newLocusOfFocus)
                         if not topName.endswith(" - Thunderbird"):
+                            # Get the header information.  In Java Swing, the
+                            # information is not exposed via the description
+                            # but is instead a header object, so we fall back
+                            # to that if it exists.
+                            #
+                            # [[[TODO: WDW - the more correct thing to do, I 
+                            # think, is to look at the row header object.
+                            # We've been looking at the description for so 
+                            # long, though, that we'll give the description 
+                            # preference for now.]]]
+                            #
                             desc = table.getColumnDescription(newCol)
+                            if not desc:
+                                header = table.getColumnHeader(newCol)
+                                if header:
+                                    desc = self.getDisplayedText(header)
                             cellText = self.getDisplayedText(newLocusOfFocus)
                             if desc and len(desc) and cellText != desc:
                                 text = desc
@@ -2969,7 +3005,7 @@
                             self.speechGenerator.getSpeech(target, True))
                         return
 
-        if obj != orca_state.locusOfFocus:
+        if not self.isSameObject(obj, orca_state.locusOfFocus):
             return
 
         if event:
@@ -3202,8 +3238,11 @@
 
         return selSpoken
 
-    def _presentTextAtNewCaretPosition(self, event):
-        obj = event.source
+    def _presentTextAtNewCaretPosition(self, event, otherObj=None):
+        """Updates braille, magnification, and outputs speech for the 
+        event.source or the otherObj."""
+
+        obj = otherObj or event.source
         text = obj.queryText()
 
         if obj:
Index: src/orca/orca.py
===================================================================
--- src/orca/orca.py	(revision 4007)
+++ src/orca/orca.py	(working copy)
@@ -607,7 +607,9 @@
 
 def _setClickCount(inputEvent):
     """Sets the count of the number of clicks a user has made to one
-    of the non-modifier keys on the keyboard.
+    of the non-modifier keys on the keyboard.  Note that this looks at
+    the event_string (keysym) instead of hw_code (keycode) because
+    the Java platform gives us completely different keycodes for keys.
 
     Arguments:
     - inputEvent: the current input event.
@@ -621,7 +623,7 @@
         orca_state.clickCount = 0
     elif not isinstance(lastInputEvent, KeyboardEvent):
         orca_state.clickCount = 1
-    elif (lastInputEvent.hw_code != inputEvent.hw_code) or \
+    elif (lastInputEvent.event_string != inputEvent.event_string) or \
          (lastInputEvent.modifiers != inputEvent.modifiers):
         orca_state.clickCount = 1
     elif (inputEvent.time - lastInputEvent.time) < \
Index: src/orca/braillegenerator.py
===================================================================
--- src/orca/braillegenerator.py	(revision 4007)
+++ src/orca/braillegenerator.py	(working copy)
@@ -1719,7 +1719,22 @@
             if (row >= 0) \
                 and (not obj.getRole() in [pyatspi.ROLE_ROW_HEADER,
                                            pyatspi.ROLE_TABLE_ROW_HEADER]):
+                # Get the header information.  In Java Swing, the
+                # information is not exposed via the description
+                # but is instead a header object, so we fall back
+                # to that if it exists.
+                #
+                # [[[TODO: WDW - the more correct thing to do, I 
+                # think, is to look at the row header object.
+                # We've been looking at the description for so 
+                # long, though, that we'll give the description 
+                # preference for now.]]]
+                #
                 desc = table.getRowDescription(row)
+                if not desc:
+                    header = table.getRowHeader(row)
+                    if header:
+                        desc = self._script.getDisplayedText(header)
             else:
                 desc = None
             if desc and len(desc):
@@ -1740,7 +1755,22 @@
             if (col >= 0) \
                 and (not obj.getRole() in [pyatspi.ROLE_COLUMN_HEADER,
                                            pyatspi.ROLE_TABLE_COLUMN_HEADER]):
+                # Get the header information.  In Java Swing, the
+                # information is not exposed via the description
+                # but is instead a header object, so we fall back
+                # to that if it exists.
+                #
+                # [[[TODO: WDW - the more correct thing to do, I 
+                # think, is to look at the row header object.
+                # We've been looking at the description for so 
+                # long, though, that we'll give the description 
+                # preference for now.]]]
+                #
                 desc = table.getColumnDescription(col)
+                if not desc:
+                    header = table.getColumnHeader(col)
+                    if header:
+                        desc = self._script.getDisplayedText(header)
             else:
                 desc = None
             if desc and len(desc):
