Index: src/orca/settings.py
===================================================================
--- src/orca/settings.py	(revision 4098)
+++ src/orca/settings.py	(working copy)
@@ -107,6 +107,7 @@
     "enableFunctionKeys",
     "enableActionKeys",
     "enableNavigationKeys",
+    "enableTutorialMessages",
     "enableBraille",
     "enableBrailleGrouping",
     "disableBrailleEOL",
@@ -562,6 +563,9 @@
 #
 enableNavigationKeys    = False
 
+# If True, roles that have tutorial strings defined will be spoken.
+enableTutorialMessages = False
+
 # If True, show the main Orca window.
 #
 showMainWindow          = True
Index: src/orca/orca-setup.glade
===================================================================
--- src/orca/orca-setup.glade	(revision 4098)
+++ src/orca/orca-setup.glade	(working copy)
@@ -1387,6 +1387,26 @@
 		      </child>
 
 		      <child>
+			<widget class="GtkCheckButton" id="speakTutorialMessagesCheckButton">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">Speak tutorial messages</property>
+			  <property name="use_underline">True</property>
+			  <property name="relief">GTK_RELIEF_NORMAL</property>
+			  <property name="focus_on_click">True</property>
+			  <property name="active">True</property>
+			  <property name="inconsistent">False</property>
+			  <property name="draw_indicator">True</property>
+			  <signal name="toggled" handler="speakTutorialMessagesToggled" last_modification_time="Mon, 04 Aug 2008 17:11:04 GMT"/>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+
+		      <child>
 			<widget class="GtkHBox" id="hbox39">
 			  <property name="visible">True</property>
 			  <property name="homogeneous">False</property>
