Index: tasque.mdp
===================================================================
--- tasque.mdp	(revision 219)
+++ tasque.mdp	(working copy)
@@ -1,9 +1,10 @@
-<Project name="tasque" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
+<Project name="tasque" fileversion="2.0" language="C#" clr-version="Net_2_0" targetFramework="2.0" ctype="DotNetProject">
   <Configurations active="Debug">
     <Configuration name="Debug" ctype="DotNetProjectConfiguration">
       <Output directory="bin/Debug" assembly="tasque" />
       <Build debugmode="True" target="Exe" />
       <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <EnvironmentVariables />
       <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
     </Configuration>
     <Configuration name="Release" ctype="DotNetProjectConfiguration">
@@ -45,6 +46,7 @@
     <File name="src/Backends/Rtm/RtmNote.cs" subtype="Code" buildaction="Compile" />
     <File name="src/AllCategory.cs" subtype="Code" buildaction="Compile" />
     <File name="src/TaskCalendar.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskCountHelper.cs" subtype="Code" buildaction="Compile" />
     <File name="src/NoteDialog.cs" subtype="Code" buildaction="Compile" />
     <File name="src/NoteWidget.cs" subtype="Code" buildaction="Compile" />
     <File name="src/Backends/IceCore" subtype="Directory" buildaction="Compile" />
@@ -77,6 +79,12 @@
     <File name="src/Backends/Hiveminder/service/Group.cs" subtype="Code" buildaction="Compile" />
     <File name="src/Backends/Hiveminder/HmPreferencesWidget.cs" subtype="Code" buildaction="Compile" />
     <File name="src/Backends/Hiveminder/service/HiveminderException.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqlitePreferencesWidget.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/ICategoryEditorView.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/CategoryEditorPresenter.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/ICategoryRepository.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/CategoryEditorView.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqliteCategoryRepository.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="glib-sharp, Version=2.10.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
@@ -92,8 +100,7 @@
     <ProjectReference type="Gac" localcopy="True" refto="Mono.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
     <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   </References>
-  <GtkDesignInfo />
-  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am" ExecuteTargetName="run" IsAutotoolsProject="True" RelativeConfigureInPath=".">
+  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am" IsAutotoolsProject="True" RelativeConfigureInPath=".">
     <BuildFilesVar />
     <DeployFilesVar />
     <ResourcesVar />
@@ -102,4 +109,4 @@
     <AsmRefVar />
     <ProjectRefVar />
   </MonoDevelop.Autotools.MakefileInfo>
-</Project>
\ No newline at end of file
+</Project>
Index: src/ICategory.cs
===================================================================
--- src/ICategory.cs	(revision 219)
+++ src/ICategory.cs	(working copy)
@@ -14,6 +14,11 @@
 		{
 			get;
 		}
+
+		int TaskCount
+		{
+			get;
+		}
 		
 		bool ContainsTask(ITask task);
 	}
