
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Mono.Unix;

namespace Tomboy
{
        public class NoteRecentChanges : ForcedPresentWindow
        {
                NoteManager manager;

                Gtk.MenuBar menu_bar;
                Gtk.ComboBoxEntry find_combo;
                Gtk.Button clear_search_button;
                Gtk.CheckButton case_sensitive;
                Gtk.Label note_count;
                Gtk.ScrolledWindow matches_window;
                Gtk.VBox content_vbox;
                Gtk.TreeViewColumn matches_column;
				
				Notebooks.NotebooksTreeView notebooksTree;
				
                // Use the following like a Set
		        Dictionary<Tag, Tag> selected_tags;

                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
                        typeof (string),     // title
                        typeof (string),     // change date
                        typeof (Note),       // note
                };

                static Gdk.Pixbuf note_icon;
                static Gdk.Pixbuf all_notes_icon;
                static Gdk.Pixbuf notebook_icon;
                static ArrayList previous_searches;
                static NoteRecentChanges instance;

                static NoteRecentChanges ()
                {
                        note_icon = GuiUtils.GetIcon ("tomboy-note", 22);
                        all_notes_icon = GuiUtils.GetIcon ("tomboy-all-notes", 22);
                        notebook_icon = GuiUtils.GetIcon ("tomboy-notebook", 22);
                }

                public static NoteRecentChanges GetInstance (NoteManager manager)
                {
                        if (instance == null)
                                instance = new NoteRecentChanges (manager);
                        System.Diagnostics.Debug.Assert (
                                instance.manager == manager,
                                "Multiple NoteManagers not supported");
                        return instance;
                }