Index: src/orca/tutorialgenerator.py
===================================================================
--- src/orca/tutorialgenerator.py	(revision 0)
+++ src/orca/tutorialgenerator.py	(revision 0)
@@ -0,0 +1,692 @@
+# Orca
+#
+# Copyright 2005-2008 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA  02110-1301 USA.
+
+"""Utilities for obtaining tutorial utterances for objects.  In general,
+there probably should be a singleton instance of the TutorialGenerator
+class.  For those wishing to override the generators, however,
+one can create a new instance and replace/extend the tutorial generators
+as they see fit."""
+
+__id__        = "$Id:$"
+__version__   = "$Revision:$"
+__date__      = "$Date:$"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import pyatspi
+import debug
+import settings
+
+from orca_i18n import _         # for gettext support
+
+class TutorialGenerator:
+    """Takes accessible objects and produces a tutorial string to speak 
+    for those objects.  See the getTutorialString method, which is the 
+    primary entry point.  Subclasses can feel free to override/extend 
+    the getTutorialGenerators instance field as they see fit."""
+
+    def __init__(self, script):
+
+        # The script that created us.  This allows us to ask the
+        # script for information if we need it.
+        #
+        self._script = script
+        # storing the last spoken message.
+        self.lastTutorial = ""
+
+        # Set up a dictionary that maps role names to functions
+        # that generate tutorial strings for objects that implement that role.
+        #
+        self.tutorialGenerators = {}
+        self.tutorialGenerators[pyatspi.ROLE_CHECK_BOX] = \
+            self._getTutorialForCheckBox
+        self.tutorialGenerators[pyatspi.ROLE_COMBO_BOX] = \
+            self._getTutorialForComboBox
+        self.tutorialGenerators[pyatspi.ROLE_FRAME] = \
+            self._getTutorialForFrame
+        self.tutorialGenerators[pyatspi.ROLE_LIST] = \
+            self._getTutorialForList
+        self.tutorialGenerators[pyatspi.ROLE_LIST_ITEM] = \
+            self._getTutorialForListItem
+        self.tutorialGenerators[pyatspi.ROLE_PAGE_TAB] = \
+            self._getTutorialForPageTab
+        self.tutorialGenerators[pyatspi.ROLE_PARAGRAPH] = \
+            self._getTutorialForText
+        self.tutorialGenerators[pyatspi.ROLE_PASSWORD_TEXT] = \
+            self._getTutorialForText
+        self.tutorialGenerators[pyatspi.ROLE_ENTRY] = \
+            self._getTutorialForText
+        self.tutorialGenerators[pyatspi.ROLE_PUSH_BUTTON] = \
+            self._getTutorialForPushButton
+        self.tutorialGenerators[pyatspi.ROLE_SPIN_BUTTON] = \
+            self._getTutorialForSpinButton
+        self.tutorialGenerators[pyatspi.ROLE_TABLE_CELL] = \
+            self._getTutorialForTableCellRow
+        self.tutorialGenerators[pyatspi.ROLE_TEXT] = \
+            self._getTutorialForText
+        self.tutorialGenerators[pyatspi.ROLE_TOGGLE_BUTTON] = \
+            self._getTutorialForCheckBox
+        self.tutorialGenerators[pyatspi.ROLE_RADIO_BUTTON] = \
+            self._getTutorialForRadioButton
+        self.tutorialGenerators[pyatspi.ROLE_MENU]                = \
+            self._getTutorialForMenu
+        self.tutorialGenerators[pyatspi.ROLE_CHECK_MENU_ITEM]     = \
+            self._getTutorialForCheckBox
+        self.tutorialGenerators[pyatspi.ROLE_MENU_ITEM]           = \
+            self._getTutorialForPushButton
+        self.tutorialGenerators[pyatspi.ROLE_RADIO_MENU_ITEM]     = \
+            self._getTutorialForCheckBox        
+        self.tutorialGenerators[pyatspi.ROLE_SLIDER]              = \
+            self._getTutorialForSlider
+
+    def _debugGenerator(self, generatorName, obj, already_focused, utterances):
+        """Prints debug.LEVEL_FINER information regarding 
+        the tutorial generator.
+
+        Arguments:
+        - generatorName: the name of the generator
+        - obj: the object being presented
+        - already_focused: False if object just received focus
+        - utterances: the generated text
+        """
+
+        debug.println(debug.LEVEL_FINER,
+                      "GENERATOR: %s" % generatorName)
+        debug.println(debug.LEVEL_FINER,
+                      "           obj             = %s" % obj.name)
+        debug.println(debug.LEVEL_FINER,
+                      "           role            = %s" % obj.getRoleName())
+        debug.println(debug.LEVEL_FINER,
+                      "           already_focused = %s" % already_focused)
+        debug.println(debug.LEVEL_FINER,
+                      "           utterances:")
+        for text in utterances:
+            debug.println(debug.LEVEL_FINER,
+                    "               (%s)" % text)
+
+    def _getDefaultTutorial(self, obj, already_focused, forceMessage, \
+      role=None):
+        """The default tutorial generator returns the empty tutorial string
+        because We have no associated tutorial function for the object.
+        
+        Arguments:
+        - obj: an Accessible
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+        - role: A role that should be used instead of the Accessible's 
+          possible role.
+
+        Returns the empty list []
+        """
+
+        return []
+
+
+    def _getTutorialForCheckBox(self, obj, already_focused, forceMessage):
+        """Get the  tutorial string for a check box.
+
+        Arguments:
+        - obj: the check box
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is a tip for the user on how to toggle a checkbox.
+        msg = _("Press space to toggle.")
+
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForCheckBox",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForComboBox(self, obj, already_focused, forceMessage):
+        """Get the  tutorial string for a combobox.
+
+        Arguments:
+        - obj: the combo box
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is a tip for the user on how to interact 
+        # with a combobox.
+        msg = _("Press space to expand, and use up and down to select an item.")
+
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForComboBox",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForFrame(self, obj, already_focused, forceMessage):
+        """Get the  tutorial string for a frame.
+
+        Arguments:
+        - obj: the frame
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+        name = self._script.getDisplayedText(obj)
+        if not name and obj.description:
+            name = obj.description
+
+        # Translators: this is the tutorial string for when first landing 
+        # on the desktop, giving tips on how to navigate.
+        # Also describing how to access the menues.
+        desktopMsg = _("To move to items, use either " \
+                     "the arrow keys, or type ahead searching. " \
+                     "To get to the system menues press the alt+f1 key.")
+        
+        # Translators: If this application has more than one unfocused alert or
+        # dialog window, inform user of how to refocus these.
+        childWindowsMsg = _("Press alt+f6 to give focus to child windows.")
+        
+        if name == 'Desktop':
+            utterances.append(desktopMsg)
+
+        # If this application has more than one unfocused alert or
+        # dialog window, tell user how to give them focus.
+        alertAndDialogCount = \
+                    self._script.getUnfocusedAlertAndDialogCount(obj)
+        if alertAndDialogCount > 0:
+            utterances.append(childWindowsMsg)
+
+        self._debugGenerator("_getTutorialForFrame",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForList(self, obj, already_focused, forceMessage):
+        """Get the  tutorial string for a list.
+
+        Arguments:
+        - obj: the list
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+        
+        # Translators: this is the tutorial string when navigating lists.
+        msg = _("Use up and down to select an item.")
+        
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForList",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+    
+    def _getTutorialForListItem(self, obj, already_focused, forceMessage):
+        """Get the  tutorial string for a listItem.
+
+        Arguments:
+        - obj: the listitem
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+
+        # Translators: this represents the state of a node in a tree.
+	# 'expanded' means the children are showing.
+        # 'collapsed' means the children are not showing.
+        # this string informs the user how to collapse the node.
+        expandedMsg = _("To collapse, press shift plus left.")
+        
+        # Translators: this represents the state of a node in a tree.
+        # 'expanded' means the children are showing.
+        # 'collapsed' means the children are not showing.
+        # this string informs the user how to expand the node.
+        collapsedMsg = _("To expand, press shift plus right.")
+        
+        
+        # If already in focus then the tree probably collapsed or expanded
+        state = obj.getState()
+        if state.contains(pyatspi.STATE_EXPANDABLE):
+            if state.contains(pyatspi.STATE_EXPANDED):
+                if (self.lastTutorial != [expandedMsg]) or forceMessage:
+                    utterances.append(expandedMsg)
+            else:
+                if (self.lastTutorial != [collapsedMsg]) or forceMessage:
+                    utterances.append(collapsedMsg)
+                
+        self._debugGenerator("_getTutorialForListItem",
+                             obj,
+                             already_focused,
+                             utterances)
+        return utterances
+
+    def _getTutorialForText(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a text object.
+
+        Arguments:
+        - obj: the text component
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+        utterances = []
+        # Translators: This is the tutorial string for when landing 
+        # on text fields.
+        msg = _("Type in text.")
+
+        if (not already_focused or forceMessage) and \
+           not self._script.isReadOnlyTextArea(obj):
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForText",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForPageTab(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a page tab.
+
+        Arguments:
+        - obj: the page tab
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of tutorial utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is the tutorial string for landing 
+        # on a page tab, we are informing the 
+        # user how to navigate these.
+        msg = _("Use left and right to view other tabs.")
+        
+        if (self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForPageTabList",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForPushButton(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a push button
+
+        Arguments:
+        - obj: the push button
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is the tutorial string for activating a push button.
+        msg = _("To activate press space.")
+        
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForPushButton",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForSpinButton(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a spin button.  If the object already has
+        focus, then no tutorial is given.
+
+        Arguments:
+        - obj: the spin button
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is the tutorial string for when landing 
+        # on a spin button.
+        msg = _("Use up or down arrow to select value." \
+              " Or type in the desired numerical value.")
+
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForSpinButton",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForTableCell(self, obj, already_focused, forceMessage):
+        """Get the tutorial utterances for a single table cell
+
+        Arguments:
+        - obj: the table
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+
+        # Translators: this represents the state of a node in a tree.
+	# 'expanded' means the children are showing.
+        # 'collapsed' means the children are not showing.
+        # this string informs the user how to collapse the node.
+        expandedMsg = _("To collapse, press shift plus left.")
+        
+        # Translators: this represents the state of a node in a tree.
+        # 'expanded' means the children are showing.
+        # 'collapsed' means the children are not showing.
+        # this string informs the user how to expand the node.
+        collapsedMsg = _("To expand, press shift plus right.")
+
+        # If this table cell has 2 children and one of them has a 
+        # 'toggle' action and the other does not, then present this 
+        # as a checkbox where:
+        # 1) we get the checked state from the cell with the 'toggle' action
+        # 2) we get the label from the other cell.
+        # See Orca bug #376015 for more details.
+        #
+        if obj.childCount == 2:
+            cellOrder = []
+            hasToggle = [ False, False ]
+            for i, child in enumerate(obj):
+                try:
+                    action = child.queryAction()
+                except NotImplementedError:
+                    continue
+                else:
+                    for j in range(0, action.nActions):
+                        # Translators: this is the action name for
+                        # the 'toggle' action. It must be the same
+                        # string used in the *.po file for gail.
+                        #
+                        if action.getName(j) in ["toggle", _("toggle")]:
+                            hasToggle[i] = True
+                            break
+
+            if hasToggle[0] and not hasToggle[1]:
+                cellOrder = [ 1, 0 ] 
+            elif not hasToggle[0] and hasToggle[1]:
+                cellOrder = [ 0, 1 ]
+            if cellOrder:
+                for i in cellOrder:
+                    # Don't speak the label if just the checkbox state has
+                    # changed.
+                    #
+                    if already_focused and not hasToggle[i]:
+                        pass
+                    else:
+                        utterances.extend( \
+                            self._getTutorialForTableCell(obj[i],
+                            already_focused, forceMessage))
+                return utterances
+
+        # [[[TODO: WDW - Attempt to infer the cell type.  There's a
+        # bunch of stuff we can do here, such as check the EXPANDABLE
+        # state, check the NODE_CHILD_OF relation, etc.  Logged as
+        # bugzilla bug 319750.]]]
+        #
+        try:
+            action = obj.queryAction()
+        except NotImplementedError:
+            action = None
+        if action:
+            for i in range(0, action.nActions):
+                debug.println(debug.LEVEL_FINEST,
+                    "tutorialgenerator._getTutorialForTableCell" \
+                    + "looking at action %d" % i)
+
+                # Translators: this is the action name for
+                # the 'toggle' action. It must be the same
+                # string used in the *.po file for gail.
+                #
+                if action.getName(i) in ["toggle", _("toggle")]:
+                    utterances = self._getTutorialForCheckBox(obj,
+                                  already_focused, forceMessage)
+                    break
+
+        state = obj.getState()
+        if state.contains(pyatspi.STATE_EXPANDABLE):
+            if state.contains(pyatspi.STATE_EXPANDED):
+                if self.lastTutorial != [expandedMsg] or forceMessage:
+                    utterances.append(expandedMsg)
+            else:
+                if self.lastTutorial != [collapsedMsg] or forceMessage:
+                    utterances.append(collapsedMsg)
+
+        self._debugGenerator("_getTutorialForTableCell",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForTableCellRow(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for the active table cell in the table row.
+
+        Arguments:
+        - obj: the table
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+
+        if (not already_focused):
+            try:
+                parent_table = obj.parent.queryTable()
+            except NotImplementedError:
+                parent_table = None
+            if settings.readTableCellRow and parent_table \
+                and (not self._script.isLayoutOnly(obj.parent)):
+                parent = obj.parent
+                index = self._script.getCellIndex(obj)
+                row = parent_table.getRowAtIndex(index)
+                column = parent_table.getColumnAtIndex(index)
+
+                # This is an indication of whether we should speak all the
+                # table cells (the user has moved focus up or down a row),
+                # or just the current one (focus has moved left or right in
+                # the same row).
+                #
+                speakAll = True
+                if "lastRow" in self._script.pointOfReference and \
+                    "lastColumn" in self._script.pointOfReference:
+                    pointOfReference = self._script.pointOfReference
+                    speakAll = (pointOfReference["lastRow"] != row) or \
+                        ((row == 0 or row == parent_table.nRows-1) and \
+                           pointOfReference["lastColumn"] == column)
+
+                utterances.extend(self._getTutorialForTableCell(obj,
+                                        already_focused, forceMessage))
+            else:
+                utterances = self._getTutorialForTableCell(obj, 
+                  already_focused, forceMessage)
+        else:
+            utterances = self._getTutorialForTableCell(obj, already_focused, \
+              forceMessage)
+
+        self._debugGenerator("_getTutorialForTableCellRow",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def _getTutorialForRadioButton(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a radio button.
+
+        Arguments:
+        - obj: the radio button
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is a tip for the user, how to navigate radiobuttons.
+        msg = _("Use arrow keys to change.")
+
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForRadioButton",
+                             obj,
+                             already_focused,
+                             utterances)
+        return utterances
+
+    def _getTutorialForMenu(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a menu.
+
+        Arguments:
+        - obj: the menu
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is a tip for the user, how to navigate menues.
+        mainMenuMsg = _("To navigate, press left or right arrow. " \
+                       "To move through items press up or down arrow.")
+
+        # Translators: this is a tip for the user, how to 
+        # navigate into sub menues.
+        subMenuMsg = _("To enter sub menu, press right arrow.")        
+        
+        # Checking if we are a submenu,
+        # we can't rely on our parent being just a menu.
+        if obj.parent.name != "" and obj.parent.__class__ == obj.__class__:
+            if (self.lastTutorial != [subMenuMsg]) or forceMessage:
+                utterances.append(subMenuMsg)
+        else:
+            if (self.lastTutorial != [mainMenuMsg]) or forceMessage:
+                utterances.append(mainMenuMsg)
+
+        self._debugGenerator("_getTutorialForMenu",
+                             obj,
+                             already_focused,
+                             utterances)
+        return utterances
+
+    def _getTutorialForSlider(self, obj, already_focused, forceMessage):
+        """Get the tutorial string for a slider.  If the object already has
+        focus, then no tutorial is given.
+
+        Arguments:
+        - obj: the slider
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken for the object.
+        """
+
+        utterances = []
+        # Translators: this is the tutorial string for when landing 
+        # on a slider.
+        msg = _("To decrease press left arrow, to increase press right arrow." \
+          " To go to minimum press home, and for maximum press end.")
+              
+        if (not already_focused and self.lastTutorial != [msg]) or forceMessage:
+            utterances.append(msg)
+
+        self._debugGenerator("_getTutorialForSlider",
+                             obj,
+                             already_focused,
+                             utterances)
+
+        return utterances
+
+    def getTutorial(self, obj, already_focused, forceMessage = False):
+        """Get the tutorial for an Accessible object.  This will look
+        first to the specific tutorial generators and if this 
+        does not exist then return the empty tutorial.
+        This method is the primary method
+        that external callers of this class should use.
+
+        Arguments:
+        - obj: the object
+        - already_focused: False if object just received focus
+        - forceMessage: used for when whereAmI really needs the tutorial string.
+
+        Returns a list of utterances to be spoken.
+        """
+
+        if not settings.enableTutorialMessages:
+            return []
+        
+        role = obj.getRole()
+        if role in self.tutorialGenerators:
+            generator = self.tutorialGenerators[role]
+        else:
+            generator = self._getDefaultTutorial
+        msg = generator(obj, already_focused, forceMessage)
+        utterances = [" ".join(msg)]
+        if msg:
+            self.lastTutorial = msg
+        if forceMessage:
+            self.lastTutorial = ""
+
+        self._debugGenerator("getTutorial",
+                             obj,
+                             already_focused,
+                             utterances)
+        return utterances
Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 4098)
+++ src/orca/default.py	(working copy)
@@ -2028,9 +2028,11 @@
             try:
                 orca_state.locusOfFocus.queryText()
             except NotImplementedError:
-                speech.speakUtterances(
-                    self.speechGenerator.getSpeech(orca_state.locusOfFocus,
-                                                   False))
+                utterances = self.speechGenerator.getSpeech(
+                             orca_state.locusOfFocus, False)
+                utterances.extend(self.tutorialGenerator.getTutorial(
+                           orca_state.locusOfFocus, False))
+                speech.speakUtterances(utterances)
             except AttributeError:
                 pass
             else:
@@ -2935,7 +2937,8 @@
             #
             utterances.extend(
                 self.speechGenerator.getSpeech(newLocusOfFocus, False))
-
+            utterances.extend(
+                self.tutorialGenerator.getTutorial(newLocusOfFocus, False))
             # Now speak the new tree node level if it has changed.
             #
             if (oldNodeLevel != newNodeLevel) \
@@ -3105,8 +3108,10 @@
                 target = relation.getTarget(0)
                 if target == orca_state.locusOfFocus:
                     self.updateBraille(target)
-                    speech.speakUtterances(
-                        self.speechGenerator.getSpeech(target, True))
+                    utterances = self.speechGenerator.getSpeech(target, True)
+                    utterances.extend(self.tutorialGenerator.getTutorial(
+                               target, True))
+                    speech.speakUtterances(utterances)
                     return
 
         # If this object is a label, and if it has a LABEL_FOR relation
@@ -3121,8 +3126,11 @@
                     target = relation.getTarget(0)
                     if target == orca_state.locusOfFocus:
                         self.updateBraille(target)
-                        speech.speakUtterances(
-                            self.speechGenerator.getSpeech(target, True))
+                        utterances = self.speechGenerator.getSpeech(
+                                     target, True)
+                        utterances.extend(self.tutorialGenerator.getTutorial(
+                                          target, True))
+                        speech.speakUtterances(utterances)
                         return
 
         if not self.isSameObject(obj, orca_state.locusOfFocus):
@@ -3139,7 +3147,9 @@
 
         mag.magnifyAccessible(event, obj)
         self.updateBraille(obj)
-        speech.speakUtterances(self.speechGenerator.getSpeech(obj, True))
+        utterances = self.speechGenerator.getSpeech(obj, True)
+        utterances.extend(self.tutorialGenerator.getTutorial(obj, True))
+        speech.speakUtterances(utterances)
 
     def updateBraille(self, obj, extraRegion=None):
         """Updates the braille display to show the give object.
@@ -3914,10 +3924,11 @@
                     and (orca_state.lastNonModifierKeyEvent.event_string \
                                                                   == "F1"):
                     self.updateBraille(orca_state.locusOfFocus)
-                    speech.speakUtterances(self.speechGenerator.getSpeech(
-                        orca_state.locusOfFocus,
-                        False))
-
+                    utterances = self.speechGenerator.getSpeech(orca_state.locusOfFocus,
+                        False)
+                    utterances.extend(self.tutorialGenerator.getTutorial(
+                                      orca_state.locusOfFocus, False))
+                    speech.speakUtterances(utterances)
             return
 
         if event.source.getRole() in state_change_notifiers:
@@ -4990,10 +5001,11 @@
         # the Braille display as an input device.
         #
         if not isinstance(inputEvent, input_event.BrailleEvent):
-            speech.speakUtterances(
-                self.speechGenerator.getSpeech(
+            utterances = self.speechGenerator.getSpeech(
+                    context.getCurrentAccessible(), False)
+            utterances.extend(self.tutorialGenerator.getTutorial(
                     context.getCurrentAccessible(), False))
-
+            speech.speakUtterances(utterances)
         return True
 
     def reviewPreviousItem(self, inputEvent):
Index: src/orca/where_am_I.py
===================================================================
--- src/orca/where_am_I.py	(revision 4098)
+++ src/orca/where_am_I.py	(working copy)
@@ -168,6 +168,7 @@
         2. role
         3. state
         4. accelerator (i.e. Alt plus the underlined letter), if any
+        5. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -183,6 +184,9 @@
         if text:
             utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "check box utterances=%s" \
                       % utterances)
         speech.speakUtterances(utterances)
@@ -197,6 +201,7 @@
         4. state
         5. relative position
         6. accelerator (i.e. Alt plus the underlined letter), if any
+        7. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -235,6 +240,9 @@
         text = self._getObjAccelerator(obj)
         utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "radio button utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -248,6 +256,7 @@
         3. current value
         4. relative position
         5. accelerator (i.e. Alt plus the underlined letter), if any
+        6. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -267,6 +276,9 @@
         accelerator = self._getObjAccelerator(obj)
         utterances.append(accelerator)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "combo box utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -280,6 +292,7 @@
         3. current value
         4. selected (if True).
         5. accelerator (i.e. Alt plus the underlined letter), if any
+        6. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -302,6 +315,9 @@
         if text:
             utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "spin button utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -313,6 +329,7 @@
         1. label
         2. role
         3. accelerator (i.e. Alt plus the underlined letter), if any
+        4. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -325,6 +342,9 @@
         text = self._getObjAccelerator(obj)
         utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "push button utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -338,6 +358,7 @@
         3. value
         4. percentage (if possible)
         5. accelerator (i.e. Alt plus the underlined letter), if any
+        6. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -360,6 +381,9 @@
         if text:
             utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "slider utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -374,6 +398,7 @@
         accelerator key binding, if any
         3. relative position
         4. mnemonic (i.e. the underlined letter), if any
+        5. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -431,6 +456,9 @@
             text = self._getObjMnemonic(obj)
             utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "menu item utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -443,6 +471,7 @@
         2. label + 'page'
         3. relative position
         4. accelerator (i.e. Alt plus the underlined letter), if any
+        5. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -461,6 +490,9 @@
         text = self._getObjAccelerator(obj)
         utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "page utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -478,6 +510,7 @@
             by 'selected' (single press)
             C. if the current line is blank/empty, 'blank'
         4. accelerator (i.e. Alt plus the underlined letter), if any
+        5. tutorial string if enableTutorialMessages is set.
 
         Gaim, gedit, OpenOffice Writer and Terminal
         """
@@ -539,6 +572,9 @@
         text = self._getObjAccelerator(obj)
         utterances.append(text)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "text utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -557,6 +593,7 @@
            performing a detailed whereAmI.
         8. if expandable/collapsible: expanded/collapsed
         9. if applicable, the level
+        10. tutorial string if enableTutorialMessages is set.
 
         Nautilus and Gaim
         """
@@ -650,6 +687,9 @@
             #
             utterances.append(_("tree level %d") % (level + 1))
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "third table cell utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -663,6 +703,7 @@
         4. relative position
         5. if expandable/collapsible: expanded/collapsed
         6. if applicable, the level
+        7. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -711,6 +752,9 @@
             #
             utterances.append(_("tree level %d") % (level + 1))
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         debug.println(self._debugLevel, "list item utterances=%s" % \
                       utterances)
         speech.speakUtterances(utterances)
@@ -722,8 +766,10 @@
 
     def _speakIconPanel(self, obj, basicOnly):
         """Speak the contents of the pane containing this icon. The
-        number of icons in the pane is spoken. Plus the total number of
-        selected icons. Plus the name of each of the selected icons.
+        1. Number of icons in the pane is spoken.
+        2. The total number of selected icons.
+        3. The name of each of the selected icons.
+        4. tutorial string if enableTutorialMessages is set.
 
         Arguments:
         - obj: the icon object that currently has focus.
@@ -737,12 +783,20 @@
         utterances.append(self.getObjLabelAndName(obj))
         utterances.extend(self._getSelectedItemCount(panel, basicOnly))
 
+        # get our tutorial.
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+        # get the frames tutorial.
+        [frame, dialogue] = self._getFrameAndDialog(obj)
+        utterances.extend(getTutorial(frame, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
         
     def _speakLink(self, obj, basicOnly):
         """Speaks information about a link including protocol, domain 
-        comparisons and size of file if possible
-        
+        comparisons and size of file if possible.
+        Also tutorial string if enableTutorialMessages is set.
+
         Arguments:
         - obj: the icon object that currently has focus.
         - basicOnly: True if the user is performing a standard/basic whereAmI.
@@ -804,13 +858,18 @@
                 else:
                     domainoutput = _('different site')
 
-        speech.speakUtterances([linkoutput, domainoutput, sizeoutput])
+        utterances = [linkoutput, domainoutput, sizeoutput]
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
 
+        speech.speakUtterances(utterances)
+
     def _speakToggleButton(self, obj, basicOnly):
         """Speak toggle button information:
            1. Name/Label
            2. Role
            3. State (pressed/not pressed)
+           4. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -830,6 +889,9 @@
             checkedState = _("not pressed")
         utterances.append(checkedState)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
       
     def _speakSplitPane(self, obj, basicOnly):
@@ -837,6 +899,7 @@
            1. Name/Label
            2. Role
            3. Value
+           4. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -849,6 +912,9 @@
         valueString = self._script.getTextForValue(obj)
         utterances.append(valueString)
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
 
     def _speakLabel(self, obj, basicOnly):
@@ -856,6 +922,7 @@
            1. Name/Label
            2. selected (if True).
            3. Role
+           4. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -866,13 +933,19 @@
 
         text = rolenames.getSpeechForRoleName(obj)
         utterances.append(text)
+
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
 
+
     def _speakLayeredPane(self, obj, basicOnly):
         """Speak layered pane information:
            1. Name/Label
            2. Role
            3. Number of selected items and total number of items.
+           4. tutorial string if enableTutorialMessages is set.
         """
 
         utterances = []
@@ -883,8 +956,12 @@
         utterances.append(text)
         utterances.extend(self._getSelectedItemCount(obj, basicOnly))
 
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
 
+
     def _getSpeechForAllTextSelection(self, obj):
         """Check if this object has text associated with it and it's
         completely selected.
@@ -1004,8 +1081,13 @@
 
         text = rolenames.getSpeechForRoleName(obj)
         utterances.append(text)
+
+        getTutorial = self._script.tutorialGenerator.getTutorial
+        utterances.extend(getTutorial(obj, False, forceMessage=True))
+
         speech.speakUtterances(utterances)
 
+
     def _getObjName(self, obj):
         """Returns the name to speak for an object.
         """
@@ -1208,6 +1290,7 @@
     def _getTableRow(self, obj):
         """Get the speech for a table cell row or a single table cell
         if settings.readTableCellRow is False.
+        Also return a tutorial string if enableTutorialMessages is set.
 
         Arguments:
         - obj: the table
@@ -1233,6 +1316,8 @@
 
             debug.println(self._debugLevel, "row=<%s>" % utterances)
 
+            getTutorial = self._script.tutorialGenerator.getTutorial
+            utterances.extend(getTutorial(obj, False, forceMessage=True))
             return utterances
 
     def _getTableCell(self, obj):
@@ -1262,7 +1347,6 @@
             text = self._script.getDisplayedText(descendant)
 
         debug.println(self._debugLevel, "cell=<%s>" % text)
-
         return text
 
     def _getCheckBoxState(self, obj):
Index: src/orca/script.py
===================================================================
--- src/orca/script.py	(revision 4098)
+++ src/orca/script.py	(working copy)
@@ -49,6 +49,7 @@
 import structural_navigation
 import where_am_I
 import bookmarks
+import tutorialgenerator
 
 class Script:
     """The specific focus tracking scripts for applications.
@@ -90,6 +91,7 @@
         self.whereAmI = self.getWhereAmI()
         self.bookmarks = self.getBookmarks()
         self.voices = settings.voices
+        self.tutorialGenerator = self.getTutorialGenerator()
 
         self.flatReviewContextClass = flat_review.Context
 
@@ -186,6 +188,11 @@
         """
         return speechgenerator.SpeechGenerator(self)
 
+    def getTutorialGenerator(self):
+        """Returns the tutorial generator for this script.
+        """
+        return tutorialgenerator.TutorialGenerator(self)
+
     def getEnabledStructuralNavigationTypes(self):
         """Returns a list of the structural navigation object types
         enabled in this script.
Index: src/orca/Makefile.am
===================================================================
--- src/orca/Makefile.am	(revision 4098)
+++ src/orca/Makefile.am	(working copy)
@@ -58,6 +58,7 @@
 	speechserver.py \
 	structural_navigation.py \
 	text_attribute_names.py \
+	tutorialgenerator.py \
 	where_am_I.py
 
 orca_pythondir=$(pyexecdir)/orca
Index: src/orca/orca_gui_prefs.py
===================================================================
--- src/orca/orca_gui_prefs.py	(revision 4098)
+++ src/orca/orca_gui_prefs.py	(working copy)
@@ -1461,6 +1461,8 @@
 
         self.get_widget("speakBlankLinesCheckButton").set_active(\
             prefs["speakBlankLines"])
+        self.get_widget("speakTutorialMessagesCheckButton").set_active(\
+            prefs["enableTutorialMessages"])
 
         self.get_widget("sayAllStyle").set_active(prefs["sayAllStyle"])
 
@@ -2442,6 +2444,17 @@
 
         self.prefsDict["speakBlankLines"] = widget.get_active()
 
+    def speakTutorialMessagesToggled(self, widget):
+        """Signal handler for the "toggled" signal for the
+           speakTutorialMessagesCheckButton GtkCheckButton widget.
+           Set the 'enableTutorialMessages' preference to the new value.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.prefsDict["enableTutorialMessages"] = widget.get_active()
+
     def brailleSupportChecked(self, widget):
         """Signal handler for the "toggled" signal for the
            brailleSupportCheckbutton GtkCheckButton widget. The user has
Index: po/POTFILES.in
===================================================================
--- po/POTFILES.in	(revision 4098)
+++ po/POTFILES.in	(working copy)
@@ -69,4 +69,5 @@
 src/orca/speech.py
 src/orca/structural_navigation.py
 src/orca/text_attribute_names.py
+src/orca/tutorialgenerator.py
 src/orca/where_am_I.py