Index: src/Backends/Sqlite/CategoryEditorPresenter.cs
===================================================================
--- src/Backends/Sqlite/CategoryEditorPresenter.cs	(revision 0)
+++ src/Backends/Sqlite/CategoryEditorPresenter.cs	(revision 0)
@@ -0,0 +1,97 @@
+
+using System;
+
+namespace Tasque.Backends.Sqlite
+{
+	// TODO: Add a CategoryEditorPresenterTest unit test.
+	public class CategoryEditorPresenter : IDisposable
+	{
+#region public methods
+		public CategoryEditorPresenter (ICategoryRepository repo,
+		                                ICategoryEditorView view)
+		{
+			_repo = repo;
+			_view = view;
+
+			_view.SelectionChanged += HandleSelectionChanged;
+			_view.AddActivated += HandleAddActivated;
+			_view.RemoveActivated += HandleRemoveActivated;
+			_view.CategoryRenamed += HandleCategoryRenamed;
+
+			_view.CategoryModel = _repo.CategoryModel;
+			UpdateRemoveSensitivity ();
+		}
+#endregion
+
+#region IDisposable implementation
+		void IDisposable.Dispose ()
+		{
+			if (_view != null) {
+				_view.SelectionChanged -= HandleSelectionChanged;
+				_view.AddActivated -= HandleAddActivated;
+				_view.RemoveActivated -= HandleRemoveActivated;
+			}
+
+			_view = null;
+			_repo = null;
+		}
+#endregion
+
+		
+#region private fields
+		ICategoryRepository _repo;
+		ICategoryEditorView _view;
+#endregion
+
+#region private methods
+		void UpdateRemoveSensitivity ()
+		{
+			_view.RemoveSensitive = (_view.SelectedCategory != null);
+		}
+		
+		void HandleSelectionChanged(object sender, EventArgs e)
+		{
+			UpdateRemoveSensitivity ();
+		}
+
+		void HandleRemoveActivated(object sender, EventArgs e)
+		{
+			ICategory cat = _view.SelectedCategory;
+
+			if (cat == null) {
+				throw new Exception ("Remove cannot be called while there is no selection");
+			}
+
+			if (cat.TaskCount > 0) {
+				_view.ShowUnableToRemoveCategoryWithTasks ();
+				return;
+			}
+			
+			_repo.DeleteCategory (cat);
+		}
+
+		void HandleAddActivated(object sender, EventArgs e)
+		{
+			// TODO: i18n!
+			_repo.CreateCategory ("New Category");
+		}
+
+		
+		void HandleCategoryRenamed(object o, CategoryRenameEventArgs args)
+		{
+			ICategory cat = args.Category;
+			
+			if (cat == null) {
+				throw new Exception ("Can't have a rename event with no category");
+			}
+
+			if (String.IsNullOrEmpty (args.NewName)) {
+				_view.ShowUnableToSetEmptyCategoryName ();
+				return;
+			}
+
+			_repo.RenameCategory (cat, args.NewName);
+		}
+#endregion
+	}
+}
Index: src/Backends/Sqlite/SqliteCategory.cs
===================================================================
--- src/Backends/Sqlite/SqliteCategory.cs	(revision 219)
+++ src/Backends/Sqlite/SqliteCategory.cs	(working copy)
@@ -26,10 +26,19 @@
 				return backend.Database.GetSingleString(command);
 			}
 			set {
-				string command = String.Format("UPDATE Categories set Name='{0}' where ID='{0}'", value, id);
+				string command = String.Format("UPDATE Categories set Name='{0}' where ID='{1}'", value, id);
 				backend.Database.ExecuteScalar(command);
 			}
 		}
+
+
+		public int TaskCount
+		{
+			get {
+				string command = String.Format("SELECT COUNT(ID) FROM Tasks where Category='{0}' AND State IN ('{1}', '{2}')", id, (int)TaskState.Active, (int)TaskState.Inactive);
+				return backend.Database.GetSingleInt(command);
+			}
+		}
 		
 		public string ExternalID
 		{
@@ -38,7 +47,7 @@
 				return backend.Database.GetSingleString(command);
 			}
 			set {
-				string command = String.Format("UPDATE Categories set ExternalID='{0}' where ID='{0}'", value, id);
+				string command = String.Format("UPDATE Categories set ExternalID='{0}' where ID='{1}'", value, id);
 				backend.Database.ExecuteScalar(command);
 			}
 		}
@@ -65,6 +74,12 @@
 
 			return false;
 		}
+
+		public void Delete ()
+		{
+			string command = String.Format("DELETE FROM Categories WHERE ID='{0}'", id);
+			backend.Database.ExecuteScalar(command);
+		}
 		
 	}
 }
