Index: Tomboy/NoteWindow.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/NoteWindow.cs,v
retrieving revision 1.49
diff -u -r1.49 NoteWindow.cs
--- Tomboy/NoteWindow.cs	6 Sep 2006 18:47:51 -0000	1.49
+++ Tomboy/NoteWindow.cs	31 Oct 2006 07:22:02 -0000
@@ -1,5 +1,6 @@
 
 using System;
+using System.Collections;
 using System.Runtime.InteropServices;
 using System.Text;
 using Mono.Unix;
@@ -142,6 +143,7 @@
 		Gtk.Menu plugin_menu;
 		Gtk.TextView editor;
 		Gtk.ScrolledWindow editor_window;
+		NoteFindBar find_bar;
 
 		GlobalKeybinder global_keys;
 		InterruptableTimeout mark_set_timeout;
@@ -189,10 +191,15 @@
 			editor_window.Show ();
 
 			FocusChild = editor;
+			
+			find_bar = new NoteFindBar (note);
+			find_bar.Visible = false;
+			find_bar.NoShowAll = true;
 
 			Gtk.VBox box = new Gtk.VBox (false, 2);
 			box.PackStart (toolbar, false, false, 0);
 			box.PackStart (editor_window, true, true, 0);
+			box.PackStart (find_bar, false, false, 0);
 			box.Show ();
 
 			// NOTE: Since some of our keybindings are only
@@ -206,12 +213,9 @@
 						    (uint) Gdk.Key.w, 
 						    Gdk.ModifierType.ControlMask,
 						    Gtk.AccelFlags.Visible);
-
-			// Close window (Escape)
-			global_keys.AddAccelerator (new EventHandler (CloseWindowHandler),
-						    (uint) Gdk.Key.Escape, 
-						    0,
-						    Gtk.AccelFlags.Visible);
+			
+			// Escape has been moved to be handled by a KeyPress Handler so that
+			// Escape can be used to close the FindBar.
 
 			// Close all windows on current Desktop (Ctrl-Q)
 			global_keys.AddAccelerator (new EventHandler (CloseAllWindowsHandler),
@@ -243,7 +247,10 @@
 						    (uint) Gdk.Key.F1, 
 						    0,
 						    0);
-
+			
+			// Have Esc key close the note window
+			KeyPressEvent += KeyPressed;
+						    
 			this.Add (box);
 		}
 
@@ -265,6 +272,21 @@
 			Move (x, y);
 		}
 
+		void KeyPressed (object sender, Gtk.KeyPressEventArgs args)
+		{
+			args.RetVal = true;
+			
+			switch (args.Event.Key)
+			{
+			case Gdk.Key.Escape:
+				CloseWindowHandler (null, null);
+				break;
+			default:
+				args.RetVal = false;
+				break;
+			}
+		}
+
 		// FIXME: Need to just emit a delete event, and do this work in
 		// the default delete handler, so that plugins can attach to
 		// delete event and have it always work.
@@ -440,8 +462,8 @@
 
 			Gtk.Widget find = 
 				toolbar.AppendItem (
-					Catalog.GetString ("Search"), 
-					Catalog.GetString ("Search your notes"),
+					Catalog.GetString ("Find"), 
+					Catalog.GetString ("Find in this note"),
 					null, 
 					new Gtk.Image (Gtk.Stock.Find, toolbar.IconSize),
 					new Gtk.SignalFunc (FindButtonClicked));
@@ -596,16 +618,17 @@
 		// Open the find dialog, passing any currently selected text
 		//
 
