Index: src/orca/liveregions.py
===================================================================
--- src/orca/liveregions.py	(revision 3426)
+++ src/orca/liveregions.py	(working copy)
@@ -7,14 +7,14 @@
 from orca_i18n import _
 
 # define 'live' property types
-LIVE_OFF       = -1
-LIVE_NONE      = 0
+LIVE_OFF       = 1
+LIVE_NONE      = 1
 LIVE_POLITE    = 1
 LIVE_ASSERTIVE = 2
 LIVE_RUDE      = 3
 
 # Seconds a message is held in the queue before it is discarded
-MSG_KEEPALIVE_TIME = 5  # in seconds
+MSG_KEEPALIVE_TIME = 45  # in seconds
 
 # The number of messages that are cached and can later be reviewed via 
 # LiveRegionManager.reviewLiveAnnouncement.
@@ -30,10 +30,10 @@
     def __init__(self):
         self.queue = []
 
-    def enqueue(self, utts, priority, obj):
+    def enqueue(self, data, priority, obj):
         """ Add a new element to the queue according to 1) priority and
         2) timestamp. """
-        bisect.insort_left(self.queue, (priority, time.time(), utts, obj))
+        bisect.insort_left(self.queue, (priority, time.time(), data, obj))
        
     def dequeue(self):
         """get the highest priority element from the queue.  """
@@ -56,6 +56,47 @@
         myfilter = lambda item: item[0] > priority
         self.queue = filter(myfilter, self.queue)
 
+    def clumpContents(self):
+        """ Combines messages with the same 'label' by appending newer  
+        'content' and removing the newer message.  This operation is only
+        applied to the next dequeued message for performance reasons and is
+        often applied in conjunction with filterContents() """
+        if len(self.queue):
+            newqueue = []
+            newqueue.append(self.queue[0])
+            targetlabels = newqueue[0][2]['labels']
+            targetcontent = newqueue[0][2]['content']
+
+            for i in range(1, len(self.queue)):
+                if self.queue[i][2]['labels'] == targetlabels:
+                    targetcontent.extend(self.queue[i][2]['content'])
+                else:
+                    newqueue.append(self.queue[i]) 
+
+            self.queue = newqueue
+
+    def filterContents(self):
+        """ Combines utterances by eliminating repeated utterances and
+        utterances that are part of other utterances. """
+        if len(self.queue[0][2]['content']) > 1:
+            oldcontent = self.queue[0][2]['content']
+            newcontent = [oldcontent[0]]
+
+            for i in range(1, len(oldcontent)):
+                found = False
+                for j in range(len(newcontent)):
+                    if oldcontent[i].find(newcontent[j]) != -1 \
+                        or newcontent[j].find(oldcontent[i]) != -1: 
+                        if len(oldcontent[i]) > len(newcontent[j]):
+                            newcontent[j] = oldcontent[i]
+                        found = True
+                        break
+
+                if not found:
+                    newcontent.append(oldcontent[i])
+
+            self.queue[0][2]['content'] = newcontent
+ 
     def __len__(self):
         """ Return the length of the queue """
         return len(self.queue)
@@ -105,51 +146,62 @@
         or {}
 
     def handleEvent(self, event):
-
-        livetype = self._getLiveType(event.source)
-        # Purge our queue of messages based on priority of incoming event and
-        # age of queued message.
-        if livetype == LIVE_NONE or livetype == LIVE_OFF:
-            # 1) we won't do anything for bad markup right now.
-            # 2) we won't waste processing power if live type is lower
-            #    than user define.
+        if not self.monitoring:
             return
-
-        # Add a callback if the queue is empty because we are about to add 
-        # something to it.
-        if len(self.msg_queue) == 0:
-            gobject.idle_add(self.pumpMessages)
-
-        if livetype == LIVE_POLITE:
+        
+        politeness = self._getLiveType(event.source)
+        if politeness == LIVE_NONE or politeness == LIVE_OFF:
             # Nothing to do for now
             pass
-        elif livetype ==  LIVE_ASSERTIVE:
+        elif politeness == LIVE_POLITE:
+            # Nothing to do for now
+            pass
+        elif politeness ==  LIVE_ASSERTIVE:
             self.msg_queue.purgeByPriority(LIVE_POLITE)
-        elif livetype == LIVE_RUDE:
+        elif politeness == LIVE_RUDE:
             self.msg_queue.purgeByPriority(LIVE_ASSERTIVE)
 
-        utterance = self._getUtterances(event)
-        if utterance:
-            self.msg_queue.enqueue(utterance, livetype, event.source)
+        message = self._getMessage(event)
+        if message:
+            self.msg_queue.enqueue(message, politeness, event.source)
+            gobject.idle_add(self.pumpMessages)
 
     def pumpMessages(self):
         """ Main gobject callback for live region support.  Handles both 
         purging the message queue and outputting any queued messages that
         were queued up in the handleEvent() method.
         """