                protected NoteRecentChanges (NoteManager manager)
: base (Catalog.GetString ("Search All Notes"))
                {
                        this.manager = manager;
                        this.IconName = "tomboy";
                        this.DefaultWidth = 200;
                        this.current_matches = new Hashtable ();

                        selected_tags = new Dictionary<Tag, Tag> ();

                        AddAccelGroup (Tomboy.ActionManager.UI.AccelGroup);

                        menu_bar = CreateMenuBar ();

                        Gtk.Image image = new Gtk.Image (GuiUtils.GetIcon ("system-search", 48));

                        Gtk.Label label = new Gtk.Label (Catalog.GetString ("_Search:"));
                        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 ("C_ase 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 (table, true, true, 0);
                        hbox.ShowAll ();
						
						// Notebooks Pane
						Gtk.Widget notebooksPane = MakeNotebooksPane ();
						notebooksPane.Show ();
                        
                        MakeRecentTree ();
                        tree.Show ();

                        note_count = new Gtk.Label ();
                        note_count.Xalign = 0;
                        note_count.Show ();

                        // Update on changes to notes
                        manager.NoteDeleted += OnNotesChanged;
                        manager.NoteAdded += OnNotesChanged;
                        manager.NoteRenamed += OnNoteRenamed;
                        manager.NoteSaved += OnNoteSaved;

                        // List all the current notes
                        UpdateResults ();

                        matches_window = new Gtk.ScrolledWindow ();
                        matches_window.ShadowType = Gtk.ShadowType.In;

                        // Reign in the window size if there are notes with long
                        // names, or a lot of notes...

                        Gtk.Requisition tree_req = tree.SizeRequest ();
                        if (tree_req.Height > 420)
                                matches_window.HeightRequest = 420;
                        else
                                matches_window.VscrollbarPolicy = Gtk.PolicyType.Never;

                        if (tree_req.Width > 480)
                                matches_window.WidthRequest = 480;
                        else
                                matches_window.HscrollbarPolicy = Gtk.PolicyType.Never;

                        matches_window.Add (tree);
                        matches_window.Show ();

					   Gtk.HPaned hpaned = new Gtk.HPaned ();
					   hpaned.Position = 150;
						hpaned.Add1 (notebooksPane);
					   hpaned.Add2 (matches_window);
					   hpaned.Show ();

                        Gtk.HBox status_box = new Gtk.HBox (false, 8);
                        status_box.PackStart (note_count, true, true, 0);
                        status_box.Show ();

                        Gtk.VBox vbox = new Gtk.VBox (false, 8);
                        vbox.BorderWidth = 6;
                        vbox.PackStart (hbox, false, false, 0);
                        vbox.PackStart (hpaned, true, true, 0);
                        vbox.PackStart (matches_window, true, true, 0);
                        vbox.PackStart (status_box, false, false, 0);
                        vbox.Show ();

                        // Use another VBox to place the MenuBar
                        // right at thetop of the window.
                        content_vbox = new Gtk.VBox (false, 0);
                        content_vbox.PackStart (menu_bar, false, false, 0);
                        content_vbox.PackStart (vbox, true, true, 0);
                        content_vbox.Show ();

                        this.Add (content_vbox);
                        this.DeleteEvent += OnDelete;
                        this.KeyPressEvent += OnKeyPressed; // For Escape
                        
                        // Watch when notes are added to notebooks so the search
                        // results will be updated immediately instead of waiting
                        // until the note's QueueSave () kicks in.
                        Notebooks.NotebookManager.NoteAddedToNotebook += OnNoteAddedToNotebook;
                        Notebooks.NotebookManager.NoteRemovedFromNotebook += OnNoteRemovedFromNotebook;
                }

                Gtk.MenuBar CreateMenuBar ()
                {
                        ActionManager am = Tomboy.ActionManager;
                        Gtk.MenuBar menubar =
                                am.GetWidget ("/MainWindowMenubar") as Gtk.MenuBar;

                        am ["OpenNoteAction"].Activated += OnOpenNote;
                        am ["DeleteNoteAction"].Activated += OnDeleteNote;
                        am ["OpenNotebookTemplateNoteAction"].Activated += OnOpenNotebookTemplateNote;
			am ["NewNotebookAction"].Activated += OnNewNotebook;
                        am ["DeleteNotebookAction"].Activated += OnDeleteNotebook;
                        am ["CloseWindowAction"].Activated += OnCloseWindow;
                        if (Tomboy.TrayIconShowing == false)
                                am ["CloseWindowAction"].Visible = false;

                        // Allow Escape to close the window as well as <Control>W
                        // Should be able to add Escape to the CloseAction.  Can't do that
                        // until someone fixes AccelGroup.Connect:
                        //     http://bugzilla.ximian.com/show_bug.cgi?id=76988)
                        //
                        // am.UI.AccelGroup.Connect ((uint) Gdk.Key.Escape,
                        //   0,
                        //   Gtk.AccelFlags.Mask,
                        //   OnCloseWindow);

                        return menubar;
                }

		Gtk.Widget MakeNotebooksPane ()
		{
			notebooksTree = new Notebooks.NotebooksTreeView (Notebooks.NotebookManager.NotebooksWithAllNotesItem);
			notebooksTree.Selection.Mode = Gtk.SelectionMode.Single;
			notebooksTree.HeadersVisible = true;
			notebooksTree.RulesHint = false;
			
			Gtk.CellRenderer renderer;
			
			Gtk.TreeViewColumn column = new Gtk.TreeViewColumn ();
			column.Title = Catalog.GetString ("Notebooks");
			column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
			column.Resizable = false;
			
			renderer = new Gtk.CellRendererPixbuf ();
			column.PackStart (renderer, false);
			column.SetCellDataFunc (renderer,
				new Gtk.TreeCellDataFunc (NotebookPixbufCellDataFunc));
			
			renderer = new Gtk.CellRendererText ();
			column.PackStart (renderer, true);
			column.SetCellDataFunc (renderer,
				new Gtk.TreeCellDataFunc (NotebookTextCellDataFunc));
			
			notebooksTree.AppendColumn (column);
			
			notebooksTree.RowActivated += OnNotebookRowActivated;
			notebooksTree.Selection.Changed += OnNotebookSelectionChanged;
			notebooksTree.ButtonPressEvent += OnNotebooksTreeButtonPressed;
			notebooksTree.KeyPressEvent += OnNotebooksKeyPressed;
			
			notebooksTree.Show ();
			Gtk.ScrolledWindow sw = new Gtk.ScrolledWindow ();
			sw.HscrollbarPolicy = Gtk.PolicyType.Automatic;
			sw.VscrollbarPolicy = Gtk.PolicyType.Automatic;
			sw.ShadowType = Gtk.ShadowType.In;
			sw.Add (notebooksTree);
			sw.Show ();
			
			return sw;
		}

                void MakeRecentTree ()
                {
                        Gtk.TargetEntry [] targets =
                        new Gtk.TargetEntry [] {
                                new Gtk.TargetEntry ("STRING",
                                Gtk.TargetFlags.App,
                                0),
                                new Gtk.TargetEntry ("text/plain",
                                Gtk.TargetFlags.App,
                                0),
                                new Gtk.TargetEntry ("text/uri-list",
                                Gtk.TargetFlags.App,
                                1),
                        };

                        tree = new Gtk.TreeView ();
                        tree.HeadersVisible = true;
                        tree.RulesHint = true;
                        tree.RowActivated += OnRowActivated;
                        tree.DragDataGet += OnDragDataGet;
                        tree.Selection.Changed += OnSelectionChanged;
                        tree.ButtonPressEvent += OnButtonPressed;

                        tree.EnableModelDragSource (Gdk.ModifierType.Button1Mask,
                                                    targets,
                                                    Gdk.DragAction.Copy);

                        Gtk.CellRenderer renderer;

                        Gtk.TreeViewColumn title = new Gtk.TreeViewColumn ();
                        title.Title = Catalog.GetString ("Note");
                        title.Sizing = Gtk.TreeViewColumnSizing.Autosize;
                        title.Resizable = true;

                        renderer = new Gtk.CellRendererPixbuf ();
                        title.PackStart (renderer, false);
                        title.AddAttribute (renderer, "pixbuf", 0 /* icon */);

                        renderer = new Gtk.CellRendererText ();
                        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);

                        Gtk.TreeViewColumn change = new Gtk.TreeViewColumn ();
                        change.Title = Catalog.GetString ("Last Changed");
                        change.Sizing = Gtk.TreeViewColumnSizing.Autosize;
                        change.Resizable = true;

                        renderer = new Gtk.CellRendererText ();
                        renderer.Data ["xalign"] = 1.0;
                        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);
                }

                void UpdateResults ()
                {
                        // Restore the currently highlighted note
                        Note selected_note = null;
                        Gtk.TreeIter selected_iter;
                        if (store_sort != null &&
                                        tree.Selection.GetSelected (out selected_iter)) {
                                selected_note =
                                        (Note) store_sort.GetValue (selected_iter, 3 /* note */);
                        }

                        int sort_column = 2; /* change date */
                        Gtk.SortType sort_type = Gtk.SortType.Descending;
                        if (store_sort != null)
                                store_sort.GetSortColumnId (out sort_column, out sort_type);

                        store = new Gtk.ListStore (column_types);

                        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) {
                                string nice_date =
                                        GuiUtils.GetPrettyPrintDate (note.ChangeDate, true);

                                Gtk.TreeIter iter =
                                        store.AppendValues (note_icon,  /* icon */
                                                            note.Title, /* title */
                                                            nice_date,  /* change date */
                                                            note);      /* note */
                                cnt++;
                        }

                        tree.Model = store_sort;

                        PerformSearch ();

                        if (sort_column >= 0) {
                                // Set the sort column after loading data, since we
                                // don't want to resort on every append.
                                store_sort.SetSortColumnId (sort_column, sort_type);
                        }

                        // Restore the previous selection
                        if (selected_note != null) {
                                SelectNote (selected_note);
                        }
                }

                void SelectNote (Note note)
                {
                        Gtk.TreeIter iter;

                        if (store_sort.IterChildren (out iter) == false)
                                return;

                        do {
                                Note iter_note = (Note) store_sort.GetValue (iter, 3 /* note */);
                                if (iter_note == note) {
                                        // Found it!
                                        tree.Selection.SelectIter (iter);
                                        break;
                                }
                        } while (store_sort.IterNext (ref iter));
                }

		void PerformSearch()
		{
			// For some reason, the matches column must be rebuilt
			// every time because otherwise, it's not sortable.
			RemoveMatchesColumn ();
			Search search = new Search(manager);

			string text = SearchText;
			if (text == null) {
				current_matches.Clear ();
				store_filter.Refilter ();
				UpdateTotalNoteCount (store_sort.IterNChildren ());
				if (tree.IsRealized)
					tree.ScrollToPoint (0, 0);
				return;
			}
			if (!case_sensitive.Active)
				text = text.ToLower ();

			current_matches.Clear ();
			
			// Search using the currently selected notebook
			Notebooks.Notebook selected_notebook = GetSelectedNotebook ();
			if (selected_notebook is Notebooks.AllNotesNotebook)
				selected_notebook = null;
			
			IDictionary<Note,int> results =
				search.SearchNotes(text, case_sensitive.Active, selected_notebook);
			foreach (Note note in results.Keys){
				current_matches.Add(note.Uri, results[note]);
			}
			
			AddMatchesColumn ();
			store_filter.Refilter ();
			tree.ScrollToPoint (0, 0);
			UpdateMatchNoteCount (current_matches.Count);
		}

                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.Fixed;
                                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 = true;
                                matches_column.Reorderable = false;
                                matches_column.SortOrder = Gtk.SortType.Descending;
                                matches_column.Clickable = true;
                                store_sort.SetSortFunc (4 /* matches */,
                                                        new Gtk.TreeIterCompareFunc (CompareSearchHits));

                                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_str = "";

                        Note note = (Note) model.GetValue (iter, 3 /* note */);
                        if (note != null) {
                                object matches = current_matches [note.Uri];
                                if (matches != null) {
                                        int match_count = (int) matches;
                                        if (match_count > 0) {
                                                match_str = string.Format (
                                                                    Catalog.GetPluralString ("{0} match",
                                                                                             "{0} matches",
                                                                                             match_count),
                                                                    match_count);
                                        }
                                }
                        }

                        crt.Text = match_str;
                }
                
                void UpdateTotalNoteCount (int total)
                {
                	string status = string.Format (
                			Catalog.GetPluralString ("Total: {0} note",
                									 "Total: {0} notes",
                									 total),
                			total);
                	note_count.Text = status;
                }
                
                void UpdateMatchNoteCount (int matches)
                {
                	string status = string.Format (
                			Catalog.GetPluralString ("Matches: {0} note",
                									 "Matches: {0} notes",
                									 matches),
                			matches);
                	note_count.Text = status;
                }
                
                /// <summary>
                /// Filter out notes based on the current search string
                /// and selected tags.  Also prevent template notes from
                /// appearing.
                /// </summary>
                bool FilterNotes (Gtk.TreeModel model, Gtk.TreeIter iter)
                {
                        Note note = model.GetValue (iter, 3 /* note */) as Note;
                        if (note == null)
                                return false;
                        
                        // Don't show the template notes in the list
                        Tag template_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSystemTag);
                        if (note.ContainsTag (template_tag))
                        	return false;

                        bool passes_search_filter = FilterBySearch (note);
                        if (passes_search_filter == false)
                                return false; // don't waste time checking tags if it's already false

			            bool passes_tag_filter = FilterByTag (note);

                        // Must pass both filters to appear in the list
                        return passes_tag_filter && passes_search_filter;
                       // return true;
                }
		
		        bool FilterTags (Gtk.TreeModel model, Gtk.TreeIter iter)
                {
						Tag t = model.GetValue (iter, 0 /* note */) as Tag;
						if(t.IsProperty || t.IsSystem)
							return false;
						
						return true;
                }

                // <summary>
                // Return true if the specified note should be shown in the list
                // based on the current selection of tags.  If no tags are selected,
                // all notes should be allowed.
                // </summary>
				  bool FilterByTag (Note note)
				  {
				   if (selected_tags.Count == 0)
				    return true;

				//   // FIXME: Ugh!  NOT an O(1) operation.  Is there a better way?
				   List<Tag> tags = note.Tags;
				   foreach (Tag tag in note.Tags) {
				    if (selected_tags.ContainsKey (tag))
				     return true;
				   }

				   return false;
				  }

                // <summary>
                // Return true if the specified note should be shown in the list
                // based on the search string.  If no search string is specified,
                // all notes should be allowed.
                // </summary>
                bool FilterBySearch (Note note)
                {
                        if (SearchText == null)
                                return true;

                        if (current_matches.Count == 0)
                                return false;

                        return note != null && current_matches [note.Uri] != null;
                }

                void OnCaseSensitiveToggled (object sender, EventArgs args)
                {
                        PerformSearch ();
                }

                void OnNotesChanged (object sender, Note changed)
                {
                        UpdateResults ();
                }

                void OnNoteRenamed (Note note, string old_title)
                {
                        UpdateResults ();
                }

                void OnNoteSaved (Note note)
                {
                        UpdateResults ();
                }

                void OnDragDataGet (object sender, Gtk.DragDataGetArgs args)
                {
                        Note note = GetSelectedNote ();
                        if (note == null)
                                return;

                        // FIXME: Gtk.SelectionData has no way to get the
                        //        requested target.

                        args.SelectionData.Set (Gdk.Atom.Intern ("text/uri-list", false),
                                                8,
                                                Encoding.UTF8.GetBytes (note.Uri));

                        args.SelectionData.Text = note.Title;
                }

                void OnSelectionChanged (object sender, EventArgs args)
                {
                        Note note = GetSelectedNote ();
                        if (note != null) {
                                Tomboy.ActionManager ["OpenNoteAction"].Sensitive = true;
                                Tomboy.ActionManager ["DeleteNoteAction"].Sensitive = true;
                        } else {
                                Tomboy.ActionManager ["OpenNoteAction"].Sensitive = false;
                                Tomboy.ActionManager ["DeleteNoteAction"].Sensitive = false;
                        }
                }

                [GLib.ConnectBefore]
                void OnButtonPressed (object sender, Gtk.ButtonPressEventArgs args)
                {
                        switch (args.Event.Button) {
                        case 3: // third mouse button (right-click)
                                Gtk.TreePath path = null;
                                Gtk.TreeViewColumn column = null;

                                if (tree.GetPathAtPos ((int) args.Event.X,
                                                       (int) args.Event.Y,
                                                       out path,
                                                       out column) == false)
                                        break;

                                Gtk.TreeSelection selection = tree.Selection;
                                if (selection.CountSelectedRows () == 0)
                                        break;

		                        Gtk.Menu menu = Tomboy.ActionManager.GetWidget (
                                            "/MainWindowContextMenu") as Gtk.Menu;
                                PopupContextMenuAtLocation (menu,
                                							(int) args.Event.X,
                                                            (int) args.Event.Y);

                                break;
                        }
                }

                void PopupContextMenuAtLocation (Gtk.Menu menu, int x, int y)
                {
                        menu.ShowAll ();
                        Gtk.MenuPositionFunc pos_menu_func = null;

                        // Set up the funtion to position the context menu
                        // if we were called by the keyboard Gdk.Key.Menu.
                        if (x == 0 && y == 0)
                                pos_menu_func = PositionContextMenu;

                        menu.Popup (null, null,
                                    pos_menu_func,
                                    0,
                                    Gtk.Global.CurrentEventTime);
                }

                // This is needed for when the user opens
                // the context menu with the keyboard.
                void PositionContextMenu (Gtk.Menu menu,
                                          out int x, out int y, out bool push_in)
                {
                        Gtk.TreeIter iter;
                        Gtk.TreePath path;
                        Gtk.TreeSelection selection;

                        // Set default "return" values
                        push_in = false; // not used
                        x = 0;
                        y = 0;

                        selection = tree.Selection;
                        if (!selection.GetSelected (out iter))
                                return;

                        path = store_sort.GetPath (iter);

                        int pos_x = 0;
                        int pos_y = 0;

                        GetWidgetScreenPos (tree, ref pos_x, ref pos_y);
                        Gdk.Rectangle cell_rect = tree.GetCellArea (path, tree.Columns [0]);

                        // Add 100 to x so it's not be at the far left
                        x = pos_x + cell_rect.X + 100;
                        y = pos_y + cell_rect.Y;
                }

                // Walk the widget hiearchy to figure out
                // where to position the context menu.
                void GetWidgetScreenPos (Gtk.Widget widget, ref int x, ref int y)
                {
                        int widget_x;
                        int widget_y;

                        if (widget is Gtk.Window) {
                                ((Gtk.Window) widget).GetPosition (out widget_x, out widget_y);
                        } else {
                                GetWidgetScreenPos (widget.Parent, ref x, ref y);

                                // Special case the TreeView because it adds
                                // too much since it's in a scrolled window.
                                if (widget == tree) {
                                        widget_x = 2;
                                        widget_y = 2;
                                } else {
                                        Gdk.Rectangle widget_rect = widget.Allocation;
                                        widget_x = widget_rect.X;
                                        widget_y = widget_rect.Y;
                                }
                        }

                        x += widget_x;
                        y += widget_y;
                }

                Note GetSelectedNote ()
                {
                        Gtk.TreeModel model;
                        Gtk.TreeIter iter;

                        if (!tree.Selection.GetSelected (out model, out iter))
                                return null;

                        return (Note) model.GetValue (iter, 3 /* note */);
                }

                void OnOpenNote (object sender, EventArgs args)
                {
                        Note note = GetSelectedNote ();
                        if (note == null)
                                return;

                        note.Window.Present ();
                }

                void OnDeleteNote (object sender, EventArgs args)
                {
                        Note note = GetSelectedNote ();
                        if (note == null)
                                return;

                        NoteUtils.ShowDeletionDialog (note, this);
                }

                void OnCloseWindow (object sender, EventArgs args)
                {
                        // Disconnect external signal handlers to prevent bloweup
                        manager.NoteDeleted -= OnNotesChanged;
                        manager.NoteAdded -= OnNotesChanged;
                        manager.NoteRenamed -= OnNoteRenamed;
                        manager.NoteSaved -= OnNoteSaved;
                        
                        Notebooks.NotebookManager.NoteAddedToNotebook -= OnNoteAddedToNotebook;
                        Notebooks.NotebookManager.NoteRemovedFromNotebook -= OnNoteRemovedFromNotebook;

                        // The following code has to be done for the MenuBar to
                        // appear properly the next time this window is opened.
                        if (menu_bar != null) {
                                content_vbox.Remove (menu_bar);
                                ActionManager am = Tomboy.ActionManager;
                                am ["OpenNoteAction"].Activated -= OnOpenNote;
                                am ["DeleteNoteAction"].Activated -= OnDeleteNote;
				am ["NewNotebookAction"].Activated -= OnNewNotebook;
				am ["DeleteNotebookAction"].Activated -= OnDeleteNotebook;
				am ["OpenNotebookTemplateNoteAction"].Activated -= OnOpenNotebookTemplateNote;
                                am ["CloseWindowAction"].Activated -= OnCloseWindow;
                        }

                        Hide ();
                        Destroy ();
                        instance = null;

                        if (Tomboy.TrayIconShowing == false)
                                Tomboy.ActionManager ["QuitTomboyAction"].Activate ();
                }

                void OnDelete (object sender, Gtk.DeleteEventArgs args)
                {
                        OnCloseWindow (sender, EventArgs.Empty);
                        args.RetVal = true;
                }

                void OnKeyPressed (object sender, Gtk.KeyPressEventArgs args)
                {
                        switch (args.Event.Key) {
                        case Gdk.Key.Escape:
                                // Allow Escape to close the window
                                OnCloseWindow (this, EventArgs.Empty);
                                break;
                        case Gdk.Key.Menu:
                                // Pop up the context menu if a note is selected
                                Note note = GetSelectedNote ();
                                if (note != null) {
			                        Gtk.Menu menu = Tomboy.ActionManager.GetWidget (
                                                "/MainWindowContextMenu") as Gtk.Menu;
                                    PopupContextMenuAtLocation (menu, 0, 0);
                                }

                                break;
                        }
                }

		protected override void OnShown ()
		{
			// Select "All Notes" in the notebooks list
			SelectAllNotesNotebook ();
			
			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 */);
                        Note note_b = (Note) model.GetValue (b, 3 /* note */);

                        if (note_a == null || note_b == null)
                                return -1;
                        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;
                        }

                        object matches_a = current_matches [note_a.Uri];
                        object matches_b = current_matches [note_b.Uri];

                        if (matches_a == null || matches_b == null) {
                                if (matches_a != null)
                                        return 1;

                                return -1;
                        }

                        int result = (int) matches_a - (int) matches_b;
                        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_sort.GetIter (out iter, args.Path))
                                return;

                        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 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 ();
                }

		private void NotebookPixbufCellDataFunc (Gtk.TreeViewColumn treeColumn,
				Gtk.CellRenderer renderer, Gtk.TreeModel model,
				Gtk.TreeIter iter)
		{
			Notebooks.Notebook notebook = model.GetValue (iter, 0) as Notebooks.Notebook;
			if (notebook == null)
				return;
			
			Gtk.CellRendererPixbuf crp = renderer as Gtk.CellRendererPixbuf;
			if (notebook is Notebooks.AllNotesNotebook) {
				crp.Pixbuf = all_notes_icon;
			} else {
				crp.Pixbuf = notebook_icon;
			}
		}
		
		private void NotebookTextCellDataFunc (Gtk.TreeViewColumn treeColumn,
				Gtk.CellRenderer renderer, Gtk.TreeModel model,
				Gtk.TreeIter iter)
		{
			Gtk.CellRendererText crt = renderer as Gtk.CellRendererText;
			crt.Ellipsize = Pango.EllipsizeMode.End;
			Notebooks.Notebook notebook = model.GetValue (iter, 0) as Notebooks.Notebook;
			if (notebook == null) {
				crt.Text = String.Empty;
				return;
			}
			
			crt.Text = notebook.Name;

			if (notebook is Notebooks.AllNotesNotebook) {
				// Bold the All Notes item
				crt.Markup = string.Format ("<span weight=\"bold\">{0}</span>", notebook.Name);
			} else {
				crt.Text = notebook.Name;
			}
		}
		
		private void OnNotebookSelectionChanged (object sender, EventArgs args)
		{
			Notebooks.Notebook notebook = GetSelectedNotebook ();
			if (notebook == null) {
				// Clear out the currently selected tags so that no notebook is selected
				selected_tags.Clear ();
                Tomboy.ActionManager ["OpenNotebookTemplateNoteAction"].Sensitive = false;
				Tomboy.ActionManager ["DeleteNotebookAction"].Sensitive = false;
				
				// Select the "All Notes" item without causing
				// this handler to be called again
				notebooksTree.Selection.Changed -= OnNotebookSelectionChanged;
				SelectAllNotesNotebook ();
				notebooksTree.Selection.Changed += OnNotebookSelectionChanged;
			} else {
				selected_tags.Clear ();
				if (notebook.Tag != null)
					selected_tags.Add (notebook.Tag, notebook.Tag);
				if (notebook is Notebooks.AllNotesNotebook) {
	                Tomboy.ActionManager ["OpenNotebookTemplateNoteAction"].Sensitive = true;
					Tomboy.ActionManager ["DeleteNotebookAction"].Sensitive = false;
				} else {
	                Tomboy.ActionManager ["OpenNotebookTemplateNoteAction"].Sensitive = true;
					Tomboy.ActionManager ["DeleteNotebookAction"].Sensitive = true;
				}
			}
			
			UpdateResults ();
		}
		
		void OnNewNotebook (object sender, EventArgs args)
		{
			Notebooks.NotebookManager.PromptCreateNewNotebook (this);
		}
		
		void OnDeleteNotebook (object sender, EventArgs args)
		{
			Notebooks.Notebook notebook = GetSelectedNotebook ();
			if (notebook == null)
				return;
			
			Notebooks.NotebookManager.PromptDeleteNotebook (this, notebook);
		}

		// Open the notebook's note template when activated
		private void OnNotebookRowActivated (object sender, Gtk.RowActivatedArgs args)
		{
			OnOpenNotebookTemplateNote (sender, EventArgs.Empty);
		}
		
		private void OnOpenNotebookTemplateNote (object sender, EventArgs args)
		{
			Notebooks.Notebook notebook = GetSelectedNotebook ();
			if (notebook == null)
				return;
			
			Note templateNote = notebook.GetTemplateNote ();
			if (templateNote == null)
				return; // something seriously went wrong
			
			templateNote.Window.Present ();
		}
		
		private Notebooks.Notebook GetSelectedNotebook ()
		{
			Gtk.TreeModel model;
			Gtk.TreeIter iter;
			
			if (notebooksTree.Selection.GetSelected (out model, out iter) == false)
				return null; // Nothing selected
			
			return model.GetValue (iter, 0) as Notebooks.Notebook;
		}
		
		private void SelectAllNotesNotebook ()
		{
			Gtk.TreeIter iter;
			if (notebooksTree.Model.GetIterFirst (out iter) == true) {
				notebooksTree.Selection.SelectIter (iter);
			}
		}
		
		[GLib.ConnectBefore]
		void OnNotebooksTreeButtonPressed (object sender, Gtk.ButtonPressEventArgs args)
		{
			switch (args.Event.Button) {
				case 3: // third mouse button (right-click)
					Notebooks.Notebook notebook = GetSelectedNotebook ();
					if (notebook == null)
						return; // Don't pop open a submenu
					
					Gtk.TreePath path = null;
					Gtk.TreeViewColumn column = null;

					bool rowClicked = true;

					if (notebooksTree.GetPathAtPos ((int) args.Event.X,
							(int) args.Event.Y, out path, out column) == false)
						rowClicked = false;

					Gtk.TreeSelection selection = notebooksTree.Selection;
					if (selection.CountSelectedRows () == 0)
						rowClicked = false;

					Gtk.Menu menu = null;
					if (rowClicked)
						menu = Tomboy.ActionManager.GetWidget (
							"/NotebooksTreeContextMenu") as Gtk.Menu;
					else
						menu = Tomboy.ActionManager.GetWidget (
							"/NotebooksTreeNoRowContextMenu") as Gtk.Menu;
					
					PopupContextMenuAtLocation (menu,
												(int) args.Event.X,
												(int) args.Event.Y);

				break;
			}
		}

		void OnNotebooksKeyPressed (object sender, Gtk.KeyPressEventArgs args)
		{
			switch (args.Event.Key) {
				case Gdk.Key.Escape:
					// Allow Escape to close the window
					OnCloseWindow (this, EventArgs.Empty);
					break;
				case Gdk.Key.Menu:
					// Pop up the context menu if a notebook is selected
					Notebooks.Notebook notebook = GetSelectedNotebook ();
					if (notebook == null || notebook is Notebooks.AllNotesNotebook)
						return; // Don't pop open a submenu
					
					Gtk.Menu menu = Tomboy.ActionManager.GetWidget (
						"/NotebooksTreeContextMenu") as Gtk.Menu;
					PopupContextMenuAtLocation (menu, 0, 0);

					break;
			}
		}
		
		private void OnNoteAddedToNotebook (Note note, Notebooks.Notebook notebook)
		{
			UpdateResults ();
		}
		
		private void OnNoteRemovedFromNotebook (Note note, Notebooks.Notebook notebook)
		{
			UpdateResults ();
		}

                public string SearchText
                {
                        get {
                                string text = find_combo.Entry.Text;
                                text = text.Trim ();
                                if (text == String.Empty)
                                        return null;
                                return text;
                        }
                        set {
                                if (value != null && value != "")
                                        find_combo.Entry.Text = value;
                        }
                }
        }
}
