Index: Tomboy/PluginManager.cs
===================================================================
--- Tomboy/PluginManager.cs	(revision 1117)
+++ Tomboy/PluginManager.cs	(working copy)
@@ -323,6 +323,7 @@
 			typeof (NoteLinkWatcher),
 			typeof (NoteWikiWatcher),
 			typeof (MouseHandWatcher),
+			typeof (NoteTagsWatcher),
 			
 			// Not ready yet:
 			// typeof (NoteRelatedToWatcher),
Index: Tomboy/TagManager.cs
===================================================================
--- Tomboy/TagManager.cs	(revision 0)
+++ Tomboy/TagManager.cs	(revision 0)
@@ -0,0 +1,169 @@
+
+using System;
+using System.Collections.Generic;
+using Gtk;
+
+namespace Tomboy
+{
+	// <summary>
+	// The Gtk.TreeIter is valid for the TagManager.Tags (Gtk.ListStore).
+	// </summary>
+	public delegate void TagAddedEventHandler (Tag tag, Gtk.TreeIter iter);
+//	public delegate void TagChangedEventHandler (Tag tag, Gtk.TreeIter iter);
+	public delegate void TagRemovedEventHandler (string tag_name);
+
+	public class TagManager
+	{
+		static Gtk.ListStore tags;
+		static Gtk.TreeModelSort sorted_tags;
+		static Dictionary<string, Gtk.TreeIter> tag_map;
+		static object locker = new object ();
+		
+		#region Constructors
+		static TagManager ()
+		{
+			tags = new Gtk.ListStore (typeof (Tag));
+			
+			sorted_tags = new Gtk.TreeModelSort (tags);
+			sorted_tags.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTagsSortFunc));
+//			sorted_tags.SetSortFunc (0,
+//					new TreeIterCompareFunc (CompareTags));
+			sorted_tags.SetSortColumnId (0, Gtk.SortType.Ascending);
+			
+			// <summary>
+			// The key for this dictionary is Tag.Name.ToLower ().
+			// </summary>
+			tag_map = new Dictionary<string, Gtk.TreeIter> ();
+		}
+		
+		private TagManager ()
+		{
+		}
+		#endregion
+		
+		#region Private Methods
+		static int CompareTagsSortFunc (TreeModel model, TreeIter a, TreeIter b)
+		{
+			Tag tag_a = model.GetValue (a, 0) as Tag;
+			Tag tag_b = model.GetValue (b, 0) as Tag;
+			
+			if (tag_a == null || tag_b == null)
+				return 0;
+			
+			return string.Compare (tag_a.NormalizedName, tag_b.NormalizedName);
+		}
+		#endregion
+		
+		#region Public Static Methods
+		// <summary>
+		// Return an existing tag for the specified tag name.  If no Tag exists
+		// null will be returned.
+		// </summary>
+		public static Tag GetTag (string tag_name)
+		{
+			if (tag_name == null)
+				throw new ArgumentNullException ("TagManager.GetTag () called with a null tag name.");
+			
+			string normalized_tag_name = tag_name.Trim ().ToLower ();
+			if (normalized_tag_name == String.Empty)
+				throw new ArgumentException ("TagManager.GetTag () called with an empty tag name.");
+			
+			if (tag_map.ContainsKey (normalized_tag_name)) {
+				Gtk.TreeIter iter = tag_map [normalized_tag_name];
+				return tags.GetValue (iter, 0) as Tag;
+			}
+			
+			return null;
+		}
+		
+		// <summary>
+		// Same as GetTag () but will create a new tag if one doesn't already exist.
+		// </summary>
+		public static Tag GetOrCreateTag (string tag_name)
+		{
+			if (tag_name == null)
+				throw new ArgumentNullException ("TagManager.GetOrCreateTag () called with a null tag name.");
+			
+			string normalized_tag_name = tag_name.Trim ().ToLower ();
+			if (normalized_tag_name == String.Empty)
+				throw new ArgumentException ("TagManager.GetOrCreateTag () called with an empty tag name.");
+			
+			Gtk.TreeIter iter = Gtk.TreeIter.Zero;
+			bool tag_added = false;
+			Tag tag = GetTag (normalized_tag_name);
+			if (tag == null) {
+				lock (locker) {
+					tag = GetTag (normalized_tag_name);
+					if (tag == null) {
+						tag = new Tag (tag_name.Trim ());
+						iter = tags.Append ();
+						tags.SetValue (iter, 0, tag);
+						tag_map [tag.NormalizedName] = iter;
+						Logger.Debug ("Added TreeIter to tag_map: {0}", tag.NormalizedName);
+						
+						tag_added = true;
+					}
+				}
+			}
+			
+			if (tag_added && TagAdded != null)
+				TagAdded (tag, iter);
+			
+			return tag;
+		}
+		
+		// <summary>
+		// This will remove the tag from every note that is currently tagged
+		// and from the main list of tags.
+		// </summary>
+		public static void RemoveTag (Tag tag)
+		{
+Logger.Debug ("TagManager.RemoveTag, Total Number of Tags: {0}", tag_map.Count);
+			if (tag == null)
+				throw new ArgumentNullException ("TagManager.RemoveTag () called with a null tag");
+
+			bool tag_removed = false;
+			if (tag_map.ContainsKey (tag.NormalizedName)) {
+				lock (locker) {
+					if (tag_map.ContainsKey (tag.NormalizedName)) {
+						Gtk.TreeIter iter = tag_map [tag.NormalizedName];
+						if (tags.Remove (ref iter)) {
+							Logger.Debug ("\tRemoved the tag from the ListStore: {0}", tag.NormalizedName);
+						} else {
+							// FIXME: For some really weird reason, this block actually gets called sometimes!
+							Logger.Warn ("\tCall to remove tag from ListStore failed: {0}", tag.NormalizedName);
+						}
+
+						tag_map.Remove (tag.NormalizedName);
+						Logger.Debug ("Removed TreeIter from tag_map: {0}", tag.NormalizedName);
+						tag_removed = true;
+
+						foreach (Note note in tag.Notes) {
+							note.RemoveTag (tag);
+						}
+					}
+				}
+			}
+			
+			if (tag_removed && TagRemoved != null) {
+				TagRemoved (tag.NormalizedName);
+			}
+		}
+		
+		// <summary>
+		// </summary>
+		#endregion
+		
+		#region Properties
+		public static Gtk.TreeModel Tags
+		{
+			get { return sorted_tags; }
+		}
+		#endregion
+		
+		#region Events
+		public static event TagAddedEventHandler TagAdded;
+		public static event TagRemovedEventHandler TagRemoved;
+		#endregion
+	}
+}
Index: Tomboy/Tag.cs
===================================================================
--- Tomboy/Tag.cs	(revision 0)
+++ Tomboy/Tag.cs	(revision 0)
@@ -0,0 +1,94 @@
+
+using System;
+using System.Collections.Generic;
+
+namespace Tomboy
+{
+	public class Tag
+	{
+		string name;
+		string normalized_name;
+		
+		// <summary>
+		// Used to track which notes are currently tagged by this tag.  The
+		// dictionary key is the Note.Uri.
+		// </summary>
+		Dictionary<string, Note> notes;
+		
+		#region Constructors
+		public Tag(string tag_name)
+		{
+			Name = tag_name;
+			notes = new Dictionary<string,Note> ();
+		}
+		#endregion
+		
+		#region Public Methods
+		// <summary>
+		// Associates the specified note with this tag.
+		// </summary>
+		public void AddNote (Note note)
+		{
+			if (!notes.ContainsKey (note.Uri)) {
+				notes [note.Uri] = note;
+			}
+		}
+		
+		// <summary>
+		// Unassociates the specified note with this tag.
+		// </summary>
+		public void RemoveNote (Note note)
+		{
+			if (notes.ContainsKey (note.Uri)) {
+				notes.Remove (note.Uri);
+			}
+		}
+		#endregion
+		
+		#region Properties
+		// <summary>
+		// The name of the tag.  This is what the user types in as the tag and
+		// what's used to show the tag to the user.
+		// </summary>
+		public string Name
+		{
+			get { return name; }
+			set {
+				if (value != null) {
+					string trimmed_name = (value as string).Trim ();
+					if (trimmed_name != String.Empty) {
+						name = trimmed_name;
+						normalized_name = trimmed_name.ToLower ();
+					}
+				}
+			}
+		}
+		
+		// <summary>
+		// Use the string returned here to reference the tag in Dictionaries.
+		// </summary>
+		public string NormalizedName
+		{
+			get { return normalized_name; }
+		}
+		
+		// <summary>
+		// Returns a list of all the notes that this tag is associated with. 
+		// </summary>
+		public List<Note> Notes
+		{
+			get {
+				return new List<Note> (notes.Values);
+			}
+		}
+		
+		// <summary>
+		// Returns the number of notes this is currently tagging.
+		// </summary>
+		public int Popularity
+		{
+			get { return notes.Count; }
+		}
+		#endregion
+	}
+}
Index: Tomboy/Watchers.cs
===================================================================
--- Tomboy/Watchers.cs	(revision 1117)
+++ Tomboy/Watchers.cs	(working copy)
@@ -1050,4 +1050,56 @@
 			}
 		}
 	}