-		public NoteFindDialog Find {
+		public NoteFindBar Find {
 			get {
-				return NoteFindDialog.GetInstance (note);
+				return find_bar;
 			}
 		}
 
 		void FindButtonClicked ()
 		{
+			Find.ShowAll ();
+			Find.Visible = true;
 			Find.SearchText = note.Buffer.Selection;
-			Find.Present ();
 		}
 
 		void FindNextActivate (object sender, EventArgs args)
@@ -679,6 +702,421 @@
 		void OpenHelpActivate (object sender, EventArgs args)
 		{
 			GuiUtils.ShowHelp("tomboy.xml", "editing-notes", Screen, this);
+		}
+	}
+	
+	public class NoteFindBar : Gtk.HBox
+	{
+		private Note note;
+
+		Gtk.Entry entry;
+		Gtk.Button next_button;
+		Gtk.Button prev_button;
+		Gtk.CheckButton case_sensitive;
+		
+		ArrayList current_matches;
+		string prev_search_text;
+		
+		InterruptableTimeout entry_changed_timeout;
+
+		bool shift_key_pressed;
+		
+		public NoteFindBar (Note note) : base (false, 0)
+		{
+			this.note = note;
+			
+			note.Buffer.InsertText += OnInsertText;
+			note.Buffer.DeleteRange += OnDeleteRange;
+			
+			BorderWidth = 2;
+			
+			Gtk.Button button = new Gtk.Button ();
+			button.Image = new Gtk.Image (Gtk.Stock.Close, Gtk.IconSize.Menu);
+			button.Relief = Gtk.ReliefStyle.None;
+			button.Clicked += HideFindBar;
+			button.Show ();
+			PackStart (button, false, false, 4);
+
+			Gtk.Label label = new Gtk.Label (Catalog.GetString ("_Find:"));
+			label.Show ();
+			PackStart (label, false, false, 0);
+			
+			entry = new Gtk.Entry ();
+			label.MnemonicWidget = entry;
+			entry.Changed += OnFindEntryChanged;
+			entry.Activated += OnFindEntryActivated;
+			entry.Show ();
+			PackStart (entry, false, false, 0);
+			
+			prev_button = new Gtk.Button (Catalog.GetString ("_Previous"));
+			prev_button.Image = new Gtk.Image (Gtk.Stock.GoBack, Gtk.IconSize.Menu);
+			prev_button.Relief = Gtk.ReliefStyle.None;
+			prev_button.Sensitive = false;
+			prev_button.Clicked += OnPrevClicked;
+			prev_button.Show ();
+			PackStart (prev_button, false, false, 0);
+			
+			next_button = new Gtk.Button (Catalog.GetString ("Find _Next"));
+			next_button.Image = new Gtk.Image (Gtk.Stock.GoForward, Gtk.IconSize.Menu);
+			next_button.Relief = Gtk.ReliefStyle.None;
+			next_button.Sensitive = false;
+			next_button.Clicked += OnNextClicked;
+			next_button.Show ();
+			PackStart (next_button, false, false, 0);
+			
+			case_sensitive = new Gtk.CheckButton (Catalog.GetString ("Case _sensitive"));
+			case_sensitive.Toggled += OnCaseSensitiveToggled;
+			case_sensitive.Show ();
+			PackStart (case_sensitive, true, true, 0);
+			
+			// Bind ESC to close the FindBar if it's open and has focus or the window otherwise
+			// Also bind Return and Shift+Return to advance the search if the search entry has focus
+			shift_key_pressed = false;
+			entry.KeyPressEvent += KeyPressed;
+			entry.KeyReleaseEvent += KeyReleased;
+		}
+		
+		protected override void OnShown ()
+		{
+			entry.GrabFocus ();
+
+			// Highlight words from a previous existing search
+			HighlightMatches (true);
+
+			base.OnShown ();
+		}
+		
+		protected override void OnHidden ()
+		{
+			HighlightMatches (false);
+
+			base.OnShown ();
+		}
+		
+		void HideFindBar (object sender, EventArgs args)
+		{
+			Hide ();
+		}
+		
+		void OnPrevClicked (object sender, EventArgs args)
+		{
+			if (current_matches == null || current_matches.Count == 0)
+				return;
+			
+			for (int i = current_matches.Count; i > 0; i--) {
+				Match match = current_matches [i - 1] as Match;
+				
+				NoteBuffer buffer = match.Buffer;
+				Gtk.TextIter cursor = buffer.GetIterAtMark (buffer.InsertMark);
+				Gtk.TextIter end = buffer.GetIterAtMark (match.EndMark);
+				
+				if (end.Offset < cursor.Offset) {
+					JumpToMatch (match);
+					return;
+				}
+			}
+			
+			// Wrap to first match
+			JumpToMatch (current_matches [current_matches.Count - 1] as Match);
+		}
+		
+		void OnNextClicked (object sender, EventArgs args)
+		{
+			if (current_matches == null || current_matches.Count == 0)
+				return;
+				
+			for (int i = 0; i < current_matches.Count; i++) {
+				Match match = current_matches [i] as Match;
+				
+				NoteBuffer buffer = match.Buffer;
+				Gtk.TextIter cursor = buffer.GetIterAtMark (buffer.InsertMark);
+				Gtk.TextIter start = buffer.GetIterAtMark (match.StartMark);
+				
+				if (start.Offset >= cursor.Offset) {
+					JumpToMatch (match);
+					return;
+				}
+			}
+			
+			// Else wrap to first match
+			JumpToMatch (current_matches [0] as Match);
+		}
+		
+		void JumpToMatch (Match match)
+		{
+			NoteBuffer buffer = match.Buffer;
+
+			Gtk.TextIter start = buffer.GetIterAtMark (match.StartMark);
+			Gtk.TextIter end = buffer.GetIterAtMark (match.EndMark);
+
+			// Move cursor to end of match, and select match text
+			buffer.PlaceCursor (end);
+			buffer.MoveMark (buffer.SelectionBound, start);
+
+			Gtk.TextView editor = note.Window.Editor;
+			editor.ScrollMarkOnscreen (buffer.InsertMark);
+		}
+
+		void OnCaseSensitiveToggled (object sender, EventArgs args)
+		{
+			PerformSearch ();
+		}
+		
+		void OnFindEntryActivated (object sender, EventArgs args)
+		{
+			if (entry_changed_timeout != null)
+				entry_changed_timeout.Cancel ();
+			
+			if (prev_search_text != null 
+				&& SearchText != null 
+				&& prev_search_text.CompareTo (SearchText) == 0)
+				next_button.Click ();
+			else
+				PerformSearch ();
+		}
+		
+		void OnFindEntryChanged (object sender, EventArgs args)
+		{
+			if (entry_changed_timeout == null) {
+				entry_changed_timeout = new InterruptableTimeout ();
+				entry_changed_timeout.Timeout += EntryChangedTimeout;
+			}
+			
+			if (SearchText == null) {
+				PerformSearch ();
+			} else {
+				entry_changed_timeout.Reset (500);
+			}
+		}
+		
+		// Called after .5 seconds of typing inactivity, or on explicit
+		// activate.  Redo the serach and update the results...
+		void EntryChangedTimeout (object sender, EventArgs args)
+		{
+			if (SearchText == null)
+				return;
+			
+			PerformSearch ();
+		}
+		
+		void PerformSearch ()
+		{
+			CleanupMatches ();
+
+			string text = SearchText;
+			if (text == null)
+				return;
+
+			if (!case_sensitive.Active)
+				text = text.ToLower ();
+
+			string [] words = text.Split (' ', '\t', '\n');
+
+			// Used for matching in the raw note XML
+			string [] encoded_words = XmlEncoder.Encode (text).Split (' ', '\t', '\n');
+
+			current_matches = 
+				FindMatchesInBuffer (note.Buffer, 
+						     words, 
+						     case_sensitive.Active);
+			
+			prev_search_text = SearchText;
+
+			if (current_matches != null)
+				HighlightMatches (true);
+
+			UpdateSensitivity ();
+		}
+		
+		void UpdateSensitivity ()
+		{
+			if (SearchText == null) {
+				next_button.Sensitive = false;
+				prev_button.Sensitive = false;
+			}
+			
+			if (current_matches != null && current_matches.Count > 0) {
+				next_button.Sensitive = true;
+				prev_button.Sensitive = true;
+			} else {
+				next_button.Sensitive = false;
+				prev_button.Sensitive = false;
+			}
+		}
+		
+		void OnInsertText (object sender, Gtk.InsertTextArgs args)
+		{
+			PerformSearch ();
+		}
+		
+		void OnDeleteRange (object sender, Gtk.DeleteRangeArgs args)
+		{
+			PerformSearch ();
+		}
+		
+		//
+		// KeyPress and KeyRelease handlers
+		//
+		
+		void KeyPressed (object sender, Gtk.KeyPressEventArgs args)
+		{
+			args.RetVal = true;
+			
+			switch (args.Event.Key)
+			{
+			case Gdk.Key.Escape:
+				Hide ();
+				break;
+			case Gdk.Key.Shift_L:
+			case Gdk.Key.Shift_R:
+				shift_key_pressed = true;
+				args.RetVal = false;
+				break;
+			case Gdk.Key.Return:
+				if (shift_key_pressed)
+					prev_button.Click ();
+				break;
+			default:
+				args.RetVal = false;
+				break;
+			}
+		}
+		
+		void KeyReleased (object sender, Gtk.KeyReleaseEventArgs args)
+		{
+			args.RetVal = false;
+			
+			switch (args.Event.Key)
+			{
+			case Gdk.Key.Shift_L:
+			case Gdk.Key.Shift_R:
+				shift_key_pressed = false;
+				break;
+			}
+		}
+
+		public Gtk.Button FindNextButton
+		{
+			get { return next_button; }
+		}
+		
+		public Gtk.Button FindPreviousButton
+		{
+			get { return prev_button; }
+		}
+		
+		public string SearchText
+		{
+			get {
+				string text = entry.Text;
+				if (text.Trim () == String.Empty)
+					return null;
+
+				return text.Trim ();
+			}
+			set {
+				if (value != null && value != string.Empty)
+					entry.Text = value;
+				
+				entry.GrabFocus ();
+			}
+		}
+		
+		void HighlightMatches (bool highlight)
+		{
+			if (current_matches == null || current_matches.Count == 0)
+				return;
+			
+			foreach (Match match in current_matches) {
+				NoteBuffer buffer = match.Buffer;
+				
+				if (match.Highlighting != highlight) {
+					Gtk.TextIter start = buffer.GetIterAtMark (match.StartMark);
+					Gtk.TextIter end = buffer.GetIterAtMark (match.EndMark);
+					
+					match.Highlighting = highlight;
+					
+					if (match.Highlighting)
+						buffer.ApplyTag ("find-match", start, end);
+					else
+						buffer.RemoveTag ("find-match", start, end);
+				}
+			}
+		}
+		
+		void CleanupMatches ()
+		{
+			if (current_matches != null) {
+				HighlightMatches (false /* unhighlight */);
+				
+				foreach (Match match in current_matches) {
+					match.Buffer.DeleteMark (match.StartMark);
+					match.Buffer.DeleteMark (match.EndMark);
+				}
+				
+				current_matches = null;
+			}
+			
+			UpdateSensitivity ();
+		}
+
+		ArrayList FindMatchesInBuffer (NoteBuffer buffer, string [] words, bool match_case)
+		{
+			ArrayList matches = new ArrayList ();
+
+			string note_text = buffer.GetText (buffer.StartIter, 
+							   buffer.EndIter, 
+							   false /* hidden_chars */);
+			if (!match_case)
+				note_text = note_text.ToLower ();
+
+			foreach (string word in words) {
+				int idx = 0;
+				bool this_word_found = false;
+
+				if (word == String.Empty)
+					continue;
+
+				while (true) {					
+					idx = note_text.IndexOf (word, idx);
+
+					if (idx == -1) {
+						if (this_word_found)
+							break;
+						else
+							return null;
+					}
+
+					this_word_found = true;
+
+					Gtk.TextIter start = buffer.GetIterAtOffset (idx);
+					Gtk.TextIter end = start;
+					end.ForwardChars (word.Length);
+
+					Match match = new Match ();
+					match.Buffer = buffer;
+					match.StartMark = buffer.CreateMark (null, start, false);
+					match.EndMark = buffer.CreateMark (null, end, true);
+					match.Highlighting = false;
+
+					matches.Add (match);
+
+					idx += word.Length;
+				}
+			}
+
+			if (matches.Count == 0)
+				return null;
+			else
+				return matches;
+		}
+
+		class Match 
+		{
+			public NoteBuffer   Buffer;
+			public Gtk.TextMark StartMark;
+			public Gtk.TextMark EndMark;
+			public bool         Highlighting;
 		}
 	}
 
