context: using System;
  using System.Collections.Generic;
  using System.Runtime.InteropServices;
  using System.Text;
  using Mono.Unix;
  using Gtk;
  
  namespace Tomboy
  {
  	public class NoteWindow : ForcedPresentWindow
  	{
  		Note note;
  
  		Gtk.AccelGroup accel_group;
  		Gtk.Toolbar toolbar;
  		Gtk.ToolButton link_button;
  		NoteTextMenu text_menu;
  		Gtk.Menu plugin_menu;
  		Gtk.ImageMenuItem sync_menu_item;
  		Gtk.TextView editor;
  		Gtk.ScrolledWindow editor_window;
  		NoteFindBar find_bar;
  		Gtk.ToolButton delete;
  		Gtk.Box template_widget;
  
  		GlobalKeybinder global_keys;
  		InterruptableTimeout mark_set_timeout;
  
  		//
  		// Construct a window to display a note
  		//
  		// Currently a toolbar with Link, Search, Text, Delete buttons
  		// and a Gtk.TextView as the body.
  		//
  
  		public NoteWindow (Note note)
  			: base (note.Title)
  		{
  			this.note = note;
  			this.IconName = "tomboy";
  			this.SetDefaultSize (450, 360);
  			Resizable = true;
  
  			accel_group = new Gtk.AccelGroup ();
  			AddAccelGroup (accel_group);
  
  			text_menu = new NoteTextMenu (accel_group, note.Buffer, note.Buffer.Undoer);
  
  			// Add the Find menu item to the toolbar Text menu.  It
  			// should only show up in the toplevel Text menu, since
  			// the context menu already has a Find submenu.
  
  			Gtk.SeparatorMenuItem spacer = new Gtk.SeparatorMenuItem ();
  			spacer.Show ();
  			text_menu.Append (spacer);
  
  			Gtk.ImageMenuItem find_item =
  			        new Gtk.ImageMenuItem (Catalog.GetString("Find in This Note"));
  			find_item.Image = new Gtk.Image (Gtk.Stock.Find, Gtk.IconSize.Menu);
  			find_item.Activated += FindActivate;
  			find_item.AddAccelerator ("activate",
  			                          accel_group,
  			                          (uint) Gdk.Key.f,
  			                          Gdk.ModifierType.ControlMask,
  			                          Gtk.AccelFlags.Visible);
  			find_item.Show ();
  			text_menu.Append (find_item);
  
  			plugin_menu = MakePluginMenu ();
  
  			toolbar = MakeToolbar ();
  			toolbar.Show ();
  
  
  			template_widget = MakeTemplateBar ();
  
  			// The main editor widget
  			editor = new NoteEditor (note.Buffer);
  			editor.PopulatePopup += OnPopulatePopup;
  			editor.Show ();
  
  			// Sensitize the Link toolbar button on text selection
  			mark_set_timeout = new InterruptableTimeout();
  			mark_set_timeout.Timeout += UpdateLinkButtonSensitivity;
  			note.Buffer.MarkSet += OnSelectionMarkSet;
  
  			// FIXME: I think it would be really nice to let the
  			//        window get bigger up till it grows more than
  			//        60% of the screen, and then show scrollbars.
  			editor_window = new Gtk.ScrolledWindow ();
  			editor_window.HscrollbarPolicy = Gtk.PolicyType.Automatic;
  			editor_window.VscrollbarPolicy = Gtk.PolicyType.Automatic;
  			editor_window.Add (editor);
  			editor_window.Show ();
  
  			FocusChild = editor;
  
  			find_bar = new NoteFindBar (note);
  			find_bar.Visible = false;
  			find_bar.NoShowAll = true;
  			find_bar.Hidden += FindBarHidden;
  
  			Gtk.VBox box = new Gtk.VBox (false, 2);
  			box.PackStart (toolbar, false, false, 0);
  			box.PackStart (template_widget, false, false, 0);
  			box.PackStart (editor_window, true, true, 0);
  			box.PackStart (find_bar, false, false, 0);
  
  			box.Show ();
  
  			// Don't set up Ctrl-W or Ctrl-N if Emacs is in use
  			bool using_emacs = false;
  			string gtk_key_theme = (string)
  				Preferences.Get ("/desktop/gnome/interface/gtk_key_theme");
  			if (gtk_key_theme != null && gtk_key_theme.CompareTo ("Emacs") == 0)
  				using_emacs = true;
  
  			// NOTE: Since some of our keybindings are only
  			// available in the context menu, and the context menu
  			// is created on demand, register them with the
  			// global keybinder
  			global_keys = new GlobalKeybinder (accel_group);
  
  			// Close window (Ctrl-W)
  			if (!using_emacs)
  				global_keys.AddAccelerator (new EventHandler (CloseWindowHandler),
  				                            (uint) Gdk.Key.w,
  				                            Gdk.ModifierType.ControlMask,
  				                            Gtk.AccelFlags.Visible);
  
  			// Escape has been moved to be handled by a KeyPress Handler so that
  			// Escape can be used to close the FindBar.
  
  			// Close all windows on current Desktop (Ctrl-Q)
  			global_keys.AddAccelerator (new EventHandler (CloseAllWindowsHandler),
  			                            (uint) Gdk.Key.q,
  			                            Gdk.ModifierType.ControlMask,
  			                            Gtk.AccelFlags.Visible);
  
  			// Find Next (Ctrl-G)
  			global_keys.AddAccelerator (new EventHandler (FindNextActivate),
  			                            (uint) Gdk.Key.g,
  			                            Gdk.ModifierType.ControlMask,
  			                            Gtk.AccelFlags.Visible);
  
  			// Find Previous (Ctrl-Shift-G)
  			global_keys.AddAccelerator (new EventHandler (FindPreviousActivate),
  			                            (uint) Gdk.Key.g,
  			                            (Gdk.ModifierType.ControlMask |
  			                             Gdk.ModifierType.ShiftMask),
  			                            Gtk.AccelFlags.Visible);
  
  			// Open Help (F1)
  			global_keys.AddAccelerator (new EventHandler (OpenHelpActivate),
  			                            (uint) Gdk.Key.F1,
  			                            0,
  			                            0);
  
  			// Create a new note
  			if (!using_emacs)
  				global_keys.AddAccelerator (new EventHandler (CreateNewNote),
  				                            (uint) Gdk.Key.n,
  				                            Gdk.ModifierType.ControlMask,
  				                            Gtk.AccelFlags.Visible);
  
  			// Have Esc key close the find bar or note window
  			KeyPressEvent += KeyPressed;
  
  			// Increase Indent
  			global_keys.AddAccelerator (new EventHandler (ChangeDepthRightHandler),
  			                            (uint) Gdk.Key.Right,
  			                            Gdk.ModifierType.Mod1Mask,
  			                            Gtk.AccelFlags.Visible);
  
  			// Decrease Indent
  			global_keys.AddAccelerator (new EventHandler (ChangeDepthLeftHandler),
  			                            (uint) Gdk.Key.Left,
  			                            Gdk.ModifierType.Mod1Mask,
  			                            Gtk.AccelFlags.Visible);
  
  			this.Add (box);
  		}
  
  		protected override bool OnDeleteEvent (Gdk.Event evnt)
  		{
  			Preferences.SettingChanged -= Preferences_SettingChanged;
  
  			CloseWindowHandler (null, null);
  			return true;
  		}
  
  		protected override void OnHidden ()
  		{
  			base.OnHidden ();
  
  			// Workaround Gtk bug, where adding or changing Widgets
  			// while the Window is hidden causes it to be reshown at
  			// 0,0...
  			int x, y;
  			GetPosition (out x, out y);
  			Move (x, y);
  		}
  
  		void KeyPressed (object sender, Gtk.KeyPressEventArgs args)
  		{
  			args.RetVal = true;
  
  			switch (args.Event.Key)
  			{
  			case Gdk.Key.Escape:
  				if (find_bar != null && find_bar.Visible)
  					find_bar.Hide ();
  				else if ((bool) Preferences.Get(Preferences.ENABLE_CLOSE_NOTE_ON_ESCAPE))
  					CloseWindowHandler (null, null);
  				break;
  			default:
  				args.RetVal = false;
  				break;
  			}
  		}
  
  		// FIXME: Need to just emit a delete event, and do this work in
  		// the default delete handler, so that plugins can attach to
  		// delete event and have it always work.
  		void CloseWindowHandler (object sender, EventArgs args)
  		{
  			// Unmaximize before hiding to avoid reopening
  			// pseudo-maximized
  			if ((GdkWindow.State & Gdk.WindowState.Maximized) > 0)
  				Unmaximize ();
  			Hide ();
  		}
  
  		[DllImport("libtomboy")]
  		static extern int tomboy_window_get_workspace (IntPtr win_raw);
  
  		void CloseAllWindowsHandler (object sender, EventArgs args)
  		{
  #if WIN32 || MAC
  			Tomboy.Exit (0);
  #else
  			int workspace = tomboy_window_get_workspace (note.Window.Handle);
  
  			foreach (Note iter in note.Manager.Notes) {
  				if (!iter.IsOpened)
  					continue;
  
  				// Close windows on the same workspace, or all
  				// open windows if no workspace.
  				if (workspace < 0 ||
  				    tomboy_window_get_workspace (iter.Window.Handle) == workspace) {
  					iter.Window.CloseWindowHandler (null, null);
  				}
  			}
  #endif
  		}
  
  		//
  		// Delete this Note.
  		//
  
  		void OnDeleteButtonClicked (object sender, EventArgs args)
  		{
  			// Prompt for note deletion
  			List<Note> single_note_list = new List<Note> (1);
  			single_note_list.Add (note);
  			NoteUtils.ShowDeletionDialog (single_note_list, this);
  		}
  
  		//
  		// Public Children Accessors
  		//
  
  		public Gtk.TextView Editor {
  			get {
  				return editor;
  			}
  		}
  
  		public Gtk.Toolbar Toolbar {
  			get {
  				return toolbar;
  			}
  		}
  
  		/// <summary>
  		/// The Delete Toolbar Button
  		/// </summary>
  		public Gtk.ToolButton DeleteButton
  		{
  			get {
  				return delete;
  			}
  		}
  
  		public Gtk.Menu PluginMenu {
  			get {
  				return plugin_menu;
  			}
  		}
  
  		public Gtk.Menu TextMenu {
  			get {
  				return text_menu;
  			}
  		}
  
  		public Gtk.AccelGroup AccelGroup {
  			get {
  				return accel_group;
  			}
  		}
  
  		//
  		// Sensitize the Link toolbar button on text selection
  		//
  
  		void OnSelectionMarkSet (object sender, Gtk.MarkSetArgs args)
  		{
  			// FIXME: Process in a timeout due to GTK+ bug #172050.
  			mark_set_timeout.Reset (0);
  		}
  
  		void UpdateLinkButtonSensitivity (object sender, EventArgs args)
  		{
  			link_button.Sensitive = (note.Buffer.Selection != null);
  		}
  
  		//
  		// Right-click menu
  		//
  		// Add Undo, Redo, Link, Link To menu, Font menu to the start of
  		// the editor's context menu.
  		//
  		[GLib.ConnectBefore]
  		void OnPopulatePopup (object sender, Gtk.PopulatePopupArgs args)
  		{
  			args.Menu.AccelGroup = accel_group;
  
  			Logger.Debug ("Populating context menu...");
  
  			// Remove the lame-o gigantic Insert Unicode Control
  			// Characters menu item.
  			Gtk.Widget lame_unicode;
  			lame_unicode = (Gtk.Widget)
  			               args.Menu.Children [args.Menu.Children.Length - 1];
  			args.Menu.Remove (lame_unicode);
  
  			Gtk.MenuItem spacer1 = new Gtk.SeparatorMenuItem ();
  			spacer1.Show ();
  
  			Gtk.ImageMenuItem search = new Gtk.ImageMenuItem (
  			        Catalog.GetString ("_Search All Notes"));
  			search.Image = new Gtk.Image (Gtk.Stock.Find, Gtk.IconSize.Menu);
  			search.Activated += SearchActivate;
  			search.AddAccelerator ("activate",
  			                       accel_group,
  			                       (uint) Gdk.Key.f,
  			                       (Gdk.ModifierType.ControlMask |
  			                        Gdk.ModifierType.ShiftMask),
  			                       Gtk.AccelFlags.Visible);
  			search.Show ();
  
  			Gtk.ImageMenuItem link =
  			        new Gtk.ImageMenuItem (Catalog.GetString ("_Link to New Note"));
  			link.Image = new Gtk.Image (Gtk.Stock.JumpTo, Gtk.IconSize.Menu);
  			link.Sensitive = (note.Buffer.Selection != null);
  			link.Activated += LinkToNoteActivate;
  			link.AddAccelerator ("activate",
  			                     accel_group,
  			                     (uint) Gdk.Key.l,
  			                     Gdk.ModifierType.ControlMask,
  			                     Gtk.AccelFlags.Visible);
  			link.Show ();
  
  			Gtk.ImageMenuItem text_item =
  			        new Gtk.ImageMenuItem (Catalog.GetString ("Te_xt"));
  			text_item.Image = new Gtk.Image (Gtk.Stock.SelectFont, Gtk.IconSize.Menu);
  			text_item.Submenu = new NoteTextMenu (accel_group,
  			                                      note.Buffer,
  			                                      note.Buffer.Undoer);
  			text_item.Show ();
  
  			Gtk.ImageMenuItem find_item =
  			        new Gtk.ImageMenuItem (Catalog.GetString ("_Find in This Note"));
  			find_item.Image = new Gtk.Image (Gtk.Stock.Find, Gtk.IconSize.Menu);
  			find_item.Submenu = MakeFindMenu ();
  			find_item.Show ();
  
  			Gtk.MenuItem spacer2 = new Gtk.SeparatorMenuItem ();
  			spacer2.Show ();
  
  			args.Menu.Prepend (spacer1);
  			args.Menu.Prepend (text_item);
  			args.Menu.Prepend (find_item);
  			args.Menu.Prepend (link);
  			args.Menu.Prepend (search);
  
  			Gtk.MenuItem close_all =
  			        new Gtk.MenuItem (Catalog.GetString ("Clos_e All Notes"));
  			close_all.Activated += CloseAllWindowsHandler;
  			close_all.AddAccelerator ("activate",
  			                          accel_group,
  			                          (uint) Gdk.Key.q,
  			                          Gdk.ModifierType.ControlMask,
  			                          Gtk.AccelFlags.Visible);
  			close_all.Show ();
  
  			Gtk.ImageMenuItem close_window =
  			        new Gtk.ImageMenuItem (Catalog.GetString ("_Close"));
  			close_window.Image = new Gtk.Image (Gtk.Stock.Close, Gtk.IconSize.Menu);
  			close_window.Activated += CloseWindowHandler;
  			close_window.AddAccelerator ("activate",
  			                             accel_group,
  			                             (uint) Gdk.Key.w,
  			                             Gdk.ModifierType.ControlMask,
  			                             Gtk.AccelFlags.Visible);
  			close_window.Show ();
  
  			args.Menu.Append (close_all);
  			args.Menu.Append (close_window);
  		}
  
  		//
  		// Toolbar
  		//
  		// Add Link button, Font menu, Delete button to the window's
  		// toolbar.
  		//
  
  		Gtk.Toolbar MakeToolbar ()
  		{
  			Gtk.Toolbar tb = new Gtk.Toolbar ();
  			tb.HasTooltip = true;
  
  			Gtk.ToolButton search = new Gtk.ToolButton (
  				new Gtk.Image (Gtk.Stock.Find, tb.IconSize),
  				Catalog.GetString ("Search"));
  			search.IsImportant = true;
  			search.Clicked += SearchActivate;
  			// TODO: If we ever add a way to customize internal keybindings, this will need to change
  			search.TooltipText = Catalog.GetString ("Search your notes") + " (Ctrl-Shift-F)";
  			search.AddAccelerator ("clicked",
  			                       accel_group,
  			                       (uint) Gdk.Key.f,
  			                       (Gdk.ModifierType.ControlMask |
  			                        Gdk.ModifierType.ShiftMask),
  			                       Gtk.AccelFlags.Visible);
  			search.ShowAll ();
  			tb.Insert (search, -1);
  
  			link_button = new Gtk.ToolButton (
  				new Gtk.Image (Gtk.Stock.JumpTo, tb.IconSize),
  				Catalog.GetString ("Link"));
  			link_button.IsImportant = true;
  			link_button.Sensitive = (note.Buffer.Selection != null);
  			link_button.Clicked += LinkToNoteActivate;
  			// TODO: If we ever add a way to customize internal keybindings, this will need to change
  			link_button.TooltipText = Catalog.GetString ("Link selected text to a new note") + " (Ctrl-L)";
  			link_button.AddAccelerator ("clicked",
  			                            accel_group,
  			                            (uint) Gdk.Key.l,
  			                            Gdk.ModifierType.ControlMask,
  			                            Gtk.AccelFlags.Visible);
  			link_button.ShowAll ();
  			tb.Insert (link_button, -1);
  
  			ToolMenuButton text_button =
  			        new ToolMenuButton (tb,
  			                            Gtk.Stock.SelectFont,
  			                            Catalog.GetString ("_Text"),
  			                            text_menu);
  			text_button.IsImportant = true;
  			text_button.ShowAll ();
  			tb.Insert (text_button, -1);
  			text_button.TooltipText = Catalog.GetString ("Set properties of text");
  
  			ToolMenuButton plugin_button =
  			        new ToolMenuButton (tb,
  			                            Gtk.Stock.Execute,
  			                            Catalog.GetString ("T_ools"),
  			                            plugin_menu);
  			plugin_button.ShowAll ();
  			tb.Insert (plugin_button, -1);
  			plugin_button.TooltipText = Catalog.GetString ("Use tools on this note");
  
  			tb.Insert (new Gtk.SeparatorToolItem (), -1);
  
  			delete = new Gtk.ToolButton (Gtk.Stock.Delete);
  			delete.Clicked += OnDeleteButtonClicked;
  			delete.ShowAll ();
  			tb.Insert (delete, -1);
  			delete.TooltipText = Catalog.GetString ("Delete this note");
  
  			// Don't allow deleting the "Start Here" note...
  			if (note.IsSpecial)
  				delete.Sensitive = false;
  
  			tb.Insert (new Gtk.SeparatorToolItem (), -1);
  
  			sync_menu_item = new Gtk.ImageMenuItem (Catalog.GetString ("Synchronize Notes"));
  			sync_menu_item.Image = new Gtk.Image (Gtk.Stock.Convert, Gtk.IconSize.Menu);
  			sync_menu_item.Activated += SyncItemSelected;
  			sync_menu_item.Show ();
  			PluginMenu.Add (sync_menu_item);
  
  			// We might want to know when various settings are altered.
  			Preferences.SettingChanged += Preferences_SettingChanged;
  
  			// Update items based on configuration.
  			UpdateMenuItems ();
  
  			tb.ShowAll ();
  			return tb;
  		}
  
  		void Preferences_SettingChanged (object sender, EventArgs args)
  		{
  			// Update items based on configuration.
  			UpdateMenuItems ();
  		}
  
  		void UpdateMenuItems ()
  		{
  			// Is synchronization configured and active?
  			string sync_addin_id = Preferences.Get (Preferences.SYNC_SELECTED_SERVICE_ADDIN) as string;
  			sync_menu_item.Sensitive = !string.IsNullOrEmpty (sync_addin_id);
  		}
  
  		void SyncItemSelected (object sender, EventArgs args)
  		{
  			Tomboy.ActionManager ["NoteSynchronizationAction"].Activate ();
  		}
  
  		//
  		// Plugin toolbar menu
  		//
  		// This menu can be
  		// populated by individual plugins using
  		// NotePlugin.AddPluginMenuItem().
  		//
  
  		Gtk.Menu MakePluginMenu ()
  		{
  			Gtk.Menu menu = new Gtk.Menu ();
  			return menu;
  		}
  
  		private Gtk.Box MakeTemplateBar ()
  		{
  			// TODO: Move these to static area
  			Tag template_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSystemTag);
  			Tag template_save_size_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSaveSizeSystemTag);
  			Tag template_save_selection_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSaveSelectionSystemTag);
  			Tag template_save_title_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSaveTitleSystemTag);
  
  			var bar = new Gtk.VBox ();
  
  			var infoLabel  = new Gtk.Label (Catalog.GetString ("This note is a template note. It determines " +
  			                                                   "the default content of regular notes, and will " +
  			                                                   "not show up in the note menu or search window."));
  			infoLabel.Wrap = true;
  
  			var untemplateButton = new Gtk.Button ();
  			untemplateButton.Label = Catalog.GetString ("Convert to regular note");
  			untemplateButton.Clicked += (o, e) => {
  				note.RemoveTag (template_tag);
  			};
  
  			var saveSizeCheckbutton = new Gtk.CheckButton (Catalog.GetString ("Save Si_ze"));
  			saveSizeCheckbutton.Active = note.ContainsTag (template_save_size_tag);
  			saveSizeCheckbutton.Toggled += (o, e) => {
  				if (saveSizeCheckbutton.Active)
  					note.AddTag (template_save_size_tag);
  				else
  					note.RemoveTag (template_save_size_tag);
  			};
  
  			var saveSelectionCheckbutton = new Gtk.CheckButton (Catalog.GetString ("Save Se_lection"));
  			saveSelectionCheckbutton.Active = note.ContainsTag (template_save_selection_tag);
  			saveSelectionCheckbutton.Toggled += (o, e) => {
  				if (saveSelectionCheckbutton.Active)
  					note.AddTag (template_save_selection_tag);
  				else
  					note.RemoveTag (template_save_selection_tag);
  			};
  			
  			var saveTitleCheckbutton = new Gtk.CheckButton (Catalog.GetString ("Save _Title"));
  			saveTitleCheckbutton.Active = note.ContainsTag (template_save_title_tag);
  			saveTitleCheckbutton.Toggled += (o, e) => {
  				if (saveTitleCheckbutton.Active)
  					note.AddTag (template_save_title_tag);
  				else
  					note.RemoveTag (template_save_title_tag);
  			};
  
  			bar.PackStart (infoLabel, true, true, 0);
  			bar.PackStart (untemplateButton, true, true, 0);
  
gt: 			bar.PackStart (saveSizeCheckbutton, true, true, 0);