+	
+	public class NoteTagsWatcher : NotePlugin
+	{
+		static NoteTagsWatcher ()
+		{
+		}
+
+		protected override void Initialize ()
+		{
+			Note.TagAdded += OnTagAdded;
+			Note.TagRemoving += OnTagRemoving;
+			Note.TagRemoved += OnTagRemoved;
+		}
+
+		protected override void Shutdown ()
+		{
+			Note.TagAdded -= OnTagAdded;
+			Note.TagRemoving -= OnTagRemoving;
+			Note.TagRemoved -= OnTagRemoved;
+		}
+
+		protected override void OnNoteOpened ()
+		{
+			// FIXME: Just for kicks, spit out the current tags
+			Logger.Debug ("{0} tags:", Note.Title);
+			foreach (Tag tag in Note.Tags) {
+				Logger.Debug ("\t{0}", tag.Name);
+			}
+		}
+		
+		void OnTagAdded (Note note, Tag tag)
+		{
+			Logger.Debug ("Tag added to {0}: {1}", note.Title, tag.Name);
+		}
+		
+		void OnTagRemoving (Note note, Tag tag)
+		{
+			Logger.Debug ("Removing tag from {0}: {1}", note.Title, tag.Name);
+		}
+		
+		// <summary>
+		// Keep the TagManager clean by removing tags that are no longer
+		// tagging any other notes.
+		// </summary>
+		void OnTagRemoved (Note note, string tag_name)
+		{
+			Tag tag = TagManager.GetTag (tag_name);
+Logger.Debug ("Watchers.OnTagRemoved popularity count: {0}", tag.Popularity);
+			if (tag.Popularity == 0)
+				TagManager.RemoveTag (tag);
+		}
+	}
 }