Index: Tomboy/Preferences.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Preferences.cs,v
retrieving revision 1.11
diff -u -r1.11 Preferences.cs
--- Tomboy/Preferences.cs	6 Sep 2006 18:47:51 -0000	1.11
+++ Tomboy/Preferences.cs	31 Oct 2006 07:22:02 -0000
@@ -360,16 +360,16 @@
 			keybind_peditor.AddGuard (entry);
 
 
-			// Search dialog keybinding...
+			// Open All Notes window keybinding...
 
-			label = MakeLabel (Catalog.GetString ("S_earch notes"));
+			label = MakeLabel (Catalog.GetString ("Open \"_All Notes\" window"));
 			table.Attach (label, 0, 1, 3, 4);
 
 			entry = new Gtk.Entry ();
 			entry.Show ();
 			table.Attach (entry, 1, 2, 3, 4);
 
-			peditor = new PropertyEditorEntry (Preferences.KEYBINDING_OPEN_SEARCH, 
+			peditor = new PropertyEditorEntry (Preferences.KEYBINDING_OPEN_RECENT_CHANGES, 
 							   entry);
 			SetupPropertyEditor (peditor);
 
Index: Tomboy/RecentChanges.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/RecentChanges.cs,v
retrieving revision 1.16
diff -u -r1.16 RecentChanges.cs
--- Tomboy/RecentChanges.cs	28 Jul 2006 08:15:50 -0000	1.16
+++ Tomboy/RecentChanges.cs	31 Oct 2006 07:22:02 -0000
@@ -1,7 +1,8 @@
 
 using System;
-using Mono.Unix;
+using System.Collections;
 using System.Text;
+using Mono.Unix;
 
 namespace Tomboy
 {
@@ -10,14 +11,31 @@
 		NoteManager manager;
 
 		Gtk.AccelGroup accel_group;
+		Gtk.ComboBoxEntry find_combo;
+		Gtk.Button clear_search_button;
+		Gtk.CheckButton case_sensitive;
 		Gtk.Label note_count;
 		Gtk.Button close_button;
 		Gtk.ScrolledWindow matches_window;
 		Gtk.VBox content_vbox;
+		Gtk.TreeViewColumn matches_column;
+		
+		int search_hit_max;
+		int search_hit_min;
 
 		Gtk.TreeView tree;
 		Gtk.ListStore store;
-
+		
+		Gtk.TreeModelFilter store_filter;
+		Gtk.TreeModelSort store_sort;
+		
+		/// <summary>
+		/// Stores search results as ArrayLists hashed by note uri.
+		/// </summary>
+		Hashtable current_matches;
+		
+		InterruptableTimeout entry_changed_timeout;
+		
 		static Type [] column_types = 
 			new Type [] {
 				typeof (Gdk.Pixbuf), // icon
@@ -27,6 +45,7 @@
 			};
 
 		static Gdk.Pixbuf note_icon;
+		static ArrayList previous_searches;
 
 		static NoteRecentChanges ()
 		{
@@ -34,11 +53,14 @@
 		}
 
 		public NoteRecentChanges (NoteManager manager)
-			: base (Catalog.GetString ("Table of Contents"))
+			: base (Catalog.GetString ("All Notes"))
 		{
 			this.manager = manager;
 			this.IconName = "tomboy";
 			this.DefaultWidth = 200;
+			this.current_matches = new Hashtable ();
+			this.search_hit_max = 0;
+			this.search_hit_min = 0;
 
 			// For Escape (Close)
 			accel_group = new Gtk.AccelGroup ();
@@ -47,16 +69,41 @@
 			Gtk.Image image = new Gtk.Image (Gtk.Stock.SortAscending, 
 							 Gtk.IconSize.Dialog);
 
-			Gtk.Label label = new Gtk.Label (Catalog.GetString (
-				"<b>Table of Contents</b> lists all your notes.\n" +
-				"Double click to open a note."));
-			label.UseMarkup = true;
-			label.Wrap = true;
+			Gtk.Label label = new Gtk.Label (Catalog.GetString ("_Filter:"));
+			label.Xalign = 1;
+			
+			find_combo = Gtk.ComboBoxEntry.NewText ();
+			label.MnemonicWidget = find_combo;
+			find_combo.Changed += OnEntryChanged;
+			find_combo.Entry.ActivatesDefault = false;
+			find_combo.Entry.Activated += OnEntryActivated;
+			if (previous_searches != null) {
+				foreach (string prev in previous_searches) {
+					find_combo.AppendText (prev);
+				}
+			}
+			
+			clear_search_button = new Gtk.Button (new Gtk.Image (Gtk.Stock.Clear, Gtk.IconSize.Menu));
+			clear_search_button.Sensitive = false;
+			clear_search_button.Clicked += ClearSearchClicked;
+			clear_search_button.Show ();
+			
+			case_sensitive = 
+				new Gtk.CheckButton (Catalog.GetString ("Case _Sensitive"));
+			case_sensitive.Toggled += OnCaseSensitiveToggled;
+
+			Gtk.Table table = new Gtk.Table (2, 3, false);
+			table.Attach (label, 0, 1, 0, 1, Gtk.AttachOptions.Shrink, 0, 0, 0);
+			table.Attach (find_combo, 1, 2, 0, 1);
+			table.Attach (case_sensitive, 1, 2, 1, 2);
+			table.Attach (clear_search_button, 2, 3, 0, 1, Gtk.AttachOptions.Shrink, 0, 0, 0);
+			table.ColumnSpacing = 4;
+			table.ShowAll ();
 
 			Gtk.HBox hbox = new Gtk.HBox (false, 2);
 			hbox.BorderWidth = 8;
 			hbox.PackStart (image, false, false, 4);
-			hbox.PackStart (label, false, false, 0);
+			hbox.PackStart (table, true, true, 0);
 			hbox.ShowAll ();
 
 			MakeRecentTree ();
@@ -117,6 +164,7 @@
 			content_vbox.Show ();
 
 			this.Add (content_vbox);
+			this.DeleteEvent += OnDelete;
 		}
 
 		void MakeRecentTree ()
@@ -160,6 +208,9 @@
 			title.PackStart (renderer, true);
 			title.AddAttribute (renderer, "text", 1 /* title */);
 			title.SortColumnId = 1; /* title */
+			title.SortIndicator = false;
+			title.Reorderable = false;
+			title.SortOrder = Gtk.SortType.Ascending;
 
 			tree.AppendColumn (title);
 
@@ -173,6 +224,9 @@
 			change.PackStart (renderer, false);
 			change.AddAttribute (renderer, "text", 2 /* change date */);
 			change.SortColumnId = 2; /* change date */
+			change.SortIndicator = false;
+			change.Reorderable = false;
+			change.SortOrder = Gtk.SortType.Descending;
 
 			tree.AppendColumn (change);
 		}
@@ -188,8 +242,14 @@
 			}
 
 			store = new Gtk.ListStore (column_types);
