Index: test/keystrokes/oowriter/bug_450210.py
===================================================================
--- test/keystrokes/oowriter/bug_450210.py	(revision 4344)
+++ test/keystrokes/oowriter/bug_450210.py	(working copy)
@@ -33,14 +33,14 @@
                            30000))
 sequence.append(utils.AssertPresentationAction(
     "Press 'o' to open the Open File Chooser",
-    ["BUG? - We are presenting both a combo box and a list for what amounts to the same object.",
+    ["KNOWN ISSUE - When this test is manually performed, we include the combo box in the context",
      "BRAILLE LINE:  'soffice Application Open Dialog'",
      "     VISIBLE:  'soffice Application Open Dialog', cursor=21",
-     "BRAILLE LINE:  'soffice Application Open Dialog Open OptionPane File name: List'",
-     "     VISIBLE:  'File name: List', cursor=1",
+     "BRAILLE LINE:  'soffice Application Open Dialog Open OptionPane File name:  $l'",
+     "     VISIBLE:  'File name:  $l', cursor=12",
      "SPEECH OUTPUT: 'Open'",
-     "SPEECH OUTPUT: 'File name: combo box'",
-     "SPEECH OUTPUT: 'File name: list'"]))
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'File name: text '"]))
 
 ######################################################################
 # 4. Press Escape to dismiss the Open File Chooser.
Index: test/keystrokes/oowriter/bug_546941.py
===================================================================
--- test/keystrokes/oowriter/bug_546941.py	(revision 4344)
+++ test/keystrokes/oowriter/bug_546941.py	(working copy)
@@ -60,16 +60,10 @@
 sequence.append(WaitForFocus("", acc_role=pyatspi.ROLE_LIST))
 sequence.append(utils.AssertPresentationAction(
     "Move to Strikethrough",
-    ["BUG? - 1. We're presenting both a combo box and a list for what amounts to the same object.",
-     "BUG? - 2. There should be a space between 'Combo' and 'Strikethrough' -- assuming we should be presenting the combo box in the first place.",
-     "BRAILLE LINE:  'soffice Application Character Dialog Character OptionPane TabList Font Effects Page Strikethrough  Combo'",
-     "     VISIBLE:  ' Combo', cursor=1",
-     "BRAILLE LINE:  'soffice Application Character Dialog Character OptionPane TabList Font Effects Page  ComboStrikethrough  Strikethrough List (Without) ListItem'",
-     "     VISIBLE:  '(Without) ListItem', cursor=1",
+    ["BRAILLE LINE:  'soffice Application Character Dialog Character OptionPane TabList Font Effects Page Strikethrough (Without) Combo'",
+     "     VISIBLE:  '(Without) Combo', cursor=1",
      "SPEECH OUTPUT: ''",
-     "SPEECH OUTPUT: 'Strikethrough Select a strikethrough style for the selected text. combo box'",
-     "SPEECH OUTPUT: 'Strikethrough list'",
-     "SPEECH OUTPUT: '(Without)'"]))
+     "SPEECH OUTPUT: 'Strikethrough (Without) combo box'"]))
 
 ######################################################################
 # 8. Press Escape to exit the dialog.
Index: src/orca/scripts/apps/soffice/script.py
===================================================================
--- src/orca/scripts/apps/soffice/script.py	(revision 4344)
+++ src/orca/scripts/apps/soffice/script.py	(working copy)
@@ -1541,6 +1541,30 @@
                 speech.speak(self.getText(child, 0, -1), None, False)
             return
 