Index: Tomboy/NoteWindow.cs
===================================================================
--- Tomboy/NoteWindow.cs	(revision 1117)
+++ Tomboy/NoteWindow.cs	(working copy)
@@ -197,6 +197,7 @@
 		Gtk.Menu plugin_menu;
 		Gtk.TextView editor;
 		Gtk.ScrolledWindow editor_window;
+		NoteTagBar tag_bar;
 		NoteFindBar find_bar;
 
 		GlobalKeybinder global_keys;
@@ -245,7 +246,12 @@
 
 			toolbar = MakeToolbar ();
 			toolbar.Show ();
-
+			
+			tag_bar = new NoteTagBar (note);
+			tag_bar.Visible = false;
+			tag_bar.NoShowAll = true;
+			tag_bar.Hidden += TagBarHidden;
+			
 			// The main editor widget
 			editor = new NoteEditor (note.Buffer);
 			editor.PopulatePopup += OnPopulatePopup;
@@ -274,6 +280,7 @@
 
 			Gtk.VBox box = new Gtk.VBox (false, 2);
 			box.PackStart (toolbar, false, false, 0);
+			box.PackStart (tag_bar, false, false, 0);
 			box.PackStart (editor_window, true, true, 0);
 			box.PackStart (find_bar, false, false, 0);
 			box.Show ();