Index: src/Backends/Sqlite/CategoryEditorView.cs
===================================================================
--- src/Backends/Sqlite/CategoryEditorView.cs	(revision 0)
+++ src/Backends/Sqlite/CategoryEditorView.cs	(revision 0)
@@ -0,0 +1,190 @@
+
+using System;
+
+using Mono.Unix;
+
+using Gtk;
+
+namespace Tasque.Backends.Sqlite
+{
+	public class CategoryEditorView : Frame, ICategoryEditorView
+	{
+#region public events
+		public event CategoryRenameEventHandler CategoryRenamed;
+		public event EventHandler AddActivated;
+		public event EventHandler RemoveActivated;
+
+		public event EventHandler SelectionChanged;
+#endregion
+
+#region public properties
+		public TreeModel CategoryModel {
+			set {
+				if (value == null) {
+					categories_filter_model = null;
+					categories_tv.Model = null;
+					return;
+				}
+				
+				categories_filter_model
+					= new TreeModelFilter (value, null);
+				categories_filter_model.VisibleFunc
+					= new TreeModelFilterVisibleFunc (CategoryFilterFunc);
+
+				categories_tv.Model = categories_filter_model;
+			}
+		}
+		
+		public ICategory SelectedCategory {
+			get {
+				TreeIter iter;
+				if (!categories_tv.Selection.GetSelected (out iter)) {
+					return null;
+				}
+
+				return (ICategory)categories_tv.Model.GetValue (iter, 0);
+			}
+		}
+		
+		public bool RemoveSensitive {
+			set { remove_button.Sensitive = value; }
+		}
+#endregion
+		
+#region public methods
+		public CategoryEditorView () : base ()
+		{
+			base.BorderWidth = 10;
+			base.ShadowType = ShadowType.None;
+
+			Label frame_label = new Label ();
+			frame_label.Markup = String.Format ("<span size=\"large\" weight=\"bold\">{0}</span>",
+			                                    Catalog.GetString ("Categories"));
+			frame_label.Show ();
+			base.LabelWidget = frame_label;
+
+			Alignment align = new Alignment (0.5f, 0.5f, 1.0f, 1.0f);
+			align.BorderWidth = 6;
+			align.LeftPadding = 10;
+			base.Add (align);
+			
+			VBox vbox = new VBox (false, 6);
+			align.Add (vbox);
+			
+			ScrolledWindow sw = new ScrolledWindow ();
+			sw.SetPolicy (PolicyType.Automatic, PolicyType.Automatic);
+			sw.ShadowType = ShadowType.In;
+			
+			categories_tv = new TreeView ();
+			categories_tv.HeadersVisible = false;
+			CellRendererText text_r = new CellRendererText ();
+			text_r.Editable = true;
+			text_r.Edited += HandleEdited;
+			categories_tv.AppendColumn ("Categories",
+			                            text_r,
+			                            new TreeCellDataFunc (CategoryRender));
+			categories_tv.Selection.Changed += HandleCategorySelectionChanged;
+
+			sw.Add (categories_tv);
+			
+			vbox.PackStart (sw, true, true, 0);
+
+			HButtonBox button_box = new HButtonBox ();
+			
+			Button add_button = new Button (Gtk.Stock.Add);
+			add_button.Clicked += OnAddClicked;
+			
+			remove_button = new Button (Gtk.Stock.Remove);
+			remove_button.Clicked += OnRemoveClicked;
+
+			button_box.PackEnd (add_button, false, false, 0);
+			button_box.PackEnd (remove_button, false, false, 0);
+			
+			HBox button_parent_box = new HBox (false, 0);
+			button_parent_box.PackEnd (button_box, false, false, 0);
+			
+			vbox.PackStart (button_parent_box, false, true, 0);
+
+			align.ShowAll ();
+		}
+
+		public void ShowUnableToRemoveCategoryWithTasks ()
+		{
+			ShowMessage ("Categories with tasks cannot be removed.");
+		}
+
+		public void ShowUnableToSetEmptyCategoryName ()
+		{
+			ShowMessage ("Categories must have a name.");
+		}
+#endregion
+
+#region private fields
+		TreeModelFilter categories_filter_model;
+		TreeView categories_tv;
+		Button remove_button;
+#endregion
+
+#region private methods
+		bool CategoryFilterFunc (TreeModel m, TreeIter i)
+		{
+			ICategory category = (ICategory)m.GetValue (i, 0);
+			if (category == null || category is AllCategory)
+				return false;
+			return true;
+		}
+		
+		void CategoryRender (TreeViewColumn c, CellRenderer r,
+		                     TreeModel m, TreeIter i)
+		{
+			((CellRendererText)r).Text
+				= ((ICategory)m.GetValue (i, 0)).Name;
+		}
+
+		void HandleCategorySelectionChanged(object sender, EventArgs e)
+		{
+			if (SelectionChanged != null) {
+				SelectionChanged (this, EventArgs.Empty);
+			}
+		}
+
+		void HandleEdited(object o, EditedArgs args)
+		{
+			TreeIter iter;
+			if (!categories_tv.Model.GetIter (out iter, new TreePath (args.Path))) {
+				return;
+			}
+
+			ICategory category = (ICategory)categories_tv.Model.GetValue (iter, 0);
+			if (CategoryRenamed != null) {
+				CategoryRenamed (this, new CategoryRenameEventArgs (category, args.NewText));
+			}
+		}
+
+		void OnAddClicked (object o, EventArgs e)
+		{
+			if (AddActivated != null) {
+				AddActivated (this, EventArgs.Empty);
+			}
+		}
+
+		void OnRemoveClicked (object o, EventArgs e)
+		{
+			if (RemoveActivated != null) {
+				RemoveActivated (this, EventArgs.Empty);
+			}
+		}
+
+		void ShowMessage (string msg)
+		{
+			MessageDialog msg_dlg = new MessageDialog (base.Toplevel as Window,
+			                                           DialogFlags.Modal,
+			                                           MessageType.Warning,
+			                                           ButtonsType.Ok,
+			                                           false, msg);
+			msg_dlg.Run ();
+			msg_dlg.Hide ();
+		}
+#endregion
+	}
+}
Index: src/Backends/Sqlite/SqliteCategoryRepository.cs
===================================================================
--- src/Backends/Sqlite/SqliteCategoryRepository.cs	(revision 0)
+++ src/Backends/Sqlite/SqliteCategoryRepository.cs	(revision 0)
@@ -0,0 +1,49 @@
+
+using System;
+
+namespace Tasque.Backends.Sqlite
+{
+	public class SqliteCategoryRepository : ICategoryRepository
+	{
+		public Gtk.TreeModel CategoryModel {
+			get { return _backend.Categories; }
+		}
+		
+		public SqliteCategoryRepository (SqliteBackend backend)
+		{
+			_backend = backend;
+		}
+
+		public ICategory CreateCategory (string name)
+		{
+			ICategory cat = new SqliteCategory (_backend, name);
+			_backend.RefreshCategories ();
+			return cat;
+		}
+		
+		public void RenameCategory (ICategory category, string new_name)
+		{
+			SqliteCategory sql_category = category as SqliteCategory;
+			if (sql_category == null) {
+				throw new ArgumentException ("Cannot rename categories other than SqliteCategory objects.");
+			}
+
+			sql_category.Name = new_name;
+			_backend.RefreshCategories ();
+		}
+		
+		public void DeleteCategory (ICategory category)
+		{
+			SqliteCategory sql_category = category as SqliteCategory;
+			if (sql_category == null) {
+				throw new ArgumentException ("Cannot rename categories other than SqliteCategory objects.");
+			}
+
+			// TODO: What now?
+			sql_category.Delete ();
+			_backend.RefreshCategories ();
+		}
+
+		SqliteBackend _backend;
+	}
+}
Index: src/Backends/Sqlite/ICategoryRepository.cs
===================================================================
--- src/Backends/Sqlite/ICategoryRepository.cs	(revision 0)
+++ src/Backends/Sqlite/ICategoryRepository.cs	(revision 0)
@@ -0,0 +1,18 @@
+
+using System;
+
+namespace Tasque.Backends.Sqlite
+{
+	public interface ICategoryRepository
+	{
+#region properties
+		Gtk.TreeModel CategoryModel { get; }
+#endregion
+		
+#region methods
+		ICategory CreateCategory (string name);
+		void RenameCategory (ICategory category, string new_name);
+		void DeleteCategory (ICategory category);
+#endregion
+	}
+}
Index: src/Backends/Sqlite/SqlitePreferencesWidget.cs
===================================================================
--- src/Backends/Sqlite/SqlitePreferencesWidget.cs	(revision 0)
+++ src/Backends/Sqlite/SqlitePreferencesWidget.cs	(revision 0)
@@ -0,0 +1,31 @@
+
+using System;
+using Mono.Unix;
+using Gtk;
+
+namespace Tasque.Backends.Sqlite
+{
+	public class SqlitePreferencesWidget : Gtk.VBox
+	{
+#region public methods
+		public SqlitePreferencesWidget (SqliteBackend backend)
+		{
+			CategoryEditorView cat_editor = new CategoryEditorView ();
+			ICategoryRepository repo = new SqliteCategoryRepository (backend);
+			presenter = new CategoryEditorPresenter (repo, cat_editor);
+			base.PackStart (cat_editor, true, true, 0);
+			base.ShowAll ();
+		}
+
+		public override void Dispose ()
+		{
+			(presenter as IDisposable).Dispose ();
+			base.Dispose ();
+		}
+#endregion
+
+#region private fields
+		private CategoryEditorPresenter presenter;
+#endregion
+	}
+}
Index: src/Backends/Sqlite/ICategoryEditorView.cs
===================================================================
--- src/Backends/Sqlite/ICategoryEditorView.cs	(revision 0)
+++ src/Backends/Sqlite/ICategoryEditorView.cs	(revision 0)
@@ -0,0 +1,46 @@
+
+using System;
+
+namespace Tasque.Backends.Sqlite
+{
+	public class CategoryRenameEventArgs : EventArgs
+	{
+		public ICategory Category { get { return _cat; } }
+		public string NewName { get { return _new_name; } }
+		
+		public CategoryRenameEventArgs (ICategory category,
+		                                string new_name)
+		{
+			_cat = category;
+			_new_name = new_name;
+		}
+
+		readonly ICategory _cat;
+		readonly string _new_name;
+	}
+
+	public delegate void CategoryRenameEventHandler (
+	                                object o, CategoryRenameEventArgs args);
+	
+	public interface ICategoryEditorView
+	{
+#region events
+		event CategoryRenameEventHandler CategoryRenamed;
+		event EventHandler AddActivated;
+		event EventHandler RemoveActivated;
+
+		event EventHandler SelectionChanged;
+#endregion
+		
+#region properties
+		Gtk.TreeModel CategoryModel { set; }
+		ICategory SelectedCategory { get; }
+		bool RemoveSensitive { set; }
+#endregion
+
+#region methods
+		void ShowUnableToRemoveCategoryWithTasks ();
+		void ShowUnableToSetEmptyCategoryName ();
+#endregion
+	}
+}
Index: src/Backends/Sqlite/SqliteBackend.cs
===================================================================
--- src/Backends/Sqlite/SqliteBackend.cs	(revision 219)
+++ src/Backends/Sqlite/SqliteBackend.cs	(working copy)
@@ -128,7 +128,7 @@
 				db = new Database();
 				
 			db.Open();