-			store.SetSortFunc (2 /* change date */,
-					   new Gtk.TreeIterCompareFunc (CompareDates));
+			
+			store_filter = new Gtk.TreeModelFilter (store, null);
+			store_filter.VisibleFunc = FilterNotes;
+			store_sort = new Gtk.TreeModelSort (store_filter);
+			store_sort.SetSortFunc (1 /* title */,
+				new Gtk.TreeIterCompareFunc (CompareTitles));
+			store_sort.SetSortFunc (2 /* change date */,
+				new Gtk.TreeIterCompareFunc (CompareDates));
 
 			int cnt = 0;
 			foreach (Note note in manager.Notes) {
@@ -204,14 +264,257 @@
 
 			// Set the sort column after loading data, since we
 			// don't want to resort on every append.
-			store.SetSortColumnId (sort_column, sort_type);
-
-			tree.Model = store;
+			store_sort.SetSortColumnId (sort_column, sort_type);
 
+			tree.Model = store_sort;
+			
 			note_count.Text = string.Format (Catalog.GetPluralString("Total: {0} note",
 										 "Total: {0} notes",
 										 cnt),
 							 cnt);
+
+			PerformSearch ();
+		}
+		
+		void PerformSearch ()
+		{
+			string text = SearchText;
+			if (text == null) {
+				RemoveMatchesColumn ();
+				current_matches.Clear ();
+				store_filter.Refilter ();
+				UpdateNoteCount (store.IterNChildren (), -1);
+				if (tree.IsRealized)
+					tree.ScrollToPoint (0, 0);
+				return;
+			}
+			
+			if (!case_sensitive.Active)
+				text = text.ToLower ();
+			
+			string [] words = text.Split (' ', '\t', '\n');
+			
+			// Used for matching in the raw note XML
+			string [] encoded_words = XmlEncoder.Encode (text).Split (' ', '\t', '\n');
+			
+			bool found_one = false;
+			search_hit_max = 0;
+			search_hit_min = Int32.MaxValue;
+			current_matches.Clear ();
+			
+			foreach (Note note in manager.Notes) {
+				// Check the note's raw XML for at least
+				// one match, to avoid deserializing
+				// Buffers unnecessarily.
+				if (!CheckNoteHasMatch (note, encoded_words, case_sensitive.Active))
+					continue;
+				
+				ArrayList note_matches =
+					FindMatchesInBuffer (note.Buffer, words, case_sensitive.Active);
+				
+				if (note_matches == null)
+					continue;
+				
+				int match_count = note_matches.Count;
+				
+				// Update the max and min hits
+				if (match_count > search_hit_max)
+					search_hit_max = match_count;
+				if (match_count < search_hit_min)
+					search_hit_min = match_count;
+
+				current_matches [note.Uri] = note_matches;
+				found_one = true;
+			}
+			
+			if (found_one)
+				UpdateNoteCount (store.IterNChildren (), current_matches.Count);
+			else
+				UpdateNoteCount (store.IterNChildren (), 0);
+
+			AddMatchesColumn ();
+			store_filter.Refilter ();
+			tree.ScrollToPoint (0, 0);
+		}
+		
+		void AddMatchesColumn ()
+		{
+			if (matches_column == null) {
+				Gtk.CellRenderer renderer;
+				
+				matches_column = new Gtk.TreeViewColumn ();
+				matches_column.Title = Catalog.GetString ("Matches");
+				matches_column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
+				matches_column.Resizable = false;
+				
+				renderer = new Gtk.CellRendererText ();
+				renderer.Width = 75;
+				matches_column.PackStart (renderer, false);
+				matches_column.SetCellDataFunc (renderer, new Gtk.TreeCellDataFunc (MatchesColumnDataFunc));
+				matches_column.SortColumnId = 4;
+				matches_column.SortIndicator = false;
+				matches_column.Reorderable = false;
+				matches_column.SortOrder = Gtk.SortType.Descending;
+				store_sort.SetSortFunc (4 /* matches */,
+					new Gtk.TreeIterCompareFunc (CompareSearchHits));
+			}
+			
+			if (tree.Columns.Length < 3)
+				tree.AppendColumn (matches_column);
+
+			store_sort.SetSortColumnId (4, Gtk.SortType.Descending);
+		}
+		
+		void RemoveMatchesColumn ()
+		{
+			if (matches_column == null)
+				return;
+
+			tree.RemoveColumn (matches_column);
+			matches_column = null;
+			
+			store_sort.SetSortColumnId (2, Gtk.SortType.Descending);
+		}
+		
+		void MatchesColumnDataFunc (Gtk.TreeViewColumn column, Gtk.CellRenderer cell,
+									  Gtk.TreeModel model, Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererText crt = cell as Gtk.CellRendererText;
+			if (crt == null)
+				return;
+			
+			string match_cnt = "";
+			
+			Note note = (Note) model.GetValue (iter, 3 /* note */);
+			if (note != null) {
+				ArrayList matches = current_matches [note.Uri] as ArrayList;
+				if (matches != null && matches.Count > 0) {
+					match_cnt =
+						string.Format (Catalog.GetPluralString ("{0} match",
+										"{0} matches",
+										matches.Count),
+										matches.Count);
+				}
+			}
+			
+			crt.Text = match_cnt;
+		}
+		
+		void UpdateNoteCount (int total, int matches)
+		{
+			string cnt =
+				string.Format (
+					Catalog.GetPluralString("Total: {0} note",
+										 "Total: {0} notes",
+										 total), total);
+			if (matches >= 0) {
+				cnt += ", " +
+					string.Format (Catalog.GetPluralString ("{0} match",
+									"{0} matches",
+									matches),
+									matches);
+			}
+			note_count.Text = cnt;
+		}
+		
+		bool CheckNoteHasMatch (Note note, string [] encoded_words, bool match_case)
+		{
+			string note_text = note.XmlContent;
+			if (!match_case)
+				note_text = note_text.ToLower ();
+			
+			foreach (string word in encoded_words) {
+				if (note_text.IndexOf (word) > -1)
+					continue;
+				else
+					return false;
+			}
+			
+			return true;
+		}
+		
+		ArrayList FindMatchesInBuffer (NoteBuffer buffer, string [] words, bool match_case)
+		{
+			ArrayList matches = new ArrayList ();
+			
+			string note_text = buffer.GetText (buffer.StartIter, buffer.EndIter, false /* hidden_chars */);
+			if (!match_case)
+				note_text = note_text.ToLower ();
+			
+			foreach (string word in words) {
+				int idx = 0;
+				bool this_word_found = false;
+				
+				if (word == String.Empty)
+					continue;
+				
+				while (true) {
+					idx = note_text.IndexOf (word, idx);
+					
+					if (idx == -1) {
+						if (this_word_found)
+							break;
+						else
+							return null;
+					}
+					
+					this_word_found = true;
+					
+					Gtk.TextIter start = buffer.GetIterAtOffset (idx);
+					Gtk.TextIter end = start;
+					end.ForwardChars (word.Length);
+					
+					Match match = new Match ();
+					match.Buffer = buffer;
+					match.StartMark = buffer.CreateMark (null, start, false);
+					match.EndMark = buffer.CreateMark (null, end, true);
+					match.Highlighting = false;
+					
+					matches.Add (match);
+					
+					idx += word.Length;
+				}
+			}
+			
+			if (matches.Count == 0)
+				return null;
+			else
+				return matches;
+		}
+		
+		class Match 
+		{
+			public NoteBuffer   Buffer;
+			public Gtk.TextMark StartMark;
+			public Gtk.TextMark EndMark;
+			public bool         Highlighting;
+		}
+
+		/// <summary>
+		/// Filter out notes based on the current search string
+		/// </summary>
+		bool FilterNotes (Gtk.TreeModel model, Gtk.TreeIter iter)
+		{
+			if (SearchText == null)
+				return true;
+			
+			if (current_matches.Count == 0)
+				return false;
+
+			Note note = (Note) model.GetValue (iter, 3 /* note */);
+			if (note == null)
+				return false;
+
+			ArrayList matches = current_matches [note.Uri] as ArrayList;
+			if (matches == null)
+				return false;
+			else
+				return true;
+		}
+
+		void OnCaseSensitiveToggled (object sender, EventArgs args)
+		{
+			PerformSearch ();
 		}
 
 		void OnNotesChanged (object sender, Note changed)