@@ -608,6 +615,20 @@
 			toolbar.AppendWidget (text_button, 
 					      Catalog.GetString ("Set properties of text"), 
 					      null);
+			
+			// FIXME: Use a proper icon for the Tags button
+			Gtk.Widget tags_button =
+				toolbar.AppendItem (
+					Catalog.GetString ("Tags"),
+					Catalog.GetString ("Add/Remove note tags"),
+					null,
+					new Gtk.Image (Gtk.Stock.SelectFont, toolbar.IconSize),
+					new Gtk.SignalFunc (TagBarClicked));
+			tags_button.AddAccelerator ("activate",
+						    accel_group,
+						    (uint) Gdk.Key.t, 
+						    Gdk.ModifierType.ControlMask,
+						    Gtk.AccelFlags.Visible);
 
 			ToolMenuButton plugin_button = 
 				new ToolMenuButton (toolbar, 
@@ -707,7 +728,7 @@
 
 			return menu;
 		}
-
+		
 		//
 		// Open the find dialog, passing any currently selected text
 		//
@@ -740,6 +761,13 @@
 			Find.FindPreviousButton.Click ();
 		}
 		
+		void TagBarHidden (object sender, EventArgs args)
+		{
+			// Reposition the current focus back to the editor so the
+			// cursor will be ready for typing.
+			editor.GrabFocus ();
+		}
+		
 		void FindBarHidden (object sender, EventArgs args)
 		{
 			// Reposition the current focus back to the editor so the
@@ -824,9 +852,259 @@
 		void ChangeDepthLeftHandler (object sender, EventArgs args)
 		{
 			((NoteBuffer)editor.Buffer).ChangeCursorDepthDirectional (false);
-		}	
+		}
+		
+		void TagBarClicked ()
+		{
+			if (tag_bar.Visible)
+				tag_bar.Hide ();
+			else
+				tag_bar.Show ();
+		}
 	}
 	
