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	30 Nov 2006 22:59:02 -0000
@@ -1,6 +1,6 @@
 
 using System;
-using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using System.IO;
@@ -8,11 +8,114 @@ using Mono.Unix.Native;
 
 namespace Tomboy
 {
-	public abstract class NotePlugin : IDisposable
+	public interface IPlugin : IDisposable
+	{
+		bool Enabled { get; set; }
+
+		string Name { get; }
+		string Version { get; }
+		string Description { get; }
+		string Author { get; }
+		string WebSite { get; }
+		string FileName { get; }
+
+		string[] RequiredPlugins { get; }
+		string[] SuggestedPlugins { get; }
+	}
+
+	/// <summary>
+	/// Implement this interface if your plugin needs user configuration
+	/// from Tomboy's Preferences Dialog.
+	/// </summary>
+	public interface IConfigurablePlugin : IPlugin
+	{
+		/// <summary>
+		/// Returns a Gtk.Widget that will be placed in Tomboy's preferences
+		/// dialog.
+		/// </summary>
+		Gtk.Widget CreatePreferencesWidget ();
+	}
+ 
+	public abstract class AbstractPlugin : IPlugin
+	{
+		protected static readonly string[] NoPlugins = {};
+
+		bool enabled = true;
+
+		~AbstractPlugin ()
+		{
+			Dispose (false);
+		}
+
+		public void Dispose ()
+		{
+			Dispose (true);
+			GC.SuppressFinalize (this);
+		}
+
+		protected virtual void Dispose (bool disposing)
+		{
+		}
+
+		public virtual bool Enabled
+		{
+			get { return enabled; }
+			set { enabled = value; }
+		}
+
+		// Override this property in your plugin to provide a human-readable name.
+		public virtual string Name
+		{
+			get { return GetType ().Name; }
+		}
+
+		// Override this property in your plugin to provide a description.
+		public virtual string Description
+		{
+			get { return String.Empty; }
+		}
+
+		// Override this property in your plugin to provide a version string.
+		public virtual string Version
+		{
+			get { return Defines.VERSION; }
+		}
+
+		// Override this property in your plugin to proivde an author.
+		public virtual string Author
+		{
+			get { return String.Empty; }
+		}
+
+		// Override this property in your plugin to provide a web site.
+		public virtual string WebSite
+		{
+			get { return String.Empty; }
+		}
+
+		// Returns the path to the plugin's assembly.
+		public string FileName
+		{
+			get { return GetType ().Assembly.Location; }
+		}
+
+		public virtual string[] RequiredPlugins 
+		{ 
+			get { return NoPlugins; }
+		}
+
+		public virtual string[] SuggestedPlugins
+		{ 
+			get { return NoPlugins; }
+		}
+	}
+
+	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 +128,25 @@ namespace Tomboy
 				OnNoteOpened ();
 		}
 
-		public void Dispose ()
+		protected override void Dispose (bool disposing)
 		{
-			this.note.Opened -= OnNoteOpenedEvent;
+			if (disposing) {
+				note.Opened -= OnNoteOpenedEvent;
 
-			if (plugin_menu_items != null) {
-				foreach (Gtk.Widget item in plugin_menu_items) {
-					item.Destroy ();
+				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 ();
+			}
 		}
 
 		protected abstract void Initialize ();
@@ -93,7 +199,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 +210,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);
 
@@ -118,8 +224,8 @@ namespace Tomboy
 	public class PluginManager
 	{
 		string plugins_dir;
-		ArrayList plugin_types;
-		Hashtable plugin_hash;
+		IList<Type> plugin_types;
+		IDictionary<Note, IList<NotePlugin>> plugin_hash;
 		FileSystemWatcher dir_watcher;
 		FileSystemWatcher sys_dir_watcher;
 
@@ -166,7 +272,7 @@ namespace Tomboy
 			}
 			
 			plugin_types = FindPluginTypes ();
-			plugin_hash = new Hashtable ();
+			plugin_hash = new Dictionary<Note, IList<NotePlugin>>();
 		}
 
 		// Run file manager for ~/.tomboy/Plugins