-			
+
 			//
 			// Add in the "All" Category
 			//
@@ -136,7 +136,6 @@
 			Gtk.TreeIter iter = categoryListStore.Append ();
 			categoryListStore.SetValue (iter, 0, allCategory);
 			
-			
 			RefreshCategories();
 			RefreshTasks();		
 
@@ -163,7 +162,7 @@
 		{
 			// TODO: Replace this with returning null once things are going
 			// so that the Preferences Dialog doesn't waste space.
-			return new Gtk.Label ("Local file requires no configuration.");
+			return new SqlitePreferencesWidget (this);
 		}
 
 		/// <summary>
@@ -243,23 +242,81 @@
 			bool hasValues = false;
 			
 			string command = "SELECT id FROM Categories";
-        	SqliteCommand cmd = db.Connection.CreateCommand();
-        	cmd.CommandText = command;
-        	SqliteDataReader dataReader = cmd.ExecuteReader();
-        	while(dataReader.Read()) {
-			    int id = dataReader.GetInt32(0);
+			SqliteCommand cmd = db.Connection.CreateCommand();
+			cmd.CommandText = command;
+			SqliteDataReader dataReader = cmd.ExecuteReader();
+
+			List<int> sql_ids = new List<int> ();
+
+			while(dataReader.Read()) {
+				int id = dataReader.GetInt32(0);
+				sql_ids.Add (id);
+
+				bool found_existing_id = false;
+				iter = Gtk.TreeIter.Zero;
+				if (categoryListStore.GetIterFirst (out iter)) {
+					do {
+						ICategory cat = (ICategory)categoryListStore.GetValue (iter, 0);
+						if (cat == null || cat is AllCategory) {
+							continue;
+						}
+
+						SqliteCategory sql_cat = cat as SqliteCategory;
+						if (sql_cat == null) {
+							continue;
+						}
+
+						if (sql_cat.ID == id) {
+							found_existing_id = true;
+							break;
+						}
+					} while (categoryListStore.IterNext (ref iter));
+				}
 				hasValues = true;
+
+				if (!found_existing_id) {
+					iter = categoryListStore.Append ();
+				}
 				
 				newCategory = new SqliteCategory (this, id);
-				if( (defaultCategory == null) || (newCategory.Name.CompareTo("Work") == 0) )
+				if ((defaultCategory == null) ||
+				    (newCategory.Name.CompareTo("Work") == 0))
 					defaultCategory = newCategory;
-				iter = categoryListStore.Append ();
-				categoryListStore.SetValue (iter, 0, newCategory);				
-        	}
+				
+				categoryListStore.SetValue (iter, 0, newCategory);
+			}
 