+	public class NoteTagBar : Gtk.HBox
+	{
+		private Note note;
+		
+		WrapBox tag_cloud;
+//		Gtk.HBox tag_cloud;
+		Gtk.Entry tag_entry;
+		Gtk.Button add_tag_button;
+		
+		#region Constructors
+		public NoteTagBar (Note note) : base (false, 4)
+		{
+			this.note = note;
+			
+			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 += HideTagBar;
+			button.Show ();
+			PackStart (button, false, false, 4);
+
+			Gtk.Label label = new Gtk.Label (
+					string.Format ("<span weight=\"bold\">{0}</span>",
+						Catalog.GetString ("Tags:")));
+			label.Xalign = 0;
+			label.UseMarkup = true;
+			label.Show ();
+			
+			PackStart (label, false, false, 0);
+			
+			// FIXME: Write a custom widget that allows the tag buttons to flow to
+			// the next line if needed.
+//			tag_cloud = new Gtk.HBox (false, 4);
+			tag_cloud = new WrapBox ();
+			tag_cloud.Show ();
+			PackStart (tag_cloud, true, true, 0);
+			
+			foreach (Tag tag in note.Tags) {
+				button = MakeTagButton (tag);
+				button.Show ();
+				//tag_cloud.PackStart (button, false, false, 0);
+				tag_cloud.Insert (button);
+			}
+			
+			Gtk.EntryCompletion entry_completion = new Gtk.EntryCompletion ();
+			entry_completion.InlineCompletion = true;
+			entry_completion.PopupSingleMatch = true;
+			entry_completion.Model = TagManager.Tags;
+			entry_completion.MatchFunc = TagEntryCompletionMatchFunc;
+			entry_completion.MatchSelected += TagEntryCompletionMatchSelected;
+			Gtk.CellRendererText crt = new Gtk.CellRendererText ();
+			entry_completion.PackStart (crt, true);
+			entry_completion.SetCellDataFunc (crt,
+					new Gtk.CellLayoutDataFunc (TagEntryCompletionDataFunc));
+			
+			tag_entry = new Gtk.Entry ();
+			tag_entry.Completion = entry_completion;
+			tag_entry.Activated += TagEntryActivated;
+			tag_entry.Changed += TagEntryChanged;
+			// Bind ESC to close the TagBar if it's open and has focus.
+			tag_entry.KeyPressEvent += KeyPressed;
+			tag_entry.Show ();
+			PackStart (tag_entry, false, false, 0);
+			
+			add_tag_button = new Gtk.Button (String.Empty);
+			add_tag_button.Image = new Gtk.Image (Gtk.Stock.Add, Gtk.IconSize.Menu);
+			add_tag_button.Sensitive = false;
+			add_tag_button.Clicked += AddTagButtonClicked;
+			add_tag_button.Show ();
+			PackStart (add_tag_button, false, false, 0);
+
+			note.TagAdded += TagAddedHandler;
+			note.TagRemoved += TagRemovedHandler;
+		}
+		#endregion
+		
+		#region Private Methods
+		Gtk.Button MakeTagButton (Tag tag)
+		{
+			TagButton button = new TagButton (tag);
+			button.Clicked += TagButtonClicked;
+
+			return button;
+		}
+
+		protected override void OnShown ()
+		{
+			tag_entry.GrabFocus ();
+
+			base.OnShown ();
+		}
+		
+		protected override void OnHidden ()
+		{
+			base.OnHidden ();
+		}
+		
+		#endregion
+
+		#region Event Handlers
+		// <summary>
+		// When the user presses return, call the same method that is called
+		// when the user presses the Add button.
+		// </summary>
+		void TagEntryActivated (object sender, EventArgs args)
+		{
+			AddTagButtonClicked (sender, args);
+		}
+		
+		void TagEntryChanged (object sender, EventArgs args)
+		{
+			string text = tag_entry.Text.Trim ();
+			if (text.Length == 0) {
+				add_tag_button.Sensitive = false;
+			} else {
+				add_tag_button.Sensitive = true;
+			}
+		}
+		
+		void AddTagButtonClicked (object sender, EventArgs args)
+		{
+			string text = tag_entry.Text.Trim ();
+			if (text.Length > 0) {
+				Tag tag = TagManager.GetOrCreateTag (text);
+				if (!note.Data.Tags.ContainsKey (tag.NormalizedName)) {
+					note.AddTag (tag);
+				}
+			}
+			
+			// Clear out the entry and grab the cursor focus so it's ready to
+			// have another tag typed and added.
+			tag_entry.Text = String.Empty;
+		}
+		
+		void TagButtonClicked (object sender, EventArgs args)
+		{
+			TagButton button = sender as TagButton;
+			
+			note.RemoveTag (button.Tag);
+		}
+		
+		void TagAddedHandler (Note note, Tag tag)
+		{
+			Gtk.Button button = MakeTagButton (tag);
+			button.Show ();
+			//tag_cloud.PackStart (button, false, false, 0);
+			tag_cloud.Insert (button);
+		}
+		
+		void TagRemovedHandler (Note note, string tag_name)
+		{
+Logger.Debug ("NoteWindow.TagRemovedHandler: {0}", tag_name);
+			// Remove the button from the list
+			TagButton button_to_remove = null;
+			
+			foreach (Gtk.Widget w in tag_cloud.Children) {
+				if (w is TagButton) {
+					TagButton button = w as TagButton;
+					
+					if (string.Compare (button.Tag.NormalizedName, tag_name) == 0) {
+						button_to_remove = button;
+						button.Hide ();
+						break;
+					}
+				}
+			}
+			
+			if (button_to_remove != null)
+				tag_cloud.Remove (button_to_remove);
+			else
+				Logger.Debug ("\tDid not remove a tag button");
+		}
+		
+		void TagEntryCompletionDataFunc (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell,
+				Gtk.TreeModel tree_model, Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererText crt = cell as Gtk.CellRendererText;
+			Tag tag = tree_model.GetValue (iter, 0) as Tag;
+			crt.Text = tag.Name;
+		}
+		
+		bool TagEntryCompletionMatchFunc (Gtk.EntryCompletion completion, string key, Gtk.TreeIter iter)
+		{
+			if (key.Length == 0)
+				return true;
+			
+			Tag tag = completion.Model.GetValue (iter, 0) as Tag;
+			if (tag == null)
+				return false;
+			
+			string lower_cased_key = key.ToLower ();
+			
+			if (tag.NormalizedName.StartsWith (lower_cased_key))
+				return true;
+			
+			return false;
+		}
+		
+		[GLib.ConnectBefore]
+		void TagEntryCompletionMatchSelected (object sender, Gtk.MatchSelectedArgs args)
+		{
+			Tag tag = args.Model.GetValue (args.Iter, 0) as Tag;
+			if (tag != null) {
+				tag_entry.Text = tag.Name;
+				args.RetVal = true;
+				
+				// Press the add button for the user
+				AddTagButtonClicked (sender, EventArgs.Empty);
+			}
+		}
+		
+		void HideTagBar (object sender, EventArgs args)
+		{
+			Hide ();
+		}
+		
+		void KeyPressed (object sender, Gtk.KeyPressEventArgs args)
+		{
+			args.RetVal = true;
+			
+			switch (args.Event.Key)
+			{
+			case Gdk.Key.Escape:
+				Hide ();
+				break;
+			default:
+				args.RetVal = false;
+				break;
+			}
+		}
+
+		#endregion
+
+		#region Public Methods
+		#endregion
+		
+		#region Properties
+		#endregion
+	}
+	
 	public class NoteFindBar : Gtk.HBox
 	{
 		private Note note;
@@ -924,7 +1202,7 @@
 			note.Buffer.InsertText -= OnInsertText;
 			note.Buffer.DeleteRange -= OnDeleteRange;
 
-			base.OnShown ();
+			base.OnHidden ();
 		}
 		
 		void HideFindBar (object sender, EventArgs args)