+        # Combo boxes in OOo typically have two children: a text object
+        # and a list. The combo box will often intially claim to have
+        # focus, but not always. The list inside of it, however, will
+        # claim focus quite regularly. In addition, the list will do
+        # this even if the text object is editable and functionally has
+        # focus, such as the File Name combo box in the Save As dialog.
+        # We need to minimize chattiness and maximize useful information.
+        #
+        if newLocusOfFocus and oldLocusOfFocus \
+           and newLocusOfFocus.getRole() == pyatspi.ROLE_LIST \
+           and newLocusOfFocus.parent.getRole() == pyatspi.ROLE_COMBO_BOX \
+           and not self.isSameObject(newLocusOfFocus.parent,
+                                     oldLocusOfFocus.parent):
+
+            # If the combo box contents cannot be edited, just present the
+            # combo box. Otherwise, present the text object. The combo
+            # box will be included as part of the speech context.
+            #
+            state = newLocusOfFocus.parent[0].getState()
+            if not state.contains(pyatspi.STATE_EDITABLE):
+                newLocusOfFocus = newLocusOfFocus.parent
+            else:
+                newLocusOfFocus = newLocusOfFocus.parent[0]
+
         # Pass the event onto the parent class to be handled in the default way.
 
         default.Script.locusOfFocusChanged(self, event,
@@ -1664,6 +1688,8 @@
         - event: the Event
         """
 
+        handleEvent = False
+        presentEvent = True
         if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
             # Sometimes the items in the OOo Task Pane give up focus (e.g.
             # to a context menu) and never reclaim it. In this case, we
@@ -1680,17 +1706,39 @@
                          pyatspi.ROLE_LIST_ITEM]
             if self.isDesiredFocusedItem(event.source, rolesList) \
                and event.any_data:
+                handleEvent = True
+
+        elif self.isSameObject(orca_state.locusOfFocus, event.source.parent) \
+             and event.source.getRole() == pyatspi.ROLE_LIST \
+             and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_COMBO_BOX:
+            # Combo boxes which have been explicitly given focus by the user
+            # (as opposed to those which have been automatically given focus
+            # in a dialog or alert) issue an object:state-changed:focused
+            # event, then an object:active-descendant-changed event for the
+            # list inside the combo box, and finally a focus: event for the
+            # list itself. This leads to unnecessary chattiness. As these
+            # objects look and act like combo boxes, we'll let the first of
+            # the events cause the object to be presented. Quietly setting
+            # the locusOfFocus to the activeDescendant here will prevent
+            # this event's chattiness. The final focus: event for the list
+            # is already being handled by onFocus as part of bug 546941.
+            #
+            handleEvent = True
+            presentEvent = False
+
+        if handleEvent:
+            if presentEvent:
                 speech.stop()
-                orca.setLocusOfFocus(event, event.any_data)
+            orca.setLocusOfFocus(event, event.any_data, presentEvent)
 
-                # We'll tuck away the activeDescendant information for future
-                # reference since the AT-SPI gives us little help in finding
-                # this.
-                #
-                self.pointOfReference['activeDescendantInfo'] = \
-                    [orca_state.locusOfFocus.parent,
-                     orca_state.locusOfFocus.getIndexInParent()]
-                return
+            # We'll tuck away the activeDescendant information for future
+            # reference since the AT-SPI gives us little help in finding
+            # this.
+            #
+            self.pointOfReference['activeDescendantInfo'] = \
+                [orca_state.locusOfFocus.parent,
+                 orca_state.locusOfFocus.getIndexInParent()]
+            return
 
         default.Script.onActiveDescendantChanged(self, event)
 
@@ -1787,6 +1835,16 @@
                     self.updateBraille(event.source)
                 return
 
+            # If we get "object:state-changed:focused" events for children of
+            # a combo-box, just set the focus to the combo box. This is needed
+            # to help reduce the verbosity of focusing on the Calc Name combo
+            # box (see bug #364407).
+            #
+            elif event.source.parent and \
+                event.source.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
+                orca.setLocusOfFocus(None, event.source.parent, False)
+                return
+
         # If we are in the sbase Table Wizard, try to reduce the numerous
         # utterances of "Available fields panel". See bug #465087 for
         # more details.
@@ -1794,16 +1852,6 @@
         if self.__isAvailableFieldsPanel(event):
             return
 
-        # If we get "object:state-changed:focused" events for children of
-        # a combo-box, just set the focus to the combo box. This is needed
-        # to help reduce the verbosity of focusing on the Calc Name combo
-        # box (see bug #364407).
-        #
-        if event.source.parent and \
-           event.source.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
-            orca.setLocusOfFocus(None, event.source.parent, False)
-            return
-
         default.Script.onStateChanged(self, event)
 
     def onSelectionChanged(self, event):
@@ -2115,6 +2163,41 @@
         else:
             return default.Script.getDisplayedText(self, obj)
 
+    def getTextLineAtCaret(self, obj, offset=None):
+        """Gets the line of text where the caret is. Overridden here to
+        handle combo boxes who have a text object with a caret offset
+        of -1 as a child.
+
+        Argument:
+        - obj: an Accessible object that implements the AccessibleText
+          interface
+        - offset: an optional caret offset to use. (Not used here at the
+          moment, but needed in the Gecko script)
+
+        Returns the [string, caretOffset, startOffset] for the line of text
+        where the caret is.
+        """
+
+        if obj.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
+            try:
+                text = obj.queryText()
+            except NotImplementedError:
+                return ["", 0, 0]
+
+            if text.caretOffset < 0:
+                [lineString, startOffset, endOffset] = text.getTextAtOffset(
+                    0, pyatspi.TEXT_BOUNDARY_LINE_START)
+
+                # Sometimes we get the trailing line-feed -- remove it
+                #
+                content = lineString.decode("UTF-8")
+                if content[-1:] == "\n":
+                    content = content[:-1]
+
+                return [content.encode("UTF-8"), 0, startOffset]
+
+        return default.Script.getTextLineAtCaret(self, obj, offset)
+
     def isFunctionalDialog(self, obj):
         """Returns true if the window is functioning as a dialog."""
 