-        	dataReader.Close();
-        	cmd.Dispose();
+			dataReader.Close();
+			cmd.Dispose();
 
+			// We now iterate to remove any categories we didn't
+			// find while iterating the items from the DB.
+			if (categoryListStore.GetIterFirst (out iter)) {
+				bool successful_remove = false;
+				do {
+					successful_remove = false;
+					ICategory cat = (ICategory)categoryListStore.GetValue (iter, 0);
+					if (cat == null || cat is AllCategory) {
+						continue;
+					}
+
+					SqliteCategory sql_cat = cat as SqliteCategory;
+					if (sql_cat == null) {
+						continue;
+					}
+
+					if (!sql_ids.Contains (sql_cat.ID)) {
+						// Little known fact, if Gtk.ListStore.Remove
+						// returns false, it means the new value of
+						// iter is invalid.
+						successful_remove = categoryListStore.Remove (ref iter);
+						if (!successful_remove) {
+							break;
+						}
+					}
+				} while (successful_remove || categoryListStore.IterNext (ref iter));
+			}
+
 			if(!hasValues)
 			{
 				defaultCategory = newCategory = new SqliteCategory (this, "Work");
@@ -288,21 +345,21 @@
 			bool hasValues = false;
 			
 			string command = "SELECT id FROM Tasks";
-        	SqliteCommand cmd = db.Connection.CreateCommand();
-        	cmd.CommandText = command;
-        	SqliteDataReader dataReader = cmd.ExecuteReader();
-        	while(dataReader.Read()) {
-			    int id = dataReader.GetInt32(0);
+			SqliteCommand cmd = db.Connection.CreateCommand();
+			cmd.CommandText = command;
+			SqliteDataReader dataReader = cmd.ExecuteReader();
+			while(dataReader.Read()) {
+				int id = dataReader.GetInt32(0);
 				hasValues = true;
 				
 				newTask = new SqliteTask(this, id);
 				iter = taskStore.AppendNode();
 				taskStore.SetValue (iter, 0, newTask);
 				taskIters [newTask.SqliteId] = iter;
-        	}
+			}
 
-        	dataReader.Close();
-        	cmd.Dispose();
+			dataReader.Close();
+			cmd.Dispose();
 
 			if(!hasValues)
 			{
Index: src/Backends/Rtm/RtmCategory.cs
===================================================================
--- src/Backends/Rtm/RtmCategory.cs	(revision 219)
+++ src/Backends/Rtm/RtmCategory.cs	(working copy)
@@ -14,6 +14,7 @@
 	{
 		private List list;
 		private Gtk.TreeIter iter;
+		private TaskCountHelper _count_helper = new TaskCountHelper (Application.Backend);
 
 		public RtmCategory(List list)
 		{
@@ -25,6 +26,13 @@
 			get { return list.Name; }
 		}
 
+		public int TaskCount
+		{
+			get {
+				return _count_helper.CountTasksInCategory (this);
+			}
+		}
+
 		public string ID
 		{
 			get { return list.ID; }
Index: src/Backends/IceCore/IceCategory.cs
===================================================================
--- src/Backends/IceCore/IceCategory.cs	(revision 219)
+++ src/Backends/IceCore/IceCategory.cs	(working copy)
@@ -11,6 +11,7 @@
 		private Teamspace team;
 		private TaskFolder folder;
 		private string id;
+		private TaskCountHelper _count_helper = new TaskCountHelper (Application.Backend);
 		
 		public IceCategory(Teamspace iceTeam, TaskFolder iceFolder)
 		{
@@ -28,6 +29,11 @@
 		{
 			get { return team.Name; }
 		}
+
+		public int TaskCount
+		{
+			get { return _count_helper.CountTasksInCategory (this); }
+		}
 		
 		public bool ContainsTask(ITask task)
 		{
Index: src/Backends/Hiveminder/HmCategory.cs
===================================================================
--- src/Backends/Hiveminder/HmCategory.cs	(revision 219)
+++ src/Backends/Hiveminder/HmCategory.cs	(working copy)
@@ -37,12 +37,20 @@
 	public class HmCategory : ICategory
 	{
 		Group group;
+		TaskCountHelper _count_helper = new TaskCountHelper (Application.Backend);
 		
 		public string Name
 		{
 			get { return group.Name; }
 		}
 
+		public int TaskCount
+		{
+			get {
+				return _count_helper.CountTasksInCategory (this);
+			}
+		}
+
 		public string Id
 		{
 			get { return group.Id; }
Index: src/Backends/Dummy/DummyCategory.cs
===================================================================
--- src/Backends/Dummy/DummyCategory.cs	(revision 219)
+++ src/Backends/Dummy/DummyCategory.cs	(working copy)
@@ -25,6 +25,13 @@
 			}
 		}
 
+		public int TaskCount
+		{
+			get {
+				return _count_helper.CountTasksInCategory (this);
+			}
+		}
+
 		public bool ContainsTask(ITask task)
 		{
 			if(task.Category is DummyCategory)
@@ -32,6 +39,8 @@
 			else
 				return false;
 		}
+
+		TaskCountHelper _count_helper = new TaskCountHelper (Application.Backend);
 		
 	}
 }
Index: src/Backends/EDS/EDSCategory.cs
===================================================================
--- src/Backends/EDS/EDSCategory.cs	(revision 219)
+++ src/Backends/EDS/EDSCategory.cs	(working copy)
@@ -17,6 +17,7 @@
                private string uid;
                private Evolution.Cal taskList;
 	       private bool isSystem;
+	       private TaskCountHelper _count_helper = new TaskCountHelper (Application.Backend);
 
                public EDSCategory (Evolution.Source source, Evolution.Cal taskList)
                {
@@ -47,6 +48,11 @@
                        get { return name; }
                }
 
+	       public int TaskCount
+	       {
+	       	       get { return _count_helper.CountTasksInCategory (this); }
+	       }
+
                public string UID
                {
                        get { return uid; }
Index: src/AllCategory.cs
===================================================================
--- src/AllCategory.cs	(revision 219)
+++ src/AllCategory.cs	(working copy)
@@ -27,6 +27,13 @@
 		{
 			get { return Catalog.GetString ("All"); }
 		}
+
+		public int TaskCount
+		{
+			get {
+				return Application.Backend.Tasks.IterNChildren ();
+			}
+		}
 		
 		public bool ContainsTask(ITask task)
 		{
Index: src/TaskCountHelper.cs
===================================================================
--- src/TaskCountHelper.cs	(revision 0)
+++ src/TaskCountHelper.cs	(revision 0)
@@ -0,0 +1,45 @@
+using System;
+using Tasque.Backends;
+
+namespace Tasque
+{
+	public class TaskCountHelper
+	{
+		public TaskCountHelper (IBackend backend)
+		{
+			_backend = backend;
+		}
+
+		public int CountTasksInCategory (ICategory category)
+		{
+			if (category == null) {
+				throw new ArgumentNullException ("category");
+			}
+
+			int count = 0;
+		
+			Gtk.TreeIter iter;
+			Gtk.TreeModel model = _backend.Tasks;
+			
+			if (!model.GetIterFirst (out iter))
+				return 0;
+			
+			do {
+				ITask task = model.GetValue (iter, 0) as ITask;
+				if (task == null)
+					continue;
+				if (task.State != TaskState.Active
+						&& task.State != TaskState.Inactive)
+					continue;
+				
+				if (category.ContainsTask (task))
+					count++;
+			} while (model.IterNext (ref iter));
+			
+			return count;
+		}
+
+		IBackend _backend;
+	}
+}
+
Index: src/TaskWindow.cs
===================================================================
--- src/TaskWindow.cs	(revision 219)
+++ src/TaskWindow.cs	(working copy)
@@ -640,39 +640,12 @@
 			if(category != null && category.Name != null) {
 				crt.Text =
 					string.Format ("{0} ({1})",
-								   category.Name,
-								   GetTaskCountInCategory (category));
+					               category.Name,
+					               category.TaskCount);
 			} else
 				crt.Text = "unknown";
 		}
 		
-		// TODO: Move this method into a property of ICategory.TaskCount
-		private int GetTaskCountInCategory (ICategory category)
-		{
-			// This is disgustingly inefficient, but, oh well
-			int count = 0;
-			
-			Gtk.TreeIter iter;
-			Gtk.TreeModel model = Application.Backend.Tasks;
-			
-			if (!model.GetIterFirst (out iter))
-				return 0;
-			
-			do {
-				ITask task = model.GetValue (iter, 0) as ITask;
-				if (task == null)
-					continue;
-				if (task.State != TaskState.Active
-						&& task.State != TaskState.Inactive)
-					continue;
-				
-				if (category.ContainsTask (task))
-					count++;
-			} while (model.IterNext (ref iter));
-			
-			return count;
-		}
-		
 		/// <summary>
 		/// Search through the TaskGroups looking for the specified task and:
 		/// 1) scroll the window to its location, 2) enter directly into edit
Index: src/Makefile.am
===================================================================
--- src/Makefile.am	(revision 219)
+++ src/Makefile.am	(working copy)
@@ -80,6 +80,7 @@
 	$(srcdir)/RemoteControl.cs \
 	$(srcdir)/RemoteControlProxy.cs \
 	$(srcdir)/TaskCalendar.cs \
+	$(srcdir)/TaskCountHelper.cs \
 	$(srcdir)/TaskGroup.cs \
 	$(srcdir)/TaskPriority.cs \
 	$(srcdir)/TaskState.cs \