Index: Tomboy/NoteManager.cs
===================================================================
--- Tomboy/NoteManager.cs	(revision 1117)
+++ Tomboy/NoteManager.cs	(working copy)
@@ -15,6 +15,7 @@
 		ArrayList notes;
 		PluginManager plugin_mgr;
 		TrieController trie_controller;
+		TagManager tag_mgr;
 		
 		static string start_note_uri = String.Empty;
 
@@ -62,7 +63,7 @@
 			} else {
 				LoadNotes ();
 			}
-
+			
 			Tomboy.ExitingEvent += OnExitingEvent;
 		}
 
@@ -391,7 +392,7 @@
 			}
 			return null;
 		}
-
+		
 		class CompareDates : IComparer
 		{
 			public int Compare (object a, object b)
Index: Tomboy/Note.cs
===================================================================
--- Tomboy/Note.cs	(revision 1117)
+++ Tomboy/Note.cs	(working copy)
@@ -1,6 +1,7 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Xml;
@@ -10,6 +11,9 @@
 {
 	public delegate void NoteRenameHandler (Note sender, string old_title);
 	public delegate void NoteSavedHandler (Note note);
+	public delegate void TagAddedHandler (Note note, Tag tag);
+	public delegate void TagRemovingHandler (Note note, Tag tag);
+	public delegate void TagRemovedHandler (Note note, string tag_name);
 
 	// Contains all pure note data, like the note title and note text.
 	public class NoteData
@@ -23,6 +27,8 @@
 		int cursor_pos;
 		int width, height;
 		int x, y;
+		
+		Dictionary<string, Tag> tags;
 
 		const int noPosition = -1;
 
@@ -32,6 +38,8 @@
 			this.text = "";
 			x = noPosition;
 			y = noPosition;
+			
+			tags = new Dictionary<string, Tag> ();
 		}
 
 		public string Uri
@@ -96,6 +104,11 @@
 			get { return y; }
 			set { y = value; }
 		}