@@ -203,18 +309,20 @@ namespace Tomboy
 
 		public void LoadPluginsForNote (Note note)
 		{
-			ArrayList note_plugins = new ArrayList ();
+			List<NotePlugin> note_plugins = new List<NotePlugin> ();
 
 			foreach (Type type in plugin_types) {
-				try {
-					NotePlugin plugin = (NotePlugin) 
-						Activator.CreateInstance (type);
-					if (plugin != null) {
-						plugin.Initialize (note);
-						note_plugins.Add (plugin);
+				if (typeof (NotePlugin).IsAssignableFrom (type)) {
+					try {
+						NotePlugin plugin = (NotePlugin) 
+							Activator.CreateInstance (type);
+						if (plugin != null) {
+							plugin.Initialize (note);
+							note_plugins.Add (plugin);
+						}
+					} catch (Exception e) {
+						// Do nothing
 					}
-				} catch (Exception e) {
-					// Do nothing
 				}
 			}
 
@@ -301,11 +409,17 @@ namespace Tomboy
 		void OnNoteDeleted (object sender, Note deleted)
 		{
 			// Clean out the plugins for this deleted note.
-			ArrayList note_plugins = (ArrayList) plugin_hash [deleted];
+			IList<NotePlugin> note_plugins = plugin_hash [deleted];
 
 			if (note_plugins != null) {
 				foreach (NotePlugin plugin in note_plugins) {
-					plugin.Dispose ();
+					try {
+						plugin.Dispose ();
+					} catch (Exception e) {
+						Logger.Fatal (
+							"Failed to dispose instance of {0}: {1}", 
+							plugin.GetType(), e);
+					}
 				}
 
 				note_plugins.Clear ();
@@ -319,7 +433,7 @@ 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
 			foreach (Type type in asm_plugins) {
@@ -336,7 +450,7 @@ namespace Tomboy
 
 					plugin.Initialize (note);
 
-					ArrayList note_plugins = (ArrayList) plugin_hash [note];
+					IList<NotePlugin> note_plugins = plugin_hash [note];
 					note_plugins.Add (plugin);
 				}
 			}
@@ -349,7 +463,7 @@ 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) {
@@ -363,7 +477,7 @@ namespace Tomboy
 			}
 
 			foreach (Note note in plugin_hash.Keys) {
-				ArrayList note_plugins = (ArrayList) plugin_hash [note];
+				IList<NotePlugin> note_plugins = plugin_hash [note];
 
 				for (int i = 0; i < note_plugins.Count; i++) {
 					NotePlugin plugin = (NotePlugin) note_plugins [i];
@@ -379,9 +493,29 @@ namespace Tomboy
 			kill_list.Clear ();
 		}
 
-		ArrayList FindPluginTypes ()
+		public IList<IPlugin> Plugins
+		{
+			get
+			{
+				List<IPlugin> plugins = new List<IPlugin> ();
+
+				foreach (Type type in plugin_types) {
+					if (type.Assembly != GetType().Assembly) {
+						try {
+							plugins.Add ((IPlugin) Activator.CreateInstance (type));
+						} catch (Exception e) {
+							Logger.Fatal ("Cannot create {0}: {1}", type, e);
+						}
+					}
+				}
+
+				return plugins.AsReadOnly ();
+			}
+		}
+
+		IList<Type> FindPluginTypes ()
 		{
-			ArrayList all_plugin_types = new ArrayList ();
+			IList<Type> all_plugin_types = new List<Type> ();
 
 			// Load the stock plugins
 			foreach (Type type in stock_plugins) {
@@ -389,14 +523,14 @@ namespace Tomboy
 			}
 
 			// Load system default plugins
-			ArrayList sys_plugin_types;
+			IList<Type> sys_plugin_types;
 			sys_plugin_types = FindPluginTypesInDirectory (Defines.SYS_PLUGINS_DIR);
 			foreach (Type type in sys_plugin_types) {
 				all_plugin_types.Add (type);
 			}
 
 			// Load user's plugins
-			ArrayList user_plugin_types = FindPluginTypesInDirectory (plugins_dir);
+			IList<Type> user_plugin_types = FindPluginTypesInDirectory (plugins_dir);
 			foreach (Type type in user_plugin_types) {
 				all_plugin_types.Add (type);
 			}
@@ -404,9 +538,9 @@ namespace Tomboy
 			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 +556,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 +568,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	30 Nov 2006 22:59:02 -0000
@@ -119,17 +119,34 @@ namespace Tomboy
 
 	public class PreferencesDialog : Gtk.Dialog
 	{
+		readonly PluginManager plugin_manager;
+		IPlugin current_plugin;
+
 		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.TextView plugin_requires;
+		Gtk.TextView plugin_suggests;
+
+		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 +161,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 +409,285 @@ 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 (IPlugin));
+			plugin_store.SetSortColumnId (0, Gtk.SortType.Ascending);
+			plugin_store.SetSortFunc (0, ComparePluginsByName);
+			
+			foreach (IPlugin p in plugin_manager.Plugins)
+				plugin_store.AppendValues (p);
+
+			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, (Gtk.TreeCellDataFunc)GetPluginName);
+
+			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)
+		{
+			IPlugin plugin = (IPlugin)model.GetValue(iter, 0);
+			((Gtk.CellRendererToggle)cell).Active = plugin.Enabled;
+		}
+
+		void GetPluginName (
+			Gtk.TreeViewColumn column, Gtk.CellRenderer cell,
+			Gtk.TreeModel model, Gtk.TreeIter iter)
+		{
+			IPlugin plugin = (IPlugin)model.GetValue(iter, 0);
+			((Gtk.CellRendererText)cell).Text = plugin.Name;
+		}
+
+		int ComparePluginsByName (Gtk.TreeModel model, Gtk.TreeIter iter1, Gtk.TreeIter iter2)
+		{
+			IPlugin plugin1 = (IPlugin)model.GetValue (iter1, 0);
+			IPlugin plugin2 = (IPlugin)model.GetValue (iter2, 0);
+			return String.Compare (plugin1.Name, plugin2.Name);
+		}
+
+		void PluginStateToggled (object sender, Gtk.ToggledArgs e)
+		{
+			Gtk.TreeIter iter;
+
+			if (plugin_store.GetIter (out iter, new Gtk.TreePath (e.Path))) {
+				IPlugin plugin = (IPlugin)plugin_store.GetValue (iter, 0);
+				plugin.Enabled = !plugin.Enabled;
+			}
+		}
+
+		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 = (IPlugin)model.GetValue (iter, 0);
+
+				plugin_name.Markup = String.Format (
+					"<span size='large' weight='bold'>{0} {1}</span>",
+					current_plugin.Name, current_plugin.Version);
+
+				SetPluginDetail (plugin_description, current_plugin.Description);
+				SetPluginDetail (plugin_author, current_plugin.Author);
+				SetPluginDetail (plugin_website, current_plugin.WebSite);
+				SetPluginDetail (plugin_filename, current_plugin.FileName);
+				SetPluginDetail (plugin_requires, current_plugin.RequiredPlugins);
+				SetPluginDetail (plugin_suggests, current_plugin.SuggestedPlugins);
+
+				plugin_preferences.Sensitive = current_plugin is IConfigurablePlugin;
+			}
+		}
+
+		static void SetPluginDetail (Gtk.TextView detail, string text)
+		{
+			if (null == text || 0 == text.Length)
+				text = Catalog.GetString ("Not available");
+
+			detail.Buffer.Text = text;
+		}
+
+		static void SetPluginDetail (Gtk.TextView detail, string[] tokens)
+		{
+			string text = String.Join (", ", tokens);
+
+			if (null == text || 0 == text.Length)
+				text = Catalog.GetString ("None");
+
+			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 (6, 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:"));
+			plugin_requires = MakePluginDetail (
+				table, 4, Catalog.GetString ("Requires:"));
+			plugin_suggests = MakePluginDetail (
+				table, 5, Catalog.GetString ("Suggests:"));
+
+			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 = String.Format (
+				"<span size='large' weight='bold'>{0} {1}</span>",
+				current_plugin.Name, current_plugin.Version);
+
+			Gtk.Label instructions = new Gtk.Label ();
+			instructions.Text = Catalog.GetString (
+				"Adjust your preferences for this plugin.");
+
+			Gtk.Widget preferences = 
+				((IConfigurablePlugin) current_plugin).
+				CreatePreferencesWidget();
+
+			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 +697,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 +712,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 +723,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 +769,4 @@ namespace Tomboy
 		}
 	}
 }
+
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	30 Nov 2006 22:59:03 -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	30 Nov 2006 22:59:03 -0000
@@ -153,7 +153,7 @@ public class BugzillaLink : DynamicNoteT
 	}
 }
 
-public class BugzillaPlugin : NotePlugin
+public class BugzillaPlugin : NotePlugin, IConfigurablePlugin
 {
 	static int last_bug;
 
@@ -161,6 +161,26 @@ public class BugzillaPlugin : NotePlugin
 	{
 		last_bug = -1;
 	}
+
+	public override string Name
+	{
+		get { return "Bugzilla Plugin"; }
+	}
+	
+	public override string Description
+	{
+		get { return "FIXME: Does cool stuff with URLs from Bugzilla."; }
+	}
+	
+	public override string Author
+	{
+		get { return "David Trowbridge <trowbrds@gmail.com>"; }
+	}
+	
+	public Gtk.Widget CreatePreferencesWidget ()
+	{
+		return new Gtk.Label ("FIXME: Implement BugzillaPlugin.CreatePreferencesWidget");
+ 	}
 
 	protected override void Initialize ()
 	{
