Index: src/orca/Makefile.am
===================================================================
RCS file: /cvs/gnome/orca/src/orca/Makefile.am,v
retrieving revision 1.40
diff -u -r1.40 Makefile.am
--- src/orca/Makefile.am	27 Nov 2006 20:50:36 -0000	1.40
+++ src/orca/Makefile.am	30 Nov 2006 00:21:13 -0000
@@ -19,6 +19,7 @@
 	default.py \
 	espeechfactory.py \
 	eventsynthesizer.py \
+	find.py \
 	flat_review.py \
 	focus_tracking_presenter.py \
 	Gecko.py \
@@ -33,6 +34,7 @@
 	orca.py \
 	orca_console_prefs.py \
 	orca_glade.py \
+	orca_gui_find.py \
 	orca_gui_main.py \
 	orca_gui_prefs.py \
 	orca_i18n.py \
@@ -59,6 +61,7 @@
 SUBDIRS = scripts
 
 glade_DATA = \
+	orca-find.glade \
 	orca-mainwin.glade \
 	orca-quit.glade \
 	orca-setup.glade
Index: src/orca/default.py
===================================================================
RCS file: /cvs/gnome/orca/src/orca/default.py,v
retrieving revision 1.249
diff -u -r1.249 default.py
--- src/orca/default.py	28 Nov 2006 01:35:26 -0000	1.249
+++ src/orca/default.py	30 Nov 2006 00:21:16 -0000
@@ -34,6 +34,7 @@
 import braille
 import chnames
 import debug
+import find
 import flat_review
 import input_event
 import keybindings
@@ -101,6 +102,21 @@
                 Script.whereAmI,
                 _("Performs the where am I operation."))
 
+        self.inputEventHandlers["findHandler"] = \
+            input_event.InputEventHandler(
+                orca._showFindGUI,
+                _("Opens the Orca Find dialog."))
+
+        self.inputEventHandlers["findNextHandler"] = \
+            input_event.InputEventHandler(
+                Script.findNext,
+                _("Searches for the next instance of a string."))
+
+        self.inputEventHandlers["findPreviousHandler"] = \
+            input_event.InputEventHandler(
+                Script.findPrevious,
+                _("Searches for the previous instance of a string."))
+
         self.inputEventHandlers["showZonesHandler"] = \
             input_event.InputEventHandler(
                 Script.showZones,
@@ -409,12 +425,29 @@
                 0,
                 self.inputEventHandlers["whereAmIHandler"]))
 