-        # House cleaning on the message queue.  No purging is
-        # performed in handleEvent().
-        self.msg_queue.purgeByKeepAlive()
+        # If there are messages in the queue, we are monitoring, and we are not
+        # currently speaking then speak queued message.
+        # Note: Do all additional work within if statement to prevent
+        # it from being done for each event loop callback
+        if len(self.msg_queue) > 0 \
+                               and not speech.isSpeaking() \
+                               and self.monitoring:
+            # House cleaning on the message queue.  
+            # First we will purge the queue of old messages
+            self.msg_queue.purgeByKeepAlive()
+            # Next, we will filter the messages
+            self.msg_queue.clumpContents()
+            self.msg_queue.filterContents()
+            # Let's get our queued information
+            politeness, timestamp, message, obj = self.msg_queue.dequeue()
+            # Form output message
+            if message['labels'].count(message['content']) or \
+                 message['content'].count(message['labels']):
+                utts = message['content']
+            else:
+                utts = message['labels'] + message['content']
 
-        # If there are messages in the queue and we are not currently 
-        # speaking then speak queued message.
-        if len(self.msg_queue) > 0 and not speech.isSpeaking():
-            politeness, timestamp, utts, obj = self.msg_queue.dequeue()
             speech.speakUtterances(utts)
             # set the last live obj to be announced
             self.lastliveobj = obj
             # cache our message
             self._cacheMessage(utts)
+            
+        # We still want to maintain our queue if we are not monitoring
+        if not self.monitoring:
+            self.msg_queue.purgeByKeepAlive()
 
         # See you again soon, stay in event loop if we still have messages.
         if len(self.msg_queue) > 0:
@@ -229,7 +281,7 @@
 
     def monitorLiveRegions(self):
         # start at the document frame
-        obj = self._script.getDocumentFrame()
+        docframe = self._script.getDocumentFrame()
         # get the URI of the page.  It is used as a partial key.
         uri = self._script.bookmarks.getURIKey()
 
@@ -239,7 +291,7 @@
             # look through all the objects on the page and set/add to
             # politeness overrides.
             # TODO: use Collection
-            matches = pyatspi.findAllDescendants(obj, self._livePred)
+            matches = pyatspi.findAllDescendants(docframe, self._livePred)
             for match in matches:
                 attrs = self._getAttrDictionary(match)
                 self._politenessOverrides[(uri, attrs['id'])] = LIVE_OFF
@@ -260,7 +312,7 @@
 
             # look through all the objects on the page.
             # TODO: use Collection
-            matches = pyatspi.findAllDescendants(obj, self._livePred)
+            matches = pyatspi.findAllDescendants(docframe, self._livePred)
             for match in matches:
                 attrs = self._getAttrDictionary(match)
                 # Restore the bookmarked politeness or the markup politeness
@@ -269,7 +321,7 @@
                                 oldpoliteness[(uri, attrs['id'])]
                 except KeyError:
                     self._politenessOverrides[(uri, attrs['id'])] = \
-                            self._liveStringToType(obj, attributes=attrs)
+                            self._liveStringToType(docframe, attributes=attrs)
 
             # Translators: This lets the user know that all live regions
             # have been restored to their original politeness level.
@@ -313,77 +365,95 @@
         attrs = self._getAttrDictionary(obj)
         return (attrs.has_key('container-live') and attrs.has_key('id'))
 
-    def _getUtterances(self, event):
-
+    def _getMessage(self, event):
         attrs = self._getAttrDictionary(event.source)
-        # TODO: handle relevant property here
-
-        # Get the message content for the event.  First the relations.
-        utterances = []
-
+        content = [] 
+        labels = []
         if event.type.startswith('object:children-changed:add'):
             # Get a handle to the Text interface for the target.
             try:
                 targetitext = event.any_data.queryText()
             except NotImplementedError:
-                return [] 
+                return None
 
             # Get the text based on the atomic property
             try:
                 if attrs['container-atomic'] == 'true':
-                    utterances.append(self._script.expandEOCs(event.source))
+                    content.append(self._script.expandEOCs(event.source))
                 else:
-                    utterances.append(targetitext.getText(0, -1))
+                    content.append(targetitext.getText(0, -1))
             except (KeyError, TypeError):
-                utterances.append(targetitext.getText(0, -1))
+                content.append(targetitext.getText(0, -1))
 
         else: #object:text-changed:insert
             # Get a handle to the Text interface for the source.
             # Serious problems if this fails.
             #
+            
             try:
                 sourceitext = event.source.queryText()
             except NotImplementedError:
-                # TODO: output warning
                 return None
 
             # We found an embed character.  We can expect a children-changed
             # event, which we will act on, so just return.
-            content = sourceitext.getText(0, -1)
-            if content.find(self._script.EMBEDDED_OBJECT_CHARACTER) == 0:
+            txt = sourceitext.getText(0, -1)
+            if txt.find(self._script.EMBEDDED_OBJECT_CHARACTER) == 0:
                 return None
 
             # Get labeling information
-            utterances.extend(self._getLabelsAsUtterances(event.source))
+            labels = self._getLabelsAsUtterances(event.source)
 
             # Get the text based on the atomic property
             try:
                 if attrs['container-atomic'] == 'true':