@@ -278,7 +581,30 @@
 			Hide ();
 			Destroy ();
 		}
-
+		
+		void OnDelete (object sender, Gtk.DeleteEventArgs args)
+		{
+			CloseClicked (sender, EventArgs.Empty);
+			args.RetVal = true;
+		}
+		
+		protected override void OnShown ()
+		{
+			find_combo.Entry.GrabFocus ();
+			base.OnShown ();
+		}
+		
+		int CompareTitles (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b)
+		{
+			string title_a = model.GetValue (a, 1 /* title */) as string;
+			string title_b = model.GetValue (b, 1 /* title */) as string;
+			
+			if (title_a == null || title_b == null)
+				return -1;
+			
+			return title_a.CompareTo (title_b);
+		}
+		
 		int CompareDates (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b)
 		{
 			Note note_a = (Note) model.GetValue (a, 3 /* note */);
@@ -289,16 +615,65 @@
 			else
 				return DateTime.Compare (note_a.ChangeDate, note_b.ChangeDate);
 		}
+		
+		int CompareSearchHits (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b)
+		{
+			Note note_a = model.GetValue (a, 3 /* note */) as Note;
+			Note note_b = model.GetValue (b, 3 /* note */) as Note;
+			
+			if (note_a == null || note_b == null) {
+				return -1;
+			}
+			
+			ArrayList matches_a = current_matches [note_a.Uri] as ArrayList;
+			ArrayList matches_b = current_matches [note_b.Uri] as ArrayList;
+			
+			if (matches_a == null || matches_b == null) {
+				if (matches_a != null)
+					return 1;
+
+				return -1;
+			}
+			
+			int result = matches_a.Count - matches_b.Count;
+			if (result == 0) {
+				// Do a secondary sort by note title in alphabetical order
+				result = CompareTitles (model, a, b);
+				
+				// Make sure to always sort alphabetically
+				if (result != 0) {
+					int sort_col_id;
+					Gtk.SortType sort_type;
+					if (store_sort.GetSortColumnId (out sort_col_id, out sort_type)) {
+						if (sort_type == Gtk.SortType.Descending)
+							result = result * -1;	// reverse sign
+					}
+				}
+				
+				return result;
+			}
+			
+			return result;
+		}
 
 		void OnRowActivated (object sender, Gtk.RowActivatedArgs args)
 		{
 			Gtk.TreeIter iter;
-			if (!store.GetIter (out iter, args.Path)) 
+			if (!store_sort.GetIter (out iter, args.Path)) 
 				return;
 
-			Note note = (Note) store.GetValue (iter, 3 /* note */);
+			Note note = (Note) store_sort.GetValue (iter, 3 /* note */);
 
 			note.Window.Present ();
+			
+			// Tell the new window to highlight the matches and
+			// prepopulate the Firefox-style search
+			if (SearchText != null) {
+				NoteFindBar find = note.Window.Find;
+				find.ShowAll ();
+				find.Visible = true;
+				find.SearchText = SearchText;
+			}
 		}
 
 		void OnKeyPressed (object obj, Gtk.KeyPressEventArgs args)