-        # keyBindings.add(
-        #     keybindings.KeyBinding(
-        #         "KP_Delete",
-        #         0,
-        #         0,
-        #         self.inputEventHandlers["screenFindHandler"]))
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "KP_Delete",
+                1 << settings.MODIFIER_ORCA,
+                0,
+                self.inputEventHandlers["findHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "KP_Delete",
+                (1 << settings.MODIFIER_ORCA
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT),
+                1 << settings.MODIFIER_ORCA,
+                self.inputEventHandlers["findNextHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "KP_Delete",
+                (1 << settings.MODIFIER_ORCA
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT),
+                (1 << settings.MODIFIER_ORCA | \
+                     1 << atspi.Accessibility.MODIFIER_SHIFT),
+                self.inputEventHandlers["findPreviousHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
@@ -1442,6 +1475,15 @@
         - newLocusOfFocus: Accessible that is the new locus of focus
         """
 
+        try:
+            if self.findCommandRun:
+                # Then the Orca Find dialog has just given up focus
+                # to the original window.  We don't want to speak
+                # the window title, current line, etc.
+                return
+        except:
+            pass
+
         if newLocusOfFocus:
             mag.magnifyAccessible(event, newLocusOfFocus)
 
@@ -2238,6 +2280,16 @@
 
         # Do we care?
         #
+        if event.type == "object:state-changed:active":
+            try:
+                if self.findCommandRun:
+                    self.findCommandRun = False
+                    self.find()
+                    return
+            except:
+                debug.println(debug.LEVEL_FINEST,
+                              "onStateChanged: searchQuery not initialized")
+
         if event.type == "object:state-changed:focused":
             iconified = False
             try:
@@ -3186,7 +3238,6 @@
 
         flatReviewContext = self.getFlatReviewContext()
         lines = flatReviewContext.lines
-        print "Number of lines:", len(lines)
         for line in lines:
             string = ""
             for zone in line.zones:
@@ -3195,6 +3246,60 @@
                                  False)
             debug.println(debug.LEVEL_OFF, string)
         self.flatReviewContext = None
+
+    def find(self, query=None):
+        """Searches for the specified query.  If no query is specified,
+        it searches for the query specified in the Orca Find dialog.
+
+        Arguments:
+        - query: The search query to find.
+        """
+
+        if not query:
+            query = find.getLastQuery()
+        if query:
+            context = self.getFlatReviewContext()
+            location = query.findQuery(context)
+            if not location:
+                braille.displayMessage(_("string not found"))
+                speech.speak(_("string not found"))
+            else:
+                context.setCurrent(location.lineIndex, location.zoneIndex, \
+                                   location.wordIndex, location.charIndex)
+                self.reviewCurrentItem(None)
+                self.targetCursorCell = braille.cursorCell
+
+    def findNext(self, inputEvent):
+        """Searches forward for the next instance of the string
+        searched for via the Orca Find dialog.  Other than direction
+        and the starting point, the search options initially specified
+        (case sensitivity, window wrap, and full/partial match) are
+        preserved.
+        """
+
+        lastQuery = find.getLastQuery()
+        if lastQuery:
+            lastQuery.searchBackwards = False
+            lastQuery.startAtTop = False
+            self.find(lastQuery)
+        else:
+            orca._showFindGUI()
+
+    def findPrevious(self, inputEvent):
+        """Searches backwards for the next instance of the string
+        searched for via the Orca Find dialog.  Other than direction
+        and the starting point, the search options initially specified
+        (case sensitivity, window wrap, and full/partial match) are
+        preserved.
+        """
+
+        lastQuery = find.getLastQuery()
+        if lastQuery:
+            lastQuery.searchBackwards = True
+            lastQuery.startAtTop = False
+            self.find(lastQuery)
+        else:
+            orca._showFindGUI()
 
 # Dictionary that defines the state changes we care about for various
 # objects.  The key represents the role and the value represents a list
Index: src/orca/find.py
===================================================================
RCS file: src/orca/find.py
diff -N src/orca/find.py
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/orca/find.py	30 Nov 2006 00:21:16 -0000
@@ -0,0 +1,291 @@
+# Orca
+#
+# Copyright 2006 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., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Provides support for a flat review find."""
+
+__id__        = "$Id:$"
+__version__   = "$Revision:$"
+__date__      = "$Date:$"
+__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+
+import copy
+import re
+
+import braille
+import debug
+import flat_review
+import orca_state
+import speech
+
+from orca_i18n import _    # for gettext support
+
+class SearchQuery:
+    """Represents a search that the user wants to perform."""
+
+    def __init__(self):
+        """Creates a new SearchQuery. A searchQuery has the following
+           properties:
+
+           searchString     - the string to find
+           searchBackwards  - if true, search upward for matches
+           caseSensitive    - if true, case counts
+           matchEntireWord  - if true, only match on the entire string
+           startAtTop       - if true, begin the search from the top of
+                              the window, rather than at the current
+                              location
+           windowWrap       - if true, when the top/bottom edge of the
+                              window is reached wrap to the bottom/top
+                              and continue searching
+        """
+
+        self.searchString = ""
+        self.searchBackwards = False
+        self.caseSensitive = False
+        self.matchEntireWord = False
+        self.windowWrap = False
+        self.startAtTop = False
+
+        self.debugLevel = debug.LEVEL_FINEST
+
+    def debugContext(self, context, string):
+        debug.println(self.debugLevel, \
+            "------------------------------------------------------------")
+        debug.println(self.debugLevel, \
+                      "findQuery: %s line=%d zone=%d word=%d char=%d" \
+                      % (string, context.lineIndex, context.zoneIndex, \
+                                 context.wordIndex, context.charIndex))
+
+        debug.println(self.debugLevel, \
+                      "Number of lines: %d" % len(context.lines))
+        debug.println(self.debugLevel, \
+                      "Number of zones in current line: %d" % \
+                      len(context.lines[context.lineIndex].zones))
+        debug.println(self.debugLevel, \
+                      "Number of words in current zone: %d" % \
+           len(context.lines[context.lineIndex].zones[context.zoneIndex].words))
+
+        debug.println(self.debugLevel, \
+            "==========================================================\n\n")
+
+    def dumpContext(self, context):
+        print "DUMP"
+        for i in range(0, len(context.lines)):
+            print "  Line %d" % i
+            for j in range(0, len(context.lines[i].zones)):
+                print "    Zone: %d" % j
+                for k in range(0, len(context.lines[i].zones[j].words)):
+                    print "      Word %d = `%s` len(word): %d" % \
+                        (k, context.lines[i].zones[j].words[k].string, \
+                         len(context.lines[i].zones[j].words[k].string))
+
+    def findQuery(self, context):
+        """Performs a search on the string specified in searchQuery.
+
+           Arguments:
+           - context: The context from active script
+
+           Returns:
+           - The context of the match, if found
+        """
+
+        # Get the starting context so that we can restore it at the end.
+        #
+        originalLineIndex = context.lineIndex
+        originalZoneIndex = context.zoneIndex
+        originalWordIndex = context.wordIndex
+        originalCharIndex = context.charIndex
+
+        debug.println(self.debugLevel, \
+            "findQuery: original context line=%d zone=%d word=%d char=%d" \
+                % (originalLineIndex, originalZoneIndex, \
+                   originalWordIndex, originalCharIndex))
+        # self.dumpContext(context)
+
+        flags = re.LOCALE
+        if not self.caseSensitive:
+            flags = flags | re.IGNORECASE
+        if self.matchEntireWord:
+            regexp = "\\b" + self.searchString + "\\b"
+        else:
+            regexp = self.searchString
+        pattern = re.compile(regexp,flags)
+
+        debug.println(self.debugLevel, \
+            "findQuery: startAtTop: %d  regexp: `%s`" \
+                % (self.startAtTop, regexp))
+
+        if self.startAtTop:
+            context.goBegin(flat_review.Context.WINDOW)
+            self.debugContext(context, "go begin")
+
+        location = None
+        found = False
+        wrappedYet = False
+
+        doneWithLine = False
+        while not found:
+            # Check the current line for the string.
+            #
+            [currentLine, x, y, width, height] = \
+                 context.getCurrent(flat_review.Context.LINE)
+            debug.println(self.debugLevel, \
+                "findQuery: current line=`%s` x=%d y=%d width=%d height=%d" \
+                    % (currentLine, x, y, width, height))
+
+            if re.search(pattern, currentLine) and not doneWithLine:
+                # It's on this line. Check the current zone for the string.
+                #
+                while not found:
+                    [currentZone, x, y, width, height] = \
+                         context.getCurrent(flat_review.Context.ZONE)
+                    debug.println(self.debugLevel, \
+                        "findQuery: current zone=`%s` x=%d y=%d width=%d height=%d" \
+                            % (currentZone, x, y, width, height))
+
+                    if re.search(pattern, currentZone):
+                        # It's in this zone at least once.
+                        #
+                        theZone = context.lines[context.lineIndex] \
+                                         .zones[context.zoneIndex]
+                        startedInThisZone = \
+                              (originalLineIndex == context.lineIndex) and \
+                              (originalZoneIndex == context.zoneIndex)
+
+                        if theZone.accessible.text and \
+                           theZone.accessible.text.characterCount:
+                            # Make a list of the character offsets for the
+                            # matches in this zone.
+                            #
+                            allMatches = re.finditer(pattern, currentZone)
+                            offsets = []
+                            for m in allMatches:
+                                offsets.append(m.start(0))
+                            if self.searchBackwards:
+                                offsets.reverse()
+
+                            i = 0
+                            while not found and (i < len(offsets)):
+                                [nextInstance, offset] = \
+                                   theZone.getWordAtOffset(offsets[i])
+                                offsetDiff=nextInstance.index-context.wordIndex
+                                if self.searchBackwards and \
+                                              (offsetDiff < 0) or \
+                                   not self.searchBackwards and \
+                                              (offsetDiff > 0):
+                                    context.wordIndex = nextInstance.index
+                                    found = True
+                                elif not offsetDiff and not startedInThisZone:
+                                    # We landed on a match by happenstance.
+                                    # This can occur when the nextInstance is
+                                    # the first thing we come across.
+                                    #
+                                    found = True
+                                else:
+                                    i += 1
+                    if not found:
+                        ## Check to see if we've looked at all the zones on
+                        ## this line. If we have, then reset the zone, word
+                        ## and char indices, bump the line index and get the 
+                        ## next line.
+                        ##
+                        #if context.zoneIndex == (noZones-1):
+                        #    continue
+
+                        # Locate the next zone to try again.
+                        #
+                        if self.searchBackwards:
+                            moved = context.goPrevious( \
+                                        flat_review.Context.ZONE, \
+                                        flat_review.Context.WRAP_LINE)
+                            self.debugContext(context, "[1] go previous")
+                            context.goEnd(flat_review.Context.ZONE)
+                            self.debugContext(context, "[1] go end")
+                        else:
+                            moved = context.goNext( \
+                                        flat_review.Context.ZONE, \
+                                        flat_review.Context.WRAP_LINE)
+                            self.debugContext(context, "[1] go next")
+                        if not moved:
+                            doneWithLine = True
+                            break
+            else:
+                # Locate the next line to try again.
+                #
+                if self.searchBackwards:
+                    moved = context.goPrevious(flat_review.Context.LINE, \
+                                               flat_review.Context.WRAP_LINE)
+                    self.debugContext(context, "[2] go previous")
+                else:
+                    moved = context.goNext(flat_review.Context.LINE, \
+                                           flat_review.Context.WRAP_LINE)
+                    self.debugContext(context, "[2] go next")
+                if moved:
+                    if self.searchBackwards:
+                        moved = context.goEnd(flat_review.Context.LINE)
+                        self.debugContext(context, "[2] go end")
+                else:
+                    # Then we're at the screen's edge.
+                    #
+                    if self.windowWrap and not wrappedYet:
+                        doneWithLine = False
+                        wrappedYet = True
+                        if self.searchBackwards:
+                            speech.speak(_("Wrapping to Bottom"))
+                            moved = context.goPrevious( \
+                                    flat_review.Context.LINE, \
+                                    flat_review.Context.WRAP_ALL)
+                            self.debugContext(context, "[3] go previous")
+                        else:
+                            speech.speak(_("Wrapping to Top"))
+                            moved = context.goNext( \
+                                    flat_review.Context.LINE, \
+                                    flat_review.Context.WRAP_ALL)
+                            self.debugContext(context, "[3] go next")
+                        if not moved:
+                            debug.println(self.debugLevel, \
+                                          "findQuery: cannot wrap")
+                            break
+                    else:
+                        break
+        if found:
+            location = copy.copy(context)
+
+        self.debugContext(context, "before setting original")
+        context.setCurrent(originalLineIndex, originalZoneIndex, \
+                           originalWordIndex, originalCharIndex)
+        self.debugContext(context, "after setting original")
+
+        debug.println(self.debugLevel, \
+            "findQuery: returning line=%d zone=%d word=%d char=%d" \
+                % (location.lineIndex, location.zoneIndex, \
+                   location.wordIndex, location.charIndex))
+
+        return location
+
+def getLastQuery():
+    """Grabs the last search query performed from orca_state.
+
+       Returns:
+       - A copy of the last search query, if it exists
+    """
+
+    lastQuery = copy.copy(orca_state.searchQuery)
+    return lastQuery
Index: src/orca/orca-find.glade
===================================================================
RCS file: src/orca/orca-find.glade
diff -N src/orca/orca-find.glade
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/orca/orca-find.glade	30 Nov 2006 00:21:18 -0000
@@ -0,0 +1,414 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="findDialog">
+  <property name="title" translatable="yes">Orca Find</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="resizable">False</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">True</property>
+  <accessibility>
+    <atkproperty name="AtkObject::accessible_name" translatable="yes">Orca Find Dialog</atkproperty>
+  </accessibility>
+  <signal name="destroy" handler="findDialogDestroyed" last_modification_time="Mon, 09 Oct 2006 20:29:07 GMT"/>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="closeButton">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-close</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-7</property>
+	      <accessibility>
+		<atkproperty name="AtkObject::accessible_name" translatable="yes">Close</atkproperty>
+	      </accessibility>
+	      <signal name="clicked" handler="closeButtonClicked" last_modification_time="Wed, 11 Oct 2006 01:35:54 GMT"/>
+	      <accelerator key="c" modifiers="GDK_MOD1_MASK" signal="activate"/>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="findButton">
+	      <property name="visible">True</property>
+	      <property name="sensitive">False</property>
+	      <property name="can_default">True</property>
+	      <property name="has_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-find</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">0</property>
+	      <signal name="clicked" handler="findButtonClicked" last_modification_time="Tue, 10 Oct 2006 02:21:36 GMT"/>
+	      <accelerator key="f" modifiers="GDK_MOD1_MASK" signal="activate"/>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox1">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkHBox" id="hbox1">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkLabel" id="searchForLabel">
+		  <property name="visible">True</property>
+		  <property name="label" translatable="yes">_Search for:</property>
+		  <property name="use_underline">True</property>
+		  <property name="use_markup">True</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">False</property>
+		  <property name="selectable">False</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		  <property name="mnemonic_widget">searchForEntry</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		  <property name="width_chars">12</property>
+		  <property name="single_line_mode">False</property>
+		  <property name="angle">0</property>
+		  <accessibility>
+		    <atkproperty name="AtkObject::accessible_name" translatable="yes">Search for:</atkproperty>
+		  </accessibility>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkEntry" id="searchForEntry">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="editable">True</property>
+		  <property name="visibility">True</property>
+		  <property name="max_length">0</property>
+		  <property name="text" translatable="yes"></property>
+		  <property name="has_frame">True</property>
+		  <property name="invisible_char">●</property>
+		  <property name="activates_default">True</property>
+		  <accessibility>
+		    <atkproperty name="AtkObject::accessible_name" translatable="yes">Search for:</atkproperty>
+		  </accessibility>
+		  <signal name="changed" handler="searchForEntryChanged" last_modification_time="Wed, 11 Oct 2006 02:59:44 GMT"/>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkHBox" id="hbox2">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkFrame" id="startingPointFrame">
+		  <property name="visible">True</property>
+		  <property name="label_xalign">0</property>
+		  <property name="label_yalign">0.5</property>
+		  <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+		  <child>
+		    <widget class="GtkAlignment" id="alignment1">
+		      <property name="visible">True</property>
+		      <property name="xalign">1</property>
+		      <property name="yalign">0</property>
+		      <property name="xscale">1</property>
+		      <property name="yscale">0</property>
+		      <property name="top_padding">0</property>
+		      <property name="bottom_padding">0</property>
+		      <property name="left_padding">12</property>
+		      <property name="right_padding">0</property>
+
+		      <child>
+			<widget class="GtkVBox" id="vbox2">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">0</property>
+
+			  <child>
+			    <widget class="GtkRadioButton" id="currentLocationRadioButton">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="label" translatable="yes">C_urrent location</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="startingPointChanged" last_modification_time="Tue, 10 Oct 2006 02:43:13 GMT"/>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+
+			  <child>
+			    <widget class="GtkRadioButton" id="topRadioButton">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="label" translatable="yes">_Top of window</property>
+			      <property name="use_underline">True</property>
+			      <property name="relief">GTK_RELIEF_NORMAL</property>
+			      <property name="focus_on_click">True</property>
+			      <property name="active">False</property>
+			      <property name="inconsistent">False</property>
+			      <property name="draw_indicator">True</property>
+			      <property name="group">currentLocationRadioButton</property>
+			      <accessibility>
+				<atkproperty name="AtkObject::accessible_name" translatable="yes">Top of window</atkproperty>
+			      </accessibility>
+			      <signal name="toggled" handler="startingPointChanged" last_modification_time="Tue, 10 Oct 2006 02:43:31 GMT"/>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">False</property>
+			      <property name="fill">False</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="startFromFrame">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">&lt;b&gt;Start from:&lt;/b&gt;</property>
+		      <property name="use_underline">False</property>
+		      <property name="use_markup">True</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		      <accessibility>
+			<atkproperty name="AtkObject::accessible_name" translatable="yes">Start from:</atkproperty>
+		      </accessibility>
+		    </widget>
+		    <packing>
+		      <property name="type">label_item</property>
+		    </packing>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">6</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkAlignment" id="alignment3">
+		  <property name="visible">True</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xscale">1</property>
+		  <property name="yscale">1</property>
+		  <property name="top_padding">15</property>
+		  <property name="bottom_padding">0</property>
+		  <property name="left_padding">0</property>
+		  <property name="right_padding">0</property>
+
+		  <child>
+		    <widget class="GtkVBox" id="vbox3">
+		      <property name="visible">True</property>
+		      <property name="homogeneous">False</property>
+		      <property name="spacing">0</property>
+
+		      <child>
+			<widget class="GtkCheckButton" id="matchCaseCheckbox">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">_Match case</property>
+			  <property name="use_underline">True</property>
+			  <property name="relief">GTK_RELIEF_NORMAL</property>
+			  <property name="focus_on_click">True</property>
+			  <property name="active">False</property>
+			  <property name="inconsistent">False</property>
+			  <property name="draw_indicator">True</property>
+			  <signal name="toggled" handler="matchCaseChecked" last_modification_time="Wed, 11 Oct 2006 00:19:48 GMT"/>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+
+		      <child>
+			<widget class="GtkCheckButton" id="matchEntireWordCheckbox">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">Match _entire word only</property>
+			  <property name="use_underline">True</property>
+			  <property name="relief">GTK_RELIEF_NORMAL</property>
+			  <property name="focus_on_click">True</property>
+			  <property name="active">False</property>
+			  <property name="inconsistent">False</property>
+			  <property name="draw_indicator">True</property>
+			  <signal name="toggled" handler="matchEntireWordChecked" last_modification_time="Wed, 11 Oct 2006 00:23:23 GMT"/>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">15</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkAlignment" id="alignment4">
+		  <property name="visible">True</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xscale">1</property>
+		  <property name="yscale">1</property>
+		  <property name="top_padding">15</property>
+		  <property name="bottom_padding">0</property>
+		  <property name="left_padding">0</property>
+		  <property name="right_padding">5</property>
+
+		  <child>
+		    <widget class="GtkVBox" id="vbox4">
+		      <property name="visible">True</property>
+		      <property name="homogeneous">False</property>
+		      <property name="spacing">0</property>
+
+		      <child>
+			<widget class="GtkCheckButton" id="searchBackwardsCheckbox">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">Search _backwards</property>
+			  <property name="use_underline">True</property>
+			  <property name="relief">GTK_RELIEF_NORMAL</property>
+			  <property name="focus_on_click">True</property>
+			  <property name="active">False</property>
+			  <property name="inconsistent">False</property>
+			  <property name="draw_indicator">True</property>
+			  <signal name="toggled" handler="searchBackwardsChecked" last_modification_time="Wed, 11 Oct 2006 01:00:47 GMT"/>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+
+		      <child>
+			<widget class="GtkCheckButton" id="wrapAroundCheckbox">
+			  <property name="visible">True</property>
+			  <property name="can_focus">True</property>
+			  <property name="label" translatable="yes">_Wrap around</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="wrapAroundChecked" last_modification_time="Tue, 10 Oct 2006 02:44:45 GMT"/>
+			</widget>
+			<packing>
+			  <property name="padding">0</property>
+			  <property name="expand">False</property>
+			  <property name="fill">False</property>
+			</packing>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">10</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">5</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
Index: src/orca/orca.py
===================================================================
RCS file: /cvs/gnome/orca/src/orca/orca.py,v
retrieving revision 1.170
diff -u -r1.170 orca.py
--- src/orca/orca.py	16 Nov 2006 21:06:58 -0000	1.170
+++ src/orca/orca.py	30 Nov 2006 00:21:19 -0000
@@ -963,6 +963,22 @@
 
     return True
 
+def _showFindGUI(script=None, inputEvent=None):
+    """Displays the user interace to perform an Orca Find.
+
+    Returns True to indicate the input event has been consumed.
+    """
+
+    try:
+        module = __import__(settings.findModule,
+                            globals(),
+                            locals(),
+                            [''])
+        module.showFindUI()
+    except:
+        debug.printException(debug.LEVEL_SEVERE)
+        pass
+
 # If True, this module has been initialized.
 #
 _initialized = False
Index: src/orca/orca_gui_find.py
===================================================================
RCS file: src/orca/orca_gui_find.py
diff -N src/orca/orca_gui_find.py
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/orca/orca_gui_find.py	30 Nov 2006 00:21:19 -0000
@@ -0,0 +1,248 @@
+# Orca
+#
+# Copyright 2006 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., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Displays a GUI for the Orca Find window"""
+
+__id__        = "$Id:$"
+__version__   = "$Revision:$"
+__date__      = "$Date:$"
+__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import os
+import sys
+import debug
+import gettext
+import gtk
+import gtk.glade
+import locale
+
+import default
+import find
+import orca
+import orca_glade
+import orca_state
+import platform
+
+from orca_i18n import _  # for gettext support
+
+OS = None
+
+class orcaFindGUI(orca_glade.GladeWrapper):
+
+    def _init(self):
+        # Initialize the dialog box controls.
+        self.searchString = ""
+        self.searchBackwards = False
+        self.caseSensitive = False
+        self.matchEntireWord = False
+        self.windowWrap = True
+        self.startAtTop = False
+
+        self.activeScript = orca_state.activeScript
+
+    def _showGUI(self):
+        """Show the Orca Find dialog. This assumes that the GUI has
+        already been created.
+        """
+
+        # Set the current time on the Find GUI dialog so that it'll
+        # get focus. set_user_time is a new call in pygtk 2.9.2 or later.
+        # It's surronded by a try/except block here so that if it's not found,
+        # then we can fail gracefully.
+        #
+        try:
+            self.findDialog.realize()
+            self.findDialog.window.set_user_time(\
+                orca_state.lastInputEventTimestamp)
+        except AttributeError:
+            debug.printException(debug.LEVEL_FINEST)
+
+        self.findDialog.show()
+
+        # Populate the dialog box from the previous searchQuery, should
+        # one exist.  Note:  This is necessary because we are destroying
+        # the dialog (rather than merely hiding it) before performing the
+        # search.
+
+        try:
+            self.searchForEntry.set_text(\
+                orca_state.searchQuery.searchString)
+            self.searchForEntry.select_region (0,
+                len(self.searchForEntry.get_text()))
+            if orca_state.searchQuery.startAtTop:
+                self.topRadioButton.set_active(True)
+            self.matchCaseCheckbox.set_active(\
+                orca_state.searchQuery.caseSensitive)
+            self.matchEntireWordCheckbox.set_active(\
+                orca_state.searchQuery.matchEntireWord)
+            self.wrapAroundCheckbox.set_active(\
+                orca_state.searchQuery.windowWrap)
+            self.searchBackwardsCheckbox.set_active(\
+                orca_state.searchQuery.searchBackwards)
+        except:
+            pass
+
+    def searchForEntryChanged(self,widget):
+        """Signal handler for the "changed" signal for the
+           searchForEntry GtkEntry widget. The user has changed
+           the string to be searched for.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.searchString = widget.get_text()
+        if len(self.searchString) > 0:
+            self.findButton.set_sensitive(True)
+        else:
+            self.findButton.set_sensitive(False)
+
+    def startingPointChanged(self, widget):
+        """Signal handler for the "toggled" signal for the
+           currentLocationRadioButton or topRadioButton GtkRadioButton
+           widgets. The user has toggled the starting point for the search.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        if widget.get_active():
+            if widget.get_label() == _("C_urrent location"):
+                self.startAtTop = False
+            else:
+                self.startAtTop = True
+
+    def matchCaseChecked(self, widget):
+        """Signal handler for the "toggled" signal for the
+           matchCaseCheckbox GtkCheckButton widget. The user has
+           [un]checked the "Match Case" checkbox.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.caseSensitive = widget.get_active()
+
+    def matchEntireWordChecked(self, widget):
+        """Signal handler for the "toggled" signal for the
+           matchEntireWordCheckbox GtkCheckButton widget.
+           The user has [un]checked the "Match entire word"
+           checkbox.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.matchEntireWord = widget.get_active()
+
+    def searchBackwardsChecked(self, widget):
+        """Signal handler for the "toggled" signal for the
+           searchBackwardsCheckbox GtkCheckButton widget.
+           The user has [un]checked the "Search backwards"
+           checkbox.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.searchBackwards = widget.get_active()
+
+    def wrapAroundChecked(self, widget):
+        """Signal handler for the "toggled" signal for the
+           wrapAroundCheckbox GtkCheckButton widget. The user has
+           [un]checked the "Wrap around" checkbox.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.windowWrap = widget.get_active()
+
+    def closeButtonClicked(self, widget):
+        """Signal handler for the "clicked" signal for the cancelButton
+           GtkButton widget. The user has clicked the Cancel button.
+           Hide the dialog.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        self.findDialog.hide()
+
+    def findButtonClicked(self, widget):
+        """Signal handler for the "clicked" signal for the findButton
+           GtkButton widget. The user has clicked the Find button.
+           Call the method to begin the search.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        orca_state.searchQuery = find.SearchQuery()
+        orca_state.searchQuery.searchString = self.searchString
+        orca_state.searchQuery.searchBackwards = self.searchBackwards
+        orca_state.searchQuery.caseSensitive = self.caseSensitive
+        orca_state.searchQuery.matchEntireWord = self.matchEntireWord
+        orca_state.searchQuery.startAtTop = self.startAtTop
+        orca_state.searchQuery.windowWrap = self.windowWrap
+
+        self.activeScript.findCommandRun = True
+
+        # Merely hiding the dialog causes the find to take place before
+        # the original window has fully regained focus.
+        self.findDialog.destroy()
+
+
+    def findDialogDestroyed(self, widget):
+        """Signal handler for the "destroyed" signal for the findDialog
+           GtkWindow widget. Reset OS to None.
+
+        Arguments:
+        - widget: the component that generated the signal.
+        """
+
+        global OS
+
+        OS = None
+
+def showFindUI():
+    global OS
+
+    if not OS:
+        gladeFile = os.path.join(platform.prefix,
+                                 platform.datadirname,
+                                 platform.package,
+                                 "glade",
+                                 "orca-find.glade")
+        OS = orcaFindGUI(gladeFile, "findDialog")
+        OS._init()
+
+    OS._showGUI()
+
+def main():
+    locale.setlocale(locale.LC_ALL, '')
+
+    showFindUI()
+
+    gtk.main()
+    sys.exit(0)
+
+if __name__ == "__main__":
+    main()
Index: src/orca/orca_state.py
===================================================================
RCS file: /cvs/gnome/orca/src/orca/orca_state.py,v
retrieving revision 1.5
diff -u -r1.5 orca_state.py
--- src/orca/orca_state.py	27 Oct 2006 16:25:44 -0000	1.5
+++ src/orca/orca_state.py	30 Nov 2006 00:21:19 -0000
@@ -68,3 +68,8 @@
 # The last word spoken.
 #
 lastWord = ""
+
+# The last searchQuery
+#
+searchQuery = None
+
Index: src/orca/script.py
===================================================================
RCS file: /cvs/gnome/orca/src/orca/script.py,v
retrieving revision 1.40
diff -u -r1.40 script.py
--- src/orca/script.py	22 Oct 2006 20:51:44 -0000	1.40
+++ src/orca/script.py	30 Nov 2006 00:21:20 -0000
@@ -86,6 +86,8 @@
 
         self.flatReviewContextClass = flat_review.Context
 
+        self.findCommandRun = False
+
         debug.println(debug.LEVEL_FINE, "NEW SCRIPT: %s" % self.name)
 
     def getListeners(self):
Index: src/orca/settings.py
===================================================================
RCS file: /cvs/gnome/orca/src/orca/settings.py,v
retrieving revision 1.90
diff -u -r1.90 settings.py
--- src/orca/settings.py	18 Nov 2006 04:31:24 -0000	1.90
+++ src/orca/settings.py	30 Nov 2006 00:21:20 -0000
@@ -115,6 +115,11 @@
 #
 quitModule = "orca_quit"
 
+# The name of the module that holds the user interface for performing a
+# flat review find.
+#
+findModule = "orca_gui_find"
+
 # A list of keys that can serve as the Orca modifier key.  The list is
 # so we can provide better cross platform support (e.g., Sun keyboard
 # vs. PC-104 keyboard layouts).  When any of these keys is pressed,