-                    utterances.append(content)
+                    content.append(txt)
                 else:
-                    utterances.append(content[event.detail1:]) 
+                    content.append(txt[event.detail1:]) 
             except KeyError:
-                utterances.append(content[event.detail1:])
- 
-        return utterances 
+                content.append(txt)
 
+        return {'content':content, 'labels':labels} 
+
     def _cacheMessage(self, utts):
         self.msg_cache.append(utts)
         if len(self.msg_cache) > CACHE_SIZE:
             self.msg_cache.pop(0)
 
     def _getLabelsAsUtterances(self, obj):
-        utterances = []
-        for relation in obj.getRelationSet():
-            relationtype = relation.getRelationType()
-            if relationtype == pyatspi.RELATION_LABELLED_BY:
-                labelobj = relation.getTarget(0)
-                try:
-                    utterances.append(labelobj.queryText().getText(0, -1))
-                except NotImplemented:
-                    pass
-        return utterances
+        # try the Gecko label getter first
+        uttstring = self._script.getDisplayedLabel(obj)
+        if uttstring:
+            return [uttstring.strip()]
+        # often we see a table cell.  I'll implement my own label getter
+        elif obj.getRole() == pyatspi.ROLE_TABLE_CELL \
+                           and obj.parent.childCount > 1:
+            # We will try the table interface first for it's parent
+            try:
+                itable = obj.parent.queryTable()
+                # I'm in a table, now what row are we in?  Look in the first 
+                # columm of that row.
+                #
+                # Note: getRowHeader() fails for most markup.  We will use the
+                # relation when the markup is good (when getRowHeader() works) 
+                # so we won't see this code in those cases.  
+                row = itable.getRowAtIndex(obj.getIndexInParent())
+                header = itable.getAccessibleAt(row, 0)
+                # expand the header
+                return [self._script.expandEOCs(header).strip()]
+            except NotImplementedError:
+                pass
 
+            # Last ditch effort is to see if our parent is a table row <tr> 
+            # element.
+            parentattrs = self._getAttrDictionary(obj.parent) 
+            if parentattrs.has_key('tag') and parentattrs['tag'] == 'TR':
+                return [self._script.expandEOCs( \
+                                  obj.parent.getChildAtIndex(0)).strip()]
+
+        # Sorry, no valid labels found
+        return []
+
     def _getLiveType(self, obj):
         attrs = self._getAttrDictionary(obj)
         uri = self._script.bookmarks.getURIKey()
Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 3426)
+++ src/orca/Gecko.py	(working copy)
@@ -3762,7 +3762,10 @@
         except:
             pass
 
-        if liveRegionsOn and self.isLiveRegion(event.source):
+        if not self._loadingDocumentContent \
+           and liveRegionsOn \
+           and event.type == 'object:text-changed:insert:system' \
+           and self.inDocumentContent(event.source):
             self.liveMngr.handleEvent(event)
             return
 
@@ -3774,17 +3777,23 @@
         such example would be the programmatic insertion of a tooltip or alert  
         dialog."""
 
-        # We will just look at object addition events for now
-        if event.type.startswith('object:children-changed:add:system'):
-            # no use moving forward if we don't have our target.
-            if event.any_data is None:
-                return
+        # no need moving forward if we don't have our target.
+        if event.any_data is None:
+            return
 
-            if liveRegionsOn and self.isLiveRegion(event.source):
-                self.liveMngr.handleEvent(event)
-                return
+        # looks like a live region event
+        if not self._loadingDocumentContent \
+           and liveRegionsOn \
+           and event.type.startswith('object:children-changed:add:system'):
+           self.liveMngr.handleEvent(event)
+           return
 
-            newacc = event.any_data
+        # Some alerts come as a 'object:children-changed:add' event so we will
+        # check for them specifically.  Hopefully, any alert that came as a 
+        # 'object:children-changed:add:system' will be handled by the live 
+        # region handler.
+        if not self._loadingDocumentContent \
+           and event.type.startswith('object:children-changed:add'):
             output = None
             # The addition could be an alert/tooltip, but the xml-role:alert
             # or xml-role:tooltip object may be a child.  For performance
@@ -3793,10 +3802,10 @@
             # check xml-roles to make sure it is not a list or other 
             # roles with potentially many children.
             #
-            if self.isAriaAlert(newacc):
-                output = self.expandEOCs(newacc)
-            elif newacc.getRole() != pyatspi.ROLE_LIST:
-                for child in newacc:
+            if self.isAriaAlert(event.any_data):
+                output = self.expandEOCs(event.any_data)
+            elif event.any_data.getRole() != pyatspi.ROLE_LIST:
+                for child in event.any_data:
                     if self.isAriaAlert(child):
                         output = self.expandEOCs(child)
                         break
@@ -3804,6 +3813,7 @@
             if output:
                 speech.speak(output)
                 braille.displayMessage(output)
+                return
 
     def onDocumentReload(self, event):
         """Called when the reload button is hit for a web page."""