+		
+		public Dictionary<string, Tag> Tags
+		{
+			get { return tags; }
+		}
 
 		public void SetPositionExtent (int x, int y, int width, int height)
 		{
@@ -117,6 +130,11 @@
 		{
 			return width != 0 && height != 0;
 		}
+		
+		public bool HasTags ()
+		{
+			return tags.Count > 0 ? true : false;
+		}
 	}
 
 	// This class wraps a NoteData instance. Most method calls are
@@ -270,6 +288,13 @@
 			this.data = new NoteDataBufferSynchronizer (data);
 			this.filepath = filepath;
 			this.manager = manager;
+			
+			// Make sure each of the tags that NoteData found point to the
+			// instance of this note.
+			foreach (Tag tag in data.Tags.Values) {
+				AddTag (tag);
+			}
+			
 			save_timeout = new InterruptableTimeout ();
 			save_timeout.Timeout += SaveTimeout;
 		}
@@ -436,7 +461,46 @@
 				Logger.Log ("Error while saving: {0}", e);
 			}
 		}
+		
+		public void AddTag (Tag tag)
+		{
+			if (tag == null)
+				throw new ArgumentNullException ("Note.AddTag () called with a null tag.");
+			
+			tag.AddNote (this);
+			
+			if (!data.Data.Tags.ContainsKey (tag.NormalizedName)) {
+				data.Data.Tags [tag.NormalizedName] = tag;
 
+				if (TagAdded != null)
+					TagAdded (this, tag);
+
+				DebugSave ("Tag added, queueing save");
+				QueueSave (false);
+			}
+		}
+		
+		public void RemoveTag (Tag tag)
+		{
+			if (tag == null)
+				throw new ArgumentException ("Note.RemoveTag () called with a null tag.");
+			
+			if (!data.Data.Tags.ContainsKey (tag.NormalizedName))
+				return;
+			
+			if (TagRemoving != null)
+				TagRemoving (this, tag);
+			
+			data.Data.Tags.Remove (tag.NormalizedName);
+			tag.RemoveNote (this);
+			
+			if (TagRemoved != null)
+				TagRemoved (this, tag.NormalizedName);
+
+			DebugSave ("Tag removed, queueing save");
+			QueueSave (false);
+		}
+
 		public string Uri
 		{
 			get { return data.Data.Uri; }
@@ -645,10 +709,18 @@
 				Preferences.Set (Preferences.MENU_PINNED_NOTES, new_pinned);
 			}
 		}
+		
+		public List<Tag> Tags
+		{
+			get { return new List<Tag> (data.Data.Tags.Values); }
+		}
 
 		public event EventHandler Opened;
 		public event NoteRenameHandler Renamed;
 		public event NoteSavedHandler Saved;
+		public event TagAddedHandler TagAdded;
+		public event TagRemovingHandler TagRemoving;
+		public event TagRemovedHandler TagRemoved;
 	}
 
 	// Singleton - allow overriding the instance for easy sensing in
@@ -739,6 +811,14 @@
 					case "y":
 						note.Y = int.Parse (xml.ReadString ());
 						break;