@@ -315,6 +690,88 @@
 				NoteUtils.ShowDeletionDialog (note, this);
 			}		
 		}
+		
+		void OnEntryActivated (object sender, EventArgs args)
+		{
+			if (entry_changed_timeout != null)
+				entry_changed_timeout.Cancel ();
+			
+			EntryChangedTimeout (null, null);
+		}
+
+		void OnEntryChanged (object sender, EventArgs args)
+		{
+			if (entry_changed_timeout == null) {
+				entry_changed_timeout = new InterruptableTimeout ();
+				entry_changed_timeout.Timeout += EntryChangedTimeout;
+			}
+			
+			if (SearchText == null) {
+				clear_search_button.Sensitive = false;
+				PerformSearch ();
+			} else {
+				entry_changed_timeout.Reset (500);
+				clear_search_button.Sensitive = true;
+			}
+		}
+		
+		// Called in after .5 seconds of typing inactivity, or on
+		// explicit activate.  Redo the search, and update the
+		// results...
+		void EntryChangedTimeout (object sender, EventArgs args)
+		{
+			if (SearchText == null)
+				return;
 
+			PerformSearch ();
+			AddToPreviousSearches (SearchText);
+		}
+
+		void AddToPreviousSearches (string text)
+		{
+			// Update previous searches, by adding a new term to the
+			// list, or shuffling an existing term to the top...
+
+			if (previous_searches == null)
+				previous_searches = new ArrayList ();
+
+			bool repeat = false;
+
+			if (case_sensitive.Active) {
+				repeat = previous_searches.Contains (text);
+			} else {
+				string lower = text.ToLower();
+				foreach (string prev in previous_searches) {
+					if (prev.ToLower() == lower)
+						repeat = true;
+				}
+			}
+
+			if (!repeat) {
+				previous_searches.Insert (0, text);
+				find_combo.PrependText (text);
+			}
+		}
+		
+		void ClearSearchClicked (object sender, EventArgs args)
+		{
+			find_combo.Entry.Text = "";
+			find_combo.Entry.GrabFocus ();
+		}
+		
+		public string SearchText
+		{
+			get {
+				string text = find_combo.Entry.Text;
+				if (text.Trim () == String.Empty)
+					return null;
+				
+				return text.Trim ();
+			}
+			set {
+				if (value != null && value != "")
+					find_combo.AppendText (value);
+			}
+		}
 	}
 }
