Index: Tomboy/Defines.cs.in
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Defines.cs.in,v
retrieving revision 1.3
diff -u -p -u -r1.3 Defines.cs.in
--- Tomboy/Defines.cs.in	6 Sep 2006 18:47:51 -0000	1.3
+++ Tomboy/Defines.cs.in	5 Dec 2006 22:10:17 -0000
@@ -1,5 +1,5 @@
-
 using System;
+using System.Reflection;
 
 namespace Tomboy
 {
@@ -13,3 +13,5 @@ namespace Tomboy
 		public const string SYS_PLUGINS_DIR  = "@pkglibdir@/Plugins";
 	}
 }
+
+[assembly: AssemblyInformationalVersion ("@version@")]
Index: Tomboy/PluginManager.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/PluginManager.cs,v
retrieving revision 1.18
diff -u -p -u -r1.18 PluginManager.cs
--- Tomboy/PluginManager.cs	6 Sep 2006 18:47:51 -0000	1.18
+++ Tomboy/PluginManager.cs	5 Dec 2006 22:10:18 -0000
@@ -1,18 +1,147 @@
 
 using System;
-using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using System.IO;
+
+using Mono.Unix;
 using Mono.Unix.Native;
 
 namespace Tomboy
 {
-	public abstract class NotePlugin : IDisposable
+	using PluginTable = IDictionary<Type, NotePlugin>;
+
+	class PluginReference : WeakReference
+	{
+		readonly string description;
+
+		public PluginReference (object plugin, string description) : 
+			base (plugin)
+		{
+			this.description = description;
+		}
+
+		public string Description
+		{
+			get { return description; }
+		}
+	}
+
+	[AttributeUsage(
+		AttributeTargets.Class,
+		AllowMultiple = false, Inherited = false)]
+	public class PluginInfoAttribute : Attribute
+	{
+		string name;
+		string version;
+		string description;
+		string website;
+		string author;
+
+		Type preferencesWidget;
+
+		public string Name
+		{
+			get { return name; }
+			set { name = value; }
+		}
+
+		public string Version
+		{
+			get { return version; }
+			set { version = value; }
+		}
+
+		public string Description
+		{
+			get { return description; }
+			set { description = value; }
+		}
+
+		public string WebSite
+		{
+			get { return website; }
+			set { website = value; }
+		}
+
+		public string Author
+		{
+			get { return author; }
+			set { author = value; }
+		}
+
+		public Type PreferencesWidget
+		{
+			get { return preferencesWidget; }
+			set { preferencesWidget = value; }
+		}
+	}
+
+	[AttributeUsage(
+		AttributeTargets.Class,
+		AllowMultiple = false, Inherited = true)]
+	public class RequiredPlugins: Attribute
+	{
+		readonly string[] pluginNames;
+
+		public RequiredPlugins(params string[] pluginNames)
+		{
+			this.pluginNames = pluginNames;
+		}
+
+		public string[] PluginNames
+		{
+			get { return pluginNames; }
+		}
+	}
+
+	[AttributeUsage(
+		AttributeTargets.Class,
+		AllowMultiple = false, Inherited = true)]
+	public class SuggestedPlugins: Attribute
+	{
+		readonly string[] pluginNames;
+
+		public SuggestedPlugins(params string[] pluginNames)
+		{
+			this.pluginNames = pluginNames;
+		}
+
+		public string[] PluginNames
+		{
+			get { return pluginNames; }
+		}
+	}
+
+	public interface IPlugin : IDisposable
+	{
+	}
+
+	public abstract class AbstractPlugin : IPlugin
+	{
+		~AbstractPlugin ()
+		{
+			Dispose (false);
+		}
+
+		public void Dispose ()
+		{
+			Dispose (true);
+			GC.SuppressFinalize (this);
+		}
+
+		protected virtual void Dispose (bool disposing)
+		{
+		}
+	}
+
+	public abstract class NotePlugin : AbstractPlugin
 	{
 		Note note;
-		ArrayList plugin_menu_items;
-		ArrayList text_menu_items;
+
+		List<Gtk.MenuItem> plugin_menu_items;
+		List<Gtk.MenuItem> text_menu_items;
 
 		public void Initialize (Note note)
 		{
@@ -25,22 +154,23 @@ namespace Tomboy
 				OnNoteOpened ();
 		}
 
-		public void Dispose ()
+		protected override void Dispose (bool disposing)
 		{
-			this.note.Opened -= OnNoteOpenedEvent;
-
-			if (plugin_menu_items != null) {
-				foreach (Gtk.Widget item in plugin_menu_items) {
-					item.Destroy ();
+			if (disposing) {
+				if (plugin_menu_items != null) {
+					foreach (Gtk.Widget item in plugin_menu_items)
+						item.Destroy ();
 				}
-			}
-			if (text_menu_items != null) {
-				foreach (Gtk.Widget item in text_menu_items) {
-					item.Destroy ();
+
+				if (text_menu_items != null) {
+					foreach (Gtk.Widget item in text_menu_items)
+						item.Destroy ();
 				}
+
+				Shutdown ();
 			}
 
-			Shutdown ();
+			note.Opened -= OnNoteOpenedEvent;
 		}
 
 		protected abstract void Initialize ();
@@ -93,7 +223,7 @@ namespace Tomboy
 		public void AddPluginMenuItem (Gtk.MenuItem item)
 		{
 			if (plugin_menu_items == null)
-				plugin_menu_items = new ArrayList ();
+				plugin_menu_items = new List<Gtk.MenuItem> ();
 
 			plugin_menu_items.Add (item);
 
@@ -104,7 +234,7 @@ namespace Tomboy
 		public void AddTextMenuItem (Gtk.MenuItem item)
 		{
 			if (text_menu_items == null)
-				text_menu_items = new ArrayList ();
+				text_menu_items = new List<Gtk.MenuItem> ();
 
 			text_menu_items.Add (item);
 
@@ -117,26 +247,29 @@ namespace Tomboy
 
 	public class PluginManager
 	{
-		string plugins_dir;
-		ArrayList plugin_types;
-		Hashtable plugin_hash;
-		FileSystemWatcher dir_watcher;
-		FileSystemWatcher sys_dir_watcher;
+		readonly string plugins_dir;
+
+		readonly IList<Type> plugin_types;
+		readonly IDictionary<Note, PluginTable> attached_plugins;
+
+		readonly FileSystemWatcher dir_watcher;
+		readonly FileSystemWatcher sys_dir_watcher;
+
+		static bool check_plugin_unloading;
 
 		// Plugins in the tomboy.exe assembly, always loaded.
-		static Type [] stock_plugins = 
-			new Type [] {
-				typeof (NoteRenameWatcher),
-				typeof (NoteSpellChecker),
-				typeof (NoteUrlWatcher),
-				typeof (NoteLinkWatcher),
-				typeof (NoteWikiWatcher),
-				typeof (MouseHandWatcher),
-				
-				// Not ready yet:
-				// typeof (NoteRelatedToWatcher),
-				// typeof (IndentWatcher),
-			};
+		static Type[] stock_plugins = {
+			typeof (NoteRenameWatcher),
+			typeof (NoteSpellChecker),
+			typeof (NoteUrlWatcher),
+			typeof (NoteLinkWatcher),
+			typeof (NoteWikiWatcher),
+			typeof (MouseHandWatcher),
+			
+			// Not ready yet:
+			// typeof (NoteRelatedToWatcher),
+			// typeof (IndentWatcher),
+		};
 
 		public PluginManager (string plugins_dir)
 		{
@@ -164,9 +297,16 @@ namespace Tomboy
 					    Defines.SYS_PLUGINS_DIR, e.Message);
 				sys_dir_watcher = null;
 			}
-			
+
 			plugin_types = FindPluginTypes ();
-			plugin_hash = new Hashtable ();
+			attached_plugins = new Dictionary<Note, PluginTable> ();
+
+		}
+
+		public static bool CheckPluginUnloading
+		{
+			get { return check_plugin_unloading; }
+			set { check_plugin_unloading = value; }
 		}
 
 		// Run file manager for ~/.tomboy/Plugins
@@ -203,24 +343,11 @@ namespace Tomboy
 
 		public void LoadPluginsForNote (Note note)
 		{
-			ArrayList note_plugins = new ArrayList ();
-
 			foreach (Type type in plugin_types) {
-				try {
-					NotePlugin plugin = (NotePlugin) 
-						Activator.CreateInstance (type);
-					if (plugin != null) {
-						plugin.Initialize (note);
-						note_plugins.Add (plugin);
-					}
-				} catch (Exception e) {
-					// Do nothing
-				}
+				if (IsPluginEnabled (type))
+					AttachPlugin (type, note);
 			}
 
-			// Store the plugins for this note
-			plugin_hash [note] = note_plugins;
-
 			// Make sure we remove plugins when a note is deleted
 			note.Manager.NoteDeleted += OnNoteDeleted;
 		}
@@ -301,17 +428,151 @@ namespace Tomboy
 		void OnNoteDeleted (object sender, Note deleted)
 		{
 			// Clean out the plugins for this deleted note.
-			ArrayList note_plugins = (ArrayList) plugin_hash [deleted];
-
-			if (note_plugins != null) {
-				foreach (NotePlugin plugin in note_plugins) {
-					plugin.Dispose ();
+			PluginTable note_plugins = null;
+			
+			if (attached_plugins.TryGetValue (deleted, out note_plugins)) {
+				foreach (NotePlugin plugin in note_plugins.Values) {
+					try {
+						plugin.Dispose ();
+					} catch (Exception e) {
+						Logger.Fatal (
+							"Cannot dispose {0}: {1}", 
+							plugin.GetType(), e);
+					}
 				}
 
 				note_plugins.Clear ();
+				attached_plugins.Remove (deleted);
+			}
+		}
+
+		void AttachPlugin (Type type, Note note)
+		{
+			PluginTable note_plugins;
+
+			if (!attached_plugins.TryGetValue (note, out note_plugins)) {
+				note_plugins = new Dictionary<Type, NotePlugin> ();
+				attached_plugins [note] = note_plugins;
+			}
+
+			if (typeof (NotePlugin).IsAssignableFrom (type)) {
+				NotePlugin plugin;
+
+				try {
+					plugin = (NotePlugin)Activator.CreateInstance (type);
+					plugin.Initialize (note);
+				} catch (Exception e) {
+					Logger.Fatal (
+							"Cannot initialize {0} for note '{1}': {2}", 
+							type, note.Title, e);
+					plugin = null;
+				}
+
+				if (null != plugin)
+					note_plugins[type] = plugin;
+			}
+		}
+
+		void AttachPlugin (Type type)
+		{
+			if (typeof (NotePlugin).IsAssignableFrom (type)) {
+				foreach (Note note in attached_plugins.Keys)
+					AttachPlugin (type, note);
+			}
+		}
+
+		void DetachPlugin (Type type)
+		{
+			List<PluginReference> references;
+			
+			if (CheckPluginUnloading) {
+				references = new List<PluginReference> ();
+			} else {
+				references = null;
+			}
+
+			DetachPlugin (type, references);
+
+			Logger.Debug ("Starting garbage collection...");
+
+			GC.Collect();
+			GC.WaitForPendingFinalizers ();
+
+			Logger.Debug ("Checking plugin references...");
+
+			int finalized = 0, leaking= 0;
+
+			foreach (PluginReference r in references) {
+				object plugin = r.Target;
+
+				if (null != plugin) {
+					Logger.Fatal (
+						"Leaking reference on {0}: '{1}'",
+						plugin, r.Description);
+
+					++leaking;
+				} else {
+					++finalized;
+				}
 			}
 
-			plugin_hash.Remove (deleted);
+			if (leaking > 0) {
+				string heapshot =
+					"http://svn.myrealbox.com/source/trunk/heap-shot";
+				string title = String.Format (
+					Catalog.GetString ("Cannot fully disable {0}."),
+					type);
+				string message = String.Format (Catalog.GetString (
+					"Cannot fully disable {0} as there still are " +
+					"at least {1} reference to this plugin. This " +
+					"indicates a programming error. Contact the " +
+					"plugin's author and report this problem.\n\n" +
+					"<b>Developer Information:</b> This problem " +
+					"usually occurs when the plugin's Dispose " +
+					"method fails to disconnect all event handlers. " +
+					"The heap-shot profiler ({2}) can help to " +
+					"identify leaking references."),
+					type, leaking, heapshot);
+
+				HIGMessageDialog dialog = new HIGMessageDialog (
+					null, 0, Gtk.MessageType.Error, Gtk.ButtonsType.Ok,
+					title, message);
+
+				dialog.Run ();
+				dialog.Destroy ();
+			}
+
+			Logger.Debug ("finalized: {0}, leaking: {1}", finalized, leaking);
+		}
+
+		// Dispose loop has to happen on separate stack frame when debugging,
+		// as otherwise the local variable "plugin" will cause at least one
+		// plugin instance not to beeing garbage collected. Just assigning
+		// null to the variable will not help, as the JIT optimizes this
+		// assignment away.
+		void DetachPlugin (Type type, List<PluginReference> references)
+		{
+			if (typeof (NotePlugin).IsAssignableFrom (type)) {
+				foreach (KeyValuePair<Note, PluginTable> pair in attached_plugins) {
+					NotePlugin plugin = null;
+
+					if (pair.Value.TryGetValue (type, out plugin)) {
+						pair.Value[type] = null;
+						pair.Value.Remove (type);
+
+						try {
+							plugin.Dispose();
+						} catch (Exception e) {
+							Logger.Fatal (
+								"Cannot dispose {0} for note '{1}': {2}", 
+								type, pair.Key.Title, e);
+						}
+
+						if (null != references)
+							references.Add (new PluginReference (plugin, pair.Key.Title));
+					}
+				}
+			}
 		}
 
 		void OnPluginCreated (object sender, FileSystemEventArgs args)
@@ -319,25 +580,14 @@ namespace Tomboy
 			Logger.Log ("Plugin '{0}' Created", 
 					   Path.GetFileName (args.FullPath));
 
-			ArrayList asm_plugins = FindPluginTypesInFile (args.FullPath);
+			IList<Type> asm_plugins = FindPluginTypesInFile (args.FullPath);
 
 			// Add the plugin to the list
+			// and load the added plugin for all existing plugged in notes
 			foreach (Type type in asm_plugins) {
-				plugin_types.Add (type);
-			}
-
-			// Load the added plugin for all existing plugged in notes
-			foreach (Type type in asm_plugins) {
-				foreach (Note note in plugin_hash.Keys) {
-					NotePlugin plugin = (NotePlugin) 
-						Activator.CreateInstance (type);
-					if (plugin == null)
-						continue;
-
-					plugin.Initialize (note);
-
-					ArrayList note_plugins = (ArrayList) plugin_hash [note];
-					note_plugins.Add (plugin);
+				if (IsPluginEnabled (type)) {
+					plugin_types.Add (type);
+					AttachPlugin (type);
 				}
 			}
 
@@ -349,64 +599,147 @@ namespace Tomboy
 			Logger.Log ("Plugin '{0}' Deleted", 
 					   Path.GetFileName (args.FullPath));
 
-			ArrayList kill_list = new ArrayList ();
+			List<Type> kill_list = new List<Type> ();
 
 			// Find the plugins in the deleted assembly
 			foreach (Type type in plugin_types) {
-				if (type.Assembly.Location == args.FullPath) {
+				if (type.Assembly.Location == args.FullPath)
 					kill_list.Add (type);
-				}
 			}
 
 			foreach (Type type in kill_list) {
 				plugin_types.Remove (type);
+				DetachPlugin (type);
 			}
 
-			foreach (Note note in plugin_hash.Keys) {
-				ArrayList note_plugins = (ArrayList) plugin_hash [note];
+			kill_list.Clear ();
+		}
 
-				for (int i = 0; i < note_plugins.Count; i++) {
-					NotePlugin plugin = (NotePlugin) note_plugins [i];
+		public Type[] Plugins
+		{
+			get
+			{
+				List<Type> plugins = new List<Type> (plugin_types.Count);
 
-					if (kill_list.Contains (plugin.GetType ())) {
-						// Allow the plugin to free resources
-						plugin.Dispose ();
-						note_plugins.Remove (plugin);
-					}
+				foreach (Type type in plugin_types) {
+					if (!IsBuiltin (type))
+						plugins.Add (type);
 				}
+
+				return plugins.ToArray ();
 			}
+		}
 
-			kill_list.Clear ();
+		static string[] GetActivePlugins ()
+		{
+			return (string[]) Preferences.Get (Preferences.PLUGINS);
 		}
 
-		ArrayList FindPluginTypes ()
+		public static bool IsBuiltin (Type plugin)
 		{
-			ArrayList all_plugin_types = new ArrayList ();
+			return plugin.Assembly == typeof (PluginManager).Assembly;
+		}
 
-			// Load the stock plugins
-			foreach (Type type in stock_plugins) {
-				all_plugin_types.Add (type);
-			}
+		public bool IsPluginEnabled (Type plugin)
+		{
+			return IsBuiltin (plugin) || Array.IndexOf (GetActivePlugins (), plugin.Name) >= 0;
+		}
+
+		public void SetPluginEnabled (Type plugin, bool enabled)
+		{
+			List<string> active_plugins = new List<string> (GetActivePlugins ());
+			int index = active_plugins.IndexOf (plugin.Name);
+
+			if (enabled != (index >= 0)) {
+				if (enabled) {
+					active_plugins.Add (plugin.Name);
+					AttachPlugin (plugin);
+				} else {
+					active_plugins.RemoveAt (index);
+					DetachPlugin (plugin);
+				}
 
-			// Load system default plugins
-			ArrayList sys_plugin_types;
-			sys_plugin_types = FindPluginTypesInDirectory (Defines.SYS_PLUGINS_DIR);
-			foreach (Type type in sys_plugin_types) {
-				all_plugin_types.Add (type);
+				Preferences.Set (Preferences.PLUGINS, active_plugins.ToArray ());
 			}
+		}
+
+		public static PluginInfoAttribute GetPluginInfo(Type plugin)
+		{
+			return Attribute.GetCustomAttribute (plugin, typeof (PluginInfoAttribute)) as PluginInfoAttribute;
+		}
+
+		public static string GetPluginName (Type type, PluginInfoAttribute info)
+		{
+			string name = null;
+			
+			if (null != info)
+				name = info.Name;
+			if (null == name)
+				name = type.Name;
+
+			return name;
+		}
+
+		public static string GetPluginVersion (Type type, PluginInfoAttribute info)
+		{
+			string version = null;
+			
+			if (null != info)
+				version = info.Version;
 
-			// Load user's plugins
-			ArrayList user_plugin_types = FindPluginTypesInDirectory (plugins_dir);
-			foreach (Type type in user_plugin_types) {
-				all_plugin_types.Add (type);
+			if (null == version) {
+				foreach (Attribute attribute in type.Assembly.GetCustomAttributes (true)) {
+					AssemblyInformationalVersionAttribute productVersion =
+						attribute as AssemblyInformationalVersionAttribute;
+
+					if (null != productVersion) {
+						version = productVersion.InformationalVersion;
+						break;
+					}
+
+					AssemblyFileVersionAttribute fileVersion = 
+						attribute as AssemblyFileVersionAttribute;
+
+					if (null != fileVersion) {
+						version = fileVersion.Version;
+						break;
+					}
+				}
 			}
 
+			if (null == version)
+				version = type.Assembly.GetName().Version.ToString();
+
+			return version;
+		}
+
+		public static string GetPluginName (Type type)
+		{
+			return GetPluginName (type, GetPluginInfo (type));
+		}
+
+		public static Gtk.Widget CreatePreferencesWidget (PluginInfoAttribute info)
+		{
+			if (null != info && null != info.PreferencesWidget)
+				return (Gtk.Widget)Activator.CreateInstance (info.PreferencesWidget);
+
+			return null;
+		}
+
+		IList<Type> FindPluginTypes ()
+		{
+			List<Type> all_plugin_types = new List<Type> ();
+
+			all_plugin_types.AddRange (stock_plugins);
+			all_plugin_types.AddRange (FindPluginTypesInDirectory (Defines.SYS_PLUGINS_DIR));
+			all_plugin_types.AddRange (FindPluginTypesInDirectory (plugins_dir));
+
 			return all_plugin_types;
 		}
 
-		static ArrayList FindPluginTypesInDirectory (string dirpath)
+		static IList<Type> FindPluginTypesInDirectory (string dirpath)
 		{
-			ArrayList dir_plugin_types = new ArrayList ();
+			IList<Type> dir_plugin_types = new List<Type> ();
 			string [] files;
 			
 			try {
@@ -422,7 +755,7 @@ namespace Tomboy
 				Console.Write ("Trying Plugin: {0} ... ", Path.GetFileName (file));
 				
 				try {
-					ArrayList asm_plugins = FindPluginTypesInFile (file);
+					IList<Type> asm_plugins = FindPluginTypesInFile (file);
 					foreach (Type type in asm_plugins) {
 						dir_plugin_types.Add (type);
 					}
@@ -434,20 +767,20 @@ namespace Tomboy
 			return dir_plugin_types;
 		}
 
-		static ArrayList FindPluginTypesInFile (string filepath)
+		static IList<Type> FindPluginTypesInFile (string filepath)
 		{
 			Assembly asm = Assembly.LoadFrom (filepath);
 			return FindPluginTypesInAssembly (asm);
 		}
 
-		static ArrayList FindPluginTypesInAssembly (Assembly asm)
+		static IList<Type> FindPluginTypesInAssembly (Assembly asm)
 		{
+			IList<Type> asm_plugins = new List<Type> ();
 			Type [] types = asm.GetTypes ();
-			ArrayList asm_plugins = new ArrayList ();
 			bool found_one = false;
 
 			foreach (Type type in types) {
-				if (type.IsSubclassOf (typeof (NotePlugin))) {
+				if (typeof (IPlugin).IsAssignableFrom (type)) {
 					Console.Write ("{0}. ", type.FullName);
 					asm_plugins.Add (type);
 					found_one = true;
Index: Tomboy/Preferences.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Preferences.cs,v
retrieving revision 1.15
diff -u -p -u -r1.15 Preferences.cs
--- Tomboy/Preferences.cs	27 Nov 2006 23:12:05 -0000	1.15
+++ Tomboy/Preferences.cs	5 Dec 2006 22:10:19 -0000
@@ -7,6 +7,12 @@ namespace Tomboy
 {
 	public class Preferences
 	{
+		static readonly string[] DefaultPlugins = {
+			"BugzillaPlugin", "EvolutionPlugin", "ExportToHTMLPlugin",
+			"FixedWidthPlugin", "NoteOfTheDayPlugin", "PrintPlugin",
+			"StickyNoteImporter"
+		};
+
 		public const string ENABLE_SPELLCHECKING = "/apps/tomboy/enable_spellchecking";
 		public const string ENABLE_WIKIWORDS = "/apps/tomboy/enable_wikiwords";
 		public const string ENABLE_CUSTOM_FONT = "/apps/tomboy/enable_custom_font";
@@ -15,6 +21,7 @@ namespace Tomboy
 		public const string CUSTOM_FONT_FACE = "/apps/tomboy/custom_font_face";
 		public const string MENU_NOTE_COUNT = "/apps/tomboy/menu_note_count";
 		public const string MENU_PINNED_NOTES = "/apps/tomboy/menu_pinned_notes";
+		public const string PLUGINS = "/apps/tomboy/plugins";
 
 		public const string KEYBINDING_SHOW_NOTE_MENU = "/apps/tomboy/global_keybindings/show_note_menu";
 		public const string KEYBINDING_OPEN_START_HERE = "/apps/tomboy/global_keybindings/open_start_here";
@@ -64,6 +71,9 @@ namespace Tomboy
 			case MENU_PINNED_NOTES:
 				return "";
 
+			case PLUGINS:
+				return DefaultPlugins;
+
 			case KEYBINDING_SHOW_NOTE_MENU:
 				return "<Alt>F12";
 				
@@ -119,17 +129,34 @@ namespace Tomboy
 
 	public class PreferencesDialog : Gtk.Dialog
 	{
+		readonly PluginManager plugin_manager;
+
+		Type current_plugin;
+		PluginInfoAttribute plugin_info;
+
 		Gtk.Button font_button;
 		Gtk.Label font_face;
 		Gtk.Label font_size;
 
-		public PreferencesDialog ()
+		Gtk.ListStore plugin_store;
+		Gtk.Label plugin_name;
+
+		Gtk.TextView plugin_description;
+		Gtk.TextView plugin_author;
+		Gtk.TextView plugin_website;
+		Gtk.TextView plugin_filename;
+
+		Gtk.Button plugin_preferences;
+
+		public PreferencesDialog (PluginManager plugin_manager)
 			: base ()
 		{
+			this.plugin_manager = plugin_manager;
+
 			IconName = "tomboy";
 			HasSeparator = false;
 			BorderWidth = 5;
-			Resizable = false;
+			Resizable = true;
 			Title = Catalog.GetString ("Tomboy Preferences");
 
 			VBox.Spacing = 5;
@@ -144,10 +171,11 @@ namespace Tomboy
 			notebook.Show ();
 
 			notebook.AppendPage (MakeEditingPane (), 
-					     new Gtk.Label (Catalog.GetString ("Editing")));
-
+				new Gtk.Label (Catalog.GetString ("Editing")));
 			notebook.AppendPage (MakeHotkeysPane (), 
-					     new Gtk.Label (Catalog.GetString ("Hotkeys")));
+				new Gtk.Label (Catalog.GetString ("Hotkeys")));
+			notebook.AppendPage (MakePluginsPane (), 
+				new Gtk.Label (Catalog.GetString ("Plugins")));
 
 			VBox.PackStart (notebook, true, true, 0);
 
@@ -391,6 +419,270 @@ namespace Tomboy
 			return hotkeys_list;
 		}
 
+		// Page 3
+		// Plugin Preferences
+		public Gtk.Widget MakePluginsPane ()
+		{
+			Gtk.HPaned pane;
+
+			pane = new Gtk.HPaned ();
+			pane.Pack2 (MakePluginDetails (), true, false);
+			pane.Pack1 (MakePluginList (), false, false);
+			pane.BorderWidth = 6;
+			pane.ShowAll ();
+
+			return pane;
+		}
+
+		Gtk.Widget MakePluginList ()
+		{
+			Gtk.TreeView tree_view;
+			Gtk.CellRendererText caption;
+			Gtk.CellRendererToggle enabled;
+			Gtk.ScrolledWindow scroller;
+
+			Gtk.TreeIter first_plugin;
+
+			plugin_store = new Gtk.ListStore (typeof (Type), typeof (string));
+			plugin_store.SetSortColumnId (1, Gtk.SortType.Ascending);
+			
+			foreach (Type type in plugin_manager.Plugins) {
+				string name = PluginManager.GetPluginName (type);
+				plugin_store.AppendValues (type, name);
+			}
+
+			tree_view = new Gtk.TreeView (plugin_store);
+			tree_view.HeadersVisible = false;
+			tree_view.SetSizeRequest (160, 0);
+			tree_view.Selection.Mode = Gtk.SelectionMode.Browse;
+			tree_view.Selection.Changed += SelectedPluginChanged;
+
+			enabled = new Gtk.CellRendererToggle ();
+			enabled.Toggled += PluginStateToggled;
+			tree_view.AppendColumn (null, enabled, (Gtk.TreeCellDataFunc)GetPluginState);
+			
+			caption = new Gtk.CellRendererText ();
+			caption.Ellipsize = Pango.EllipsizeMode.End;
+			tree_view.AppendColumn (null, caption, "text", 1);
+
+			scroller = new Gtk.ScrolledWindow ();
+			scroller.BorderWidth = 6;
+			scroller.ShadowType = Gtk.ShadowType.In;
+			scroller.SetPolicy (Gtk.PolicyType.Never, Gtk.PolicyType.Automatic);
+			scroller.Add (tree_view);
+
+			if (plugin_store.GetIterFirst (out first_plugin))
+				tree_view.Selection.SelectIter (first_plugin);
+
+			return scroller;
+		}
+
+		void GetPluginState (
+			Gtk.TreeViewColumn column, Gtk.CellRenderer cell,
+			Gtk.TreeModel model, Gtk.TreeIter iter)
+		{
+			Type plugin = (Type)model.GetValue(iter, 0);
+
+			((Gtk.CellRendererToggle)cell).Active =
+				plugin_manager.IsPluginEnabled (plugin); 
+		}
+
+		void PluginStateToggled (object sender, Gtk.ToggledArgs e)
+		{
+			Gtk.TreeIter iter;
+
+			if (plugin_store.GetIter (out iter, new Gtk.TreePath (e.Path))) {
+				Type plugin = (Type)plugin_store.GetValue (iter, 0);
+				bool enabled = plugin_manager.IsPluginEnabled (plugin);
+				plugin_manager.SetPluginEnabled (plugin, !enabled);
+			}
+		}
+
+		string MakePluginTitle ()
+		{
+			return String.Format (
+				"<span size='large' weight='bold'>{0} {1}</span>",
+				PluginManager.GetPluginName (current_plugin, plugin_info),
+				PluginManager.GetPluginVersion (current_plugin, plugin_info));
+		}
+
+		void SelectedPluginChanged (object sender, EventArgs e)
+		{
+			Gtk.TreeSelection selection;
+			Gtk.TreeModel model;
+			Gtk.TreeIter iter;
+			
+			selection = (Gtk.TreeSelection)sender;
+
+			if (selection.GetSelected (out model, out iter)) {
+				current_plugin = (Type)model.GetValue (iter, 0);
+				plugin_info = PluginManager.GetPluginInfo (current_plugin);
+				string description = null, author = null, website = null;
+
+				if (null != plugin_info) {
+					description = plugin_info.Description;
+					website = plugin_info.WebSite;
+					author = plugin_info.Author;
+				}
+
+				plugin_name.Markup = MakePluginTitle ();
+
+				SetPluginDetail (plugin_filename, current_plugin.Assembly.Location);
+				SetPluginDetail (plugin_description, description);
+				SetPluginDetail (plugin_website, website);
+				SetPluginDetail (plugin_author, author);
+
+				plugin_preferences.Sensitive = null != plugin_info && null != plugin_info.PreferencesWidget;
+			} else {
+				current_plugin = null;
+				plugin_info = null;
+			}
+		}
+
+		static void SetPluginDetail (Gtk.TextView detail, string text)
+		{
+			if (null == text || 0 == text.Length)
+				text = Catalog.GetString ("Not available");
+
+			detail.Buffer.Text = text;
+		}
+
+		static Gtk.TextView MakePluginDetail (Gtk.Table table, uint row, string caption)
+		{
+			Gtk.Label label;
+			Gtk.TextView detail;
+
+			label = MakeLabel ("<b>{0}</b>", Catalog.GetString (caption));
+			label.Yalign = 0.0f;
+
+			detail = new Gtk.TextView ();
+			detail.WrapMode = Gtk.WrapMode.WordChar;
+			detail.Editable = false;
+
+			table.Attach (
+				label, 0, 1, row, row + 1,
+				Gtk.AttachOptions.Fill,
+				Gtk.AttachOptions.Fill, 0, 0);
+			table.Attach (
+				detail, 1, 2, row, row + 1, 
+				Gtk.AttachOptions.Fill | Gtk.AttachOptions.Expand, 
+				Gtk.AttachOptions.Fill, 0, 0);
+
+			return detail;
+		}
+
+		Gtk.Widget MakePluginDetails ()
+		{
+			Gtk.Table table;
+			Gtk.ScrolledWindow scroller;
+			Gdk.Color base_color;
+			Gtk.VBox vbox;
+
+			Gtk.HBox action_area;
+			Gtk.Button open_plugin_folder;
+
+			// Name...
+
+			plugin_name = new Gtk.Label ();
+			plugin_name.UseMarkup = true;
+			plugin_name.UseUnderline = false;
+			plugin_name.ModifyBase (Gtk.StateType.Normal, new Gdk.Color (255, 0, 0));
+
+			// Details...
+
+			table = new Gtk.Table (4, 2, false);
+			table.BorderWidth = 6;
+			table.ColumnSpacing = 6;
+			table.RowSpacing = 6;
+
+			plugin_description = MakePluginDetail(
+				table, 0, Catalog.GetString ("Description:"));
+			plugin_author = MakePluginDetail (
+				table, 1, Catalog.GetString ("Written by:"));
+			plugin_website = MakePluginDetail (
+				table, 2, Catalog.GetString ("Web site:"));
+			plugin_filename = MakePluginDetail (
+				table, 3, Catalog.GetString ("File name:"));
+
+			scroller = new Gtk.ScrolledWindow ();
+			scroller.SetPolicy (Gtk.PolicyType.Never, Gtk.PolicyType.Automatic);
+			scroller.AddWithViewport (table);
+
+			plugin_description.EnsureStyle ();
+			base_color = plugin_description.Style.Base (Gtk.StateType.Normal);
+			scroller.Child.ModifyBg (Gtk.StateType.Normal, base_color);
+
+			// Action area...
+
+			plugin_preferences = new Gtk.Button (Gtk.Stock.Preferences);
+			plugin_preferences.Clicked += ShowPluginPreferences;
+
+			open_plugin_folder = new Gtk.Button ("_Open Plugin Folder");
+			open_plugin_folder.Image = new Gtk.Image (Gtk.Stock.Directory, Gtk.IconSize.Button);
+			open_plugin_folder.Clicked += delegate (object sender, EventArgs e) {
+				plugin_manager.ShowPluginsDirectory();
+			};
+
+			action_area = new Gtk.HBox (false, 12);
+			action_area.PackStart (plugin_preferences, false, false, 0);
+			action_area.PackEnd (open_plugin_folder, false, false, 0);
+			action_area.ModifyBase (Gtk.StateType.Normal, new Gdk.Color (0, 255, 0));
+
+			// VBox
+
+			vbox = new Gtk.VBox (false, 6);
+			vbox.PackStart (plugin_name, false, true, 0);
+			vbox.PackStart (scroller, true, true, 0);
+			vbox.PackStart (action_area, false, true, 0);
+			vbox.BorderWidth = 6;
+			vbox.ShowAll ();
+
+			return vbox;
+		}
+
+		void ShowPluginPreferences (object sender, EventArgs e)
+		{
+			Gtk.Image icon = new Gtk.Image (Gtk.Stock.Preferences, Gtk.IconSize.Dialog);
+
+			Gtk.Label caption = new Gtk.Label();
+			caption.Markup = MakePluginTitle ();
+
+			Gtk.Label instructions = new Gtk.Label ();
+			instructions.Text = Catalog.GetString (
+				"Adjust your preferences for this plugin.");
+
+			Gtk.Widget preferences = PluginManager.CreatePreferencesWidget (plugin_info);
+			Gtk.Table table = new Gtk.Table (3, 2, false);
+
+			table.BorderWidth = 6;
+			table.ColumnSpacing = 6;
+			table.RowSpacing = 6;
+
+			table.Attach (icon, 0, 1, 0, 2, 
+				Gtk.AttachOptions.Fill, 
+				Gtk.AttachOptions.Fill, 0, 0);
+			table.Attach (caption, 1, 2, 0, 1,
+				Gtk.AttachOptions.Fill | Gtk.AttachOptions.Expand, 
+				Gtk.AttachOptions.Fill, 0, 0);
+			table.Attach (instructions, 1, 2, 1, 2,
+				Gtk.AttachOptions.Fill | Gtk.AttachOptions.Expand, 
+				Gtk.AttachOptions.Fill, 0, 0);
+			table.Attach (preferences, 0, 2, 2, 3,
+				Gtk.AttachOptions.Fill | Gtk.AttachOptions.Expand, 
+				Gtk.AttachOptions.Fill | Gtk.AttachOptions.Expand, 0, 0);
+			table.ShowAll ();
+
+			Gtk.Dialog dialog = new Gtk.Dialog (
+				Catalog.GetString ("Plugin Preferences"), this,
+				Gtk.DialogFlags.DestroyWithParent | Gtk.DialogFlags.NoSeparator,
+				Gtk.Stock.Close, Gtk.ResponseType.Close);
+
+			dialog.VBox.PackStart (table, false, true, 0);
+
+			dialog.Run ();
+			dialog.Destroy();
+		}
+
 		void SetupPropertyEditor (PropertyEditor peditor)
 		{
 			// Ensure the key exists
@@ -400,9 +692,13 @@ namespace Tomboy
 
 		// Utilities...
 
-		Gtk.Label MakeLabel (string label_text)
+		static Gtk.Label MakeLabel (string label_text, params object[] args)
 		{
+			if (args.Length > 0)
+				label_text = String.Format (label_text, args);
+
 			Gtk.Label label = new Gtk.Label (label_text);
+
 			label.UseMarkup = true;
 			label.Justify = Gtk.Justification.Left;
 			label.SetAlignment (0.0f, 0.5f);
@@ -411,7 +707,7 @@ namespace Tomboy
 			return label;
 		}
 
-		Gtk.CheckButton MakeCheckButton (string label_text)
+		static Gtk.CheckButton MakeCheckButton (string label_text)
 		{
 			Gtk.Label label = MakeLabel (label_text);
 
@@ -422,10 +718,9 @@ namespace Tomboy
 			return check;
 		}
 
-		Gtk.Label MakeTipLabel (string label_text)
+		static Gtk.Label MakeTipLabel (string label_text)
 		{
-			Gtk.Label label =  MakeLabel (String.Format ("<small>{0}</small>", 
-								     label_text));
+			Gtk.Label label =  MakeLabel ("<small>{0}</small>", label_text);
 			label.LineWrap = true;
 			label.Xpad = 20;
 			return label;
@@ -469,3 +764,4 @@ namespace Tomboy
 		}
 	}
 }
+
Index: Tomboy/Tomboy.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Tomboy.cs,v
retrieving revision 1.32
diff -u -p -u -r1.32 Tomboy.cs
--- Tomboy/Tomboy.cs	27 Nov 2006 23:06:17 -0000	1.32
+++ Tomboy/Tomboy.cs	5 Dec 2006 22:10:19 -0000
@@ -27,6 +27,8 @@ namespace Tomboy 
 
 			Initialize ("tomboy", "Tomboy", "tomboy", args);
 
+			PluginManager.CheckPluginUnloading = cmd_line.CheckPluginUnloading;
+
 			// Create the default note manager instance.
 			string note_path = GetNotePath (cmd_line.NotePath);
 			manager = new NoteManager (note_path);
@@ -112,6 +114,7 @@ namespace Tomboy 
 		string note_path;
 		string search_text;
 		bool open_search;
+		bool check_plugin_unloading;
 
 		public TomboyCommandLine (string [] args)
 		{
@@ -138,9 +141,14 @@ namespace Tomboy 
 			get { return note_path; }
 		}
 
+		public bool CheckPluginUnloading
+		{
+			get { return check_plugin_unloading; }
+		}
+
 		public static void PrintAbout () 
 		{
-                        string about = 
+            string about = 
 				Catalog.GetString (
 					"Tomboy: A simple, easy to use desktop note-taking " +
 					"application.\n" +
@@ -158,7 +166,9 @@ namespace Tomboy 
 					"  --version\t\t\tPrint version information.\n" +
 					"  --help\t\t\tPrint this usage message.\n" +
 					"  --note-path [path]\t\tLoad/store note data in this " +
-					"directory.\n");
+					"directory.\n" +
+					"  --check-plugin-unloading\tCheck if plugins are " +
+					"unloaded properly.\n");
 
 #if ENABLE_DBUS
 			usage += 
@@ -178,7 +188,7 @@ namespace Tomboy 
 #endif
 
 			Console.WriteLine (usage);
-                }
+		}
 
 		public static void PrintVersion()
 		{
@@ -279,6 +289,10 @@ namespace Tomboy 
 						quit = true;
 					}
 
+					break;
+
+				case "--check-plugin-unloading":
+					check_plugin_unloading = true;
 					break;
 
 				case "--version":
Index: Tomboy/Tray.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Tray.cs,v
retrieving revision 1.55
diff -u -p -u -r1.55 Tray.cs
--- Tomboy/Tray.cs	22 Nov 2006 09:58:06 -0000	1.55
+++ Tomboy/Tray.cs	5 Dec 2006 22:10:20 -0000
@@ -358,7 +358,7 @@ namespace Tomboy
 		public void ShowPreferences ()
 		{
 			if (prefs_dlg == null) {
-				prefs_dlg = new PreferencesDialog ();
+				prefs_dlg = new PreferencesDialog (manager.PluginManager);
 				prefs_dlg.Response += OnPreferencesResponse;
 			}
 			prefs_dlg.Present ();
Index: Tomboy/Plugins/Bugzilla.cs
===================================================================
RCS file: /cvs/gnome/tomboy/Tomboy/Plugins/Bugzilla.cs,v
retrieving revision 1.3
diff -u -p -u -r1.3 Bugzilla.cs
--- Tomboy/Plugins/Bugzilla.cs	16 Nov 2006 07:05:10 -0000	1.3
+++ Tomboy/Plugins/Bugzilla.cs	5 Dec 2006 22:10:20 -0000
@@ -153,6 +153,21 @@ public class BugzillaLink : DynamicNoteT
 	}
 }
 
+class BugzillaPreferences : Gtk.Label
+{
+	public BugzillaPreferences ()
+		: base ("FIXME: Implement BugzillaPreferences class")
+	{
+	}
+}
+
+[PluginInfo(
+	Name = "Bugzilla Plugin", Version = Defines.VERSION,
+	Author = "David Trowbridge <trowbrds@gmail.com>",
+	Description = "FIXME: Does cool stuff with URLs from Bugzilla.",
+	PreferencesWidget = typeof (BugzillaPreferences)
+	)]
+
 public class BugzillaPlugin : NotePlugin
 {
 	static int last_bug;
Index: data/tomboy.schemas.in
===================================================================
RCS file: /cvs/gnome/tomboy/data/tomboy.schemas.in,v
retrieving revision 1.6
diff -u -p -u -r1.6 tomboy.schemas.in
--- data/tomboy.schemas.in	10 Nov 2006 06:54:20 -0000	1.6
+++ data/tomboy.schemas.in	5 Dec 2006 22:10:20 -0000
@@ -109,6 +109,21 @@
     </schema>
 
     <schema>
+      <key>/schemas/apps/tomboy/plugins</key>
+      <applyto>/apps/tomboy/plugins</applyto>
+      <owner>tomboy</owner>
+      <type>list</type>
+      <list_type>string</list_type>
+      <default>[BugzillaPlugin,EvolutionPlugin,ExportToHTMLPlugin,FixedWidthPlugin,NoteOfTheDayPlugin,PrintPlugin,StickyNoteImporter]</default>
+      <locale name="C">
+         <short>Currently enabled plugins.</short>
+         <long>
+	   List containing the names of currently enabled plugins.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
       <key>/schemas/apps/tomboy/global_keybindings/show_note_menu</key>
       <applyto>/apps/tomboy/global_keybindings/show_note_menu</applyto>
       <owner>tomboy</owner>