+					case "tags":
+						XmlDocument doc = new XmlDocument ();
+						List<string> tag_strings = ParseTags (doc.ReadNode (xml.ReadSubtree ()));
+						foreach (string tag_str in tag_strings) {
+							Tag tag = TagManager.GetOrCreateTag (tag_str);
+							note.Tags [tag.NormalizedName] = tag;
+						}
+						break;
 					}
 					break;
 				}
@@ -860,10 +940,35 @@
 			xml.WriteStartElement (null, "y", null);
 			xml.WriteString (note.Y.ToString ());
 			xml.WriteEndElement ();
+			
+			if (note.Tags.Count > 0) {
+				xml.WriteStartElement (null, "tags", null);
+				foreach (Tag tag in note.Tags.Values) {
+					xml.WriteStartElement (null, "tag", null);
+					xml.WriteString (tag.Name);
+					xml.WriteEndElement ();
+				}
+				xml.WriteEndElement ();
+			}
 
 			xml.WriteEndElement (); // Note
 			xml.WriteEndDocument ();
 		}
+		
+		// <summary>
+		// Parse the tags from the <tags> element
+		// </summary>
+		List<string> ParseTags (XmlNode tagNodes)
+		{
+			List<string> tags = new List<string> ();
+			
+			foreach (XmlNode node in tagNodes.SelectNodes ("//tag")) {
+				string tag = node.InnerText;
+				tags.Add (tag);
+			}
+			
+			return tags;
+		}
 	}
 
 	public class NoteUtils
Index: Tomboy/Makefile.am
===================================================================
--- Tomboy/Makefile.am	(revision 1117)
+++ Tomboy/Makefile.am	(working copy)
@@ -49,11 +49,15 @@
 	$(srcdir)/PluginManager.cs		\
 	$(srcdir)/Preferences.cs		\
 	$(srcdir)/RecentChanges.cs		\
+	$(srcdir)/Tag.cs				\
+	$(srcdir)/TagButton.cs			\
+	$(srcdir)/TagManager.cs			\
 	$(srcdir)/Tray.cs 			\
 	$(srcdir)/Trie.cs			\
 	$(srcdir)/Undo.cs 			\
 	$(srcdir)/Utils.cs			\
 	$(srcdir)/Watchers.cs			\
+	$(srcdir)/WrapBox.cs			\
 	$(srcdir)/XKeybinder.cs			\
 	\
 	$(srcdir)/panelapplet/*.cs		\
Index: Tomboy/TagButton.cs
===================================================================
--- Tomboy/TagButton.cs	(revision 0)
+++ Tomboy/TagButton.cs	(revision 0)
@@ -0,0 +1,50 @@
+
+using System;
+using Gtk;
+
+namespace Tomboy
+{
+	public class TagButton : Gtk.Button
+	{
+		Image image;
+		Tag tag;
+		
+		public TagButton (Tag tag)
+		{
+			this.tag = tag;
+			this.Label = tag.Name;
+			this.Relief = ReliefStyle.None;
+			this.EnterNotifyEvent += EnterNotifyEventHandler;
+			this.LeaveNotifyEvent += LeaveNotifyEventHandler;
+			
+			image = new Image ();
+			image.Visible = false;
+			image.NoShowAll = true;
+			
+			this.Image = image;
+		}
+		
+		// <summary>
+		// Show the remove image
+		// </summary>
+		void EnterNotifyEventHandler (object sender, EnterNotifyEventArgs args)
+		{
+			image.SetFromStock (Stock.Remove, IconSize.Menu);
+		}
+		
+		// <summary>
+		// Hide the remove image
+		// </summary>
+		void LeaveNotifyEventHandler (object sender, LeaveNotifyEventArgs args)
+		{
+			image.Clear ();
+		}
+		
+		#region Properties
+		public Tag Tag
+		{
+			get { return tag; }
+		}
+		#endregion
+	}
+}