Index: Tomboy/Tray.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Tray.cs,v
retrieving revision 1.45
diff -u -r1.45 Tray.cs
--- Tomboy/Tray.cs	9 Aug 2006 19:18:32 -0000	1.45
+++ Tomboy/Tray.cs	31 Oct 2006 07:22:02 -0000
@@ -13,6 +13,8 @@
 		Gtk.Tooltips tips;
 		Gtk.Image image;
 		PreferencesDialog prefs_dlg;
+		Gtk.Window recent_dlg;
+		
 		int icon_size_last = -1;
 
 		static Gdk.Pixbuf note_icon;
@@ -189,7 +191,7 @@
 
 			menu.Append (new Gtk.SeparatorMenuItem ());
 
-			item = new Gtk.ImageMenuItem (Catalog.GetString ("_Table of Contents"));
+			item = new Gtk.ImageMenuItem (Catalog.GetString ("_All Notes"));
 			item.Image = new Gtk.Image (Gtk.Stock.SortAscending, Gtk.IconSize.Menu);
 			item.Activated += ViewRecentChanges;
 			menu.Append (item);
@@ -199,16 +201,6 @@
 					item, 
 					Preferences.KEYBINDING_OPEN_RECENT_CHANGES);
 
-			item = new Gtk.ImageMenuItem (Catalog.GetString ("_Search Notes..."));
-			item.Image = new Gtk.Image (Gtk.Stock.Find, Gtk.IconSize.Menu);
-			item.Activated += SearchNotes;
-			menu.Append (item);
-
-			if (enable_keybindings)
-				GConfKeybindingToAccel.AddAccelerator (
-					item, 
-					Preferences.KEYBINDING_OPEN_SEARCH);
-
 			menu.ShowAll ();
 			return menu;
 		}
@@ -268,10 +260,20 @@
 			find_dialog.Present ();
 		}
 
+		void OnRecentChangesHidden (object sender, EventArgs args)
+		{
+			recent_dlg = null;
+		}
+
 		void ViewRecentChanges (object sender, EventArgs args)
 		{
-			Gtk.Window recent = new NoteRecentChanges (manager);
-			recent.Show ();
+			if (recent_dlg == null) {
+				recent_dlg = new NoteRecentChanges (manager);
+				recent_dlg.Hidden += OnRecentChangesHidden;
+				recent_dlg.Show ();
+			}
+			
+			recent_dlg.Present ();
 		}
 
 		// Used by TomboyApplet to modify the icon background.
