Click here to Skip to main content
15,886,110 members
Articles / Security / Encryption

Encrypting Editor Notepad Replacement

Rate me:
Please Sign up or sign in to vote.
4.85/5 (50 votes)
28 Jul 2014CPOL80 min read 177.2K   6.1K   112  
A C# .NET 3.5 (Win7) Windows Forms Application with source code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using ISO8601_Class;
using Messagebox_Class;
using SI_Class;

namespace CryptPad
{
	/// <summary> NotePad similar but optionally encrypts file contents
	/// </summary>
	public partial class CryptPad : Form
	{
		#region Program data
		public const string CRYPTPAD_URL = "http://www.frank-t-clark.com/Professional/Papers/CryptPad/CryptPad.html";
		// assume no file saved
		public static int errorlevel = 1;
		public bool automation = false;
		public bool automation_script = false;
		public int automation_script_line = 0;
		string original_CurrentDirectory;
		// default file extension
		const string sSafe = ".safe";
		const string encryption_prefix = "~!@#";
		const string encryption_prefix2 = "~!@#$";
		//Hold the file name while open for the save action
		private string current_file_name;
		// hold the FileStream while locked open for the save/discard
		private FileStream file_stream;
		// does the file need backup
		private bool backup;
		//Hold the string/regex for the find replace actions
		public Regex find_regex;
		public Regex findr_regex;
		public string find_string;	// Escaped version needed when using regex!
		public string replace_string;	// ditto but not actually required
		public bool checkBoxMatchCase;
		// current encryption string
		public string encryption_string;
		public uint encryption_checksum;
		public int random_seed;
		/// <summary> basic forced read encodings </summary>
		private Encoding[] base_read_encodings =
		{
			// Binary use ANSI default codepage
			Encoding.GetEncoding (0),
			// ANSI default codepage
			Encoding.GetEncoding (0),
			// standard UTF-8 encoding
			new	UTF8Encoding (),
			// LE
			new UnicodeEncoding(),
			// BE
			new UnicodeEncoding(true,false),
		};
		/// <summary> basic auto read and write encodings </summary>
		private Encoding[] base_write_encodings =
		{
			null,	// Binary
			// ANSI default codepage with exceptions for writing Unicode
			Encoding.GetEncoding (0, EncoderExceptionFallback.ExceptionFallback,
				DecoderExceptionFallback.ExceptionFallback),
			// standard UTF-8 encoding, false BOM, true exception for reading ANSI
			new	UTF8Encoding (false,true),
			// true BOM
			new	UTF8Encoding (true,true),
			// false big endian byte order, true byte order mark, true exception.
			new UnicodeEncoding(false,false,true),
			new UnicodeEncoding(false,true,true),
			new UnicodeEncoding(true,false,true),
			new UnicodeEncoding(true,true,true),
		};
		private Encoding saved_encoding;
		/// <summary> enum to match base_write_encodings </summary>
		public enum Encodes
		{
			Binary,
			ANSI,
			UTF_8_ASCII,
			Unicode_UTF_8,
			Unicode,
			Unicode_BOM,
			Unicode_Big_Endian,
			Unicode_Big_Endian_BOM
		};
		private Encodes saved_encoding_enum;
		/// <summary> descriptions to match base_write_encodings </summary>
		private string[] encodings =
		{
			"Binary",
			"ANSI",
			"UTF-8 (ASCII)",
			"Unicode (UTF-8)",
			"Unicode",
			"Unicode (BOM)",
			"Unicode (Big-Endian)",
			"Unicode (Big-Endian BOM)",
		};
		/// <summary> items always used together to remember caret location in a window </summary>
		public struct Remember
		{
			public int startline;
			public int startcol;
			public int startpoint;
		}
		/// <summary> hold the remembered position </summary>
		Remember remember_start = new Remember ();
		// status bar update delayed until after keystroke processed
		public Timer timerStatus = new System.Windows.Forms.Timer ();
		/// <summary> A Settings shortcut </summary>
		Properties.Settings Settings = Properties.Settings.Default;
		/// <summary> combobox.items initializations used by find and replace </summary>
		public string[] combo_search = { "Insert a search pattern." };
		public string[] combo_replacement = { "Insert a replacement pattern." };
		public string[] combo_base = {
			"\\r\\n Carriage Return, New Line",
			"\\t Tab",
			"\\\\ Backslash"};
		public string[] combo_regex_find = {
			". Any character" ,
			"* zero or more times",
			"+ one or more times",
			"? zero or one time",
			"^ beginning of line",
			"$ end of line",
			"\\w Any word character",
			"\\W Any non-word character",
			"\\s Any white-space character",
			"\\S Any non-white-space ",
			"\\d any decimal digit",
			"\\D any non-decimal digit",
			"[] Character group",
			"[^] Not Character group",
			"[a-z] Character range",
			"() Captures subexpression",
			};
		public string[] combo_regex_replace = { "$1 Substitutes subexpression 1" };
		#endregion
		#region Program Startup
		/// <summary> The main entry point for the application.
		/// </summary>
		[STAThread]
		static int Main (string[] args)
		{
			Application.Run (new CryptPad ());
			return errorlevel;
		}
		#endregion
		#region Form code
		public CryptPad ()
		{
			// Required for Windows Form Designer support
			InitializeComponent ();
			Font = SystemFonts.MessageBoxFont;
		}
		private void CryptPad_Load (object sender, EventArgs e)
		{
			// save for Menu, Tools, Command Line
			original_CurrentDirectory = Environment.CurrentDirectory;

			// No settings?
			if (Settings.WindowPosition.IsEmpty)
				Settings.Upgrade ();
			Backup = Settings.Backup;
			CurrentFont = Settings.CurrentFont;
			FontColor = Settings.FontColor;
			BackgroundColor = Settings.BackColor;
			DisplayBefore = Settings.DisplayBefore;
			DisplayEncoding = Settings.DisplayEncoding;
			DisplayLines = Settings.DisplayLines;
			IsStatusBarVisible = Settings.IsStatusBarVisible;
			WordWrap = Settings.WordWrap;

			if (Settings.WindowPosition.IsEmpty)
				Settings.WindowPosition = Bounds;
			else
				Bounds = Settings.WindowPosition;
			StartPosition = FormStartPosition.Manual;
			timerStatus.Tick += new EventHandler (timerStatus_Tick);
		}
		private void CryptPad_Shown (object sender, EventArgs e)
		{
			// initialize in case there is no open or there is an open error
			NewAll (true);

			String[] arguments = Environment.GetCommandLineArgs ();

			// Command line arguments?
			if (1 == arguments.Length)
				return;
			// Script or file
			if (2 == arguments.Length)
			{
				if (".CRYPTPAD" == Path.GetExtension (arguments[1]).ToUpper ())
				{
					if (!File.Exists (arguments[1]))
					{
						DisplayArguments (arguments, "Missing script file!");
						return;
					}
					Do_Script (arguments[1]);
				}
				else
				{
					FileOpen (false, null, arguments[1]);
					// prepare for Open and Save As dialogs
					Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (arguments[1]));
				}
				return;
			}
			// Script and file
			if (3 == arguments.Length)
			{
				if (".CRYPTPAD" != Path.GetExtension (arguments[1]).ToUpper ())
				{
					DisplayArguments (arguments, "Not CryptPad script!");
					return;
				}
				if (!File.Exists (arguments[1]))
				{
					DisplayArguments (arguments, "Missing script file!");
					return;
				}
				// all references relative to the script file
				Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (arguments[1]));
				if (0 < arguments[2].Length)
					if (AutoOpen (arguments[2]))
					{
						DisplayArguments (arguments, "");
						return;
					}
				Do_Script (arguments[1]);
				return;
			}
			// Other arguments same as a script line
			if (ProcessArguments (arguments))
				DisplayArguments (arguments, "");
		}
		private void CryptPad_LocationChanged (object sender, EventArgs e)
		{
			BoundsChanged ();
		}
		private void CryptPad_SizeChanged (object sender, EventArgs e)
		{
			BoundsChanged ();
			// word wrap changes current line
			UpdateStatusBar ();
		}
		private void BoundsChanged ()
		{
			// not final settings yet
			if (FormStartPosition.Manual != StartPosition)
				return;
			// don't save maximized or minimized
			if (FormWindowState.Normal != WindowState)
				return;
			Settings.WindowPosition = Bounds;
			Settings.Save ();
		}
		private void CryptPad_FormClosing (object sender, FormClosingEventArgs e)
		{
			if (DiscardChanges ())
			{
				e.Cancel = true;
				return;
			}

			// Discard anything open to speed cleanup
			NewAll (true);
		}
		private void txtBody_MouseDown (object sender, MouseEventArgs e)
		{
			UpdateStatusBar ();
		}
		private void txtBody_KeyDown (object sender, KeyEventArgs e)
		{
			// delay status update until after key is processed
			timerStatus.Interval = 1;
			timerStatus.Start ();
		}
		private void timerStatus_Tick (Object myObject, EventArgs myEventArgs)
		{
			timerStatus.Stop ();
			UpdateStatusBar ();
		}
		#endregion
		#region Command Line Automation
		/// <summary> Command Line Automation processing
		/// </summary>
		/// <param name="arguments">arguments to process from command line or script</param>
		/// <returns>indicates non-handled failure</returns>
		private bool ProcessArguments (String[] arguments)
		{
			if ("DO" != arguments[1].ToUpper ())
			{
				DisplayArguments (arguments, "Argument 1 must be 'DO'!");
				return false;
			}
			// There must be at least three arguments
			if (3 > arguments.Length)
			{
				DisplayArguments (arguments, "Not enough arguments!");
				return false;
			}
			string s = arguments[2].ToUpper ();
			switch (s)
			{
				case "SCRIPT":
					if (automation_script)
					{
						DisplayArguments (arguments, "Nested SCRIPT is not allowed!");
						return false;
					}
					// There must be four arguments
					if (4 != arguments.Length)
					{
						DisplayArguments (arguments, "Four arguments required!");
						return false;
					}
					if (!File.Exists (arguments[3]))
					{
						DisplayArguments (arguments, "Missing script file!");
						return false;
					}
					Do_Script (arguments[3]);
					break;
				case "ANSI":
					automation = true;
					if (3 == arguments.Length)
					{
						if (null == current_file_name)
						{
							DisplayArguments (arguments, "No open file!");
							return false;
						}
						break;
					}
					if (4 < arguments.Length)
					{
						DisplayArguments (arguments, "Too many arguments!");
						return false;
					}
					if (AutoOpen (arguments[3]))
						return true;
					if (1252 != saved_encoding.CodePage)
					{
						menuFileForceANSI_Click (null, null);
						menuFileSave_Click (null, null);
					}
					if (!automation_script)
						menuFileExit_Click (null, null);
					break;
				case "UTF-8":
					automation = true;
					if (3 == arguments.Length)
					{
						if (null == current_file_name)
						{
							DisplayArguments (arguments, "No open file!");
							return false;
						}
						break;
					}
					if (4 < arguments.Length)
					{
						DisplayArguments (arguments, "Too many arguments!");
						return false;
					}
					if (AutoOpen (arguments[3]))
						return true;
					if (65001 != saved_encoding.CodePage)
					{
						menuFileForceASCII_Click (null, null);
						menuFileSave_Click (null, null);
					}
					if (!automation_script)
						menuFileExit_Click (null, null);
					break;
				case "REPLACE":
					if (Arg5Test (arguments))
						return false;
					Replace (Unescape (arguments[3]), Unescape (arguments[4]));
					break;
				case "REPLACEMATCH":
					if (Arg5Test (arguments))
						return false;
					ReplaceMatch (Unescape (arguments[3]), Unescape (arguments[4]));
					break;
				case "REGEX":
					if (Arg5Test (arguments))
						return false;
					txtBody.Text = Regex.Replace (txtBody.Text, Unescape (arguments[3]), Unescape (arguments[4]),
						RegexOptions.Multiline | RegexOptions.IgnoreCase);
					break;
				case "REGEXMATCH":
					if (Arg5Test (arguments))
						return false;
					txtBody.Text = Regex.Replace (txtBody.Text, Unescape (arguments[3]), Unescape (arguments[4]),
						RegexOptions.Multiline);
					break;
				case "READONLY":
					if (automation_script)
					{
						DisplayArguments (arguments, "READONLY is only allowed on command line!");
						return false;
					}
					if (4 != arguments.Length)
					{
						DisplayArguments (arguments, "Four arguments required!");
						return false;
					}
					ReadOnly = true;
					if (FileOpen (false, null, arguments[3]))
						return true;
					// prepare for Open and Save As dialogs
					Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (arguments[3]));
					break;
				case "EDIT":
					if (4 != arguments.Length)
					{
						DisplayArguments (arguments, "Four arguments required!");
						return false;
					}
					if (AutoOpen (arguments[3]))
						return true;
					// prepare for Open and Save As dialogs
					Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (arguments[3]));
					break;
				default:
					DisplayArguments (arguments, s + " is an unrecognized command!");
					break;
			}
			return false;
		}
		private bool Arg5Test (String[] arguments)
		{
			if (!automation_script)
			{
				DisplayArguments (arguments, "SCRIPT command!");
				return true;
			}
			if (5 != arguments.Length)
			{
				DisplayArguments (arguments, "Five arguments required!");
				return true;
			}
			if (null == current_file_name)
			{
				DisplayArguments (arguments, "No open file!");
				return true;
			}
			return false;
		}
		private void ReplaceMatch (string f, string r)
		{
			string s = txtBody.Text;
			if (0 <= s.IndexOf (f))
			{
				txtBody.Text = s.Replace (f, r);
				txtBody.Modified = true;
			}
		}
		private void Replace (string f, string r)
		{
			string s = txtBody.Text;
			int i = 0;
			int jf = f.Length;
			int jr = r.Length;
			bool c = false;

			for (; ; )
			{
				i = s.IndexOf (f, i, StringComparison.CurrentCultureIgnoreCase);
				if (0 > i)
					break;
				c = true;
				s = s.Substring (0, i) + r + s.Substring (i + jf);
				i += jr;
			}
			if (c)
			{
				txtBody.Text = s;
				txtBody.Modified = true;
			}
		}
		/// <summary> Automation open never changes Environment.CurrentDirectory
		/// </summary>
		/// <param name="path"></param>
		private bool AutoOpen (string path)
		{
			// this should always save
			DiscardChanges ();
			NewAll (false);
			if (FileOpen (false, null, path))
				return true;
			return false;
		}
		/// <summary> Display command line or script arguments
		/// </summary>
		/// <param name="arguments">string array of arguments</param>
		/// <param name="s">optional error string</param>
		private void DisplayArguments (String[] arguments, string s)
		{
			StringBuilder st = new StringBuilder ();
			if (automation_script)
			{
				st.AppendLine ("Automation Script Line: " + automation_script_line);
				st.AppendLine ();
			}
			if (null != s)
			{
				st.AppendLine ("Command Line Automation is exiting immediately!");
				st.AppendLine ();
				if ("" != s)
				{
					st.AppendLine (s);
					st.AppendLine ();
				}
			}
			st.AppendLine ("Environment.CurrentDirectory = " + original_CurrentDirectory);
			st.AppendLine ();
			st.AppendLine ("Environment.GetCommandLineArgs.Length = " + arguments.Length);
			st.AppendLine ();

			int i;
			for (i = 0; i < arguments.Length; ++i)
				st.AppendLine (String.Format ("{0} = {1}", i, arguments[i]));

			Messagebox.Show (st.ToString ());
			if (null != s)
			{
				errorlevel = 2;
				menuFileExit_Click (null, null);
			}
		}
		private void Do_Script (string path)
		{
			automation_script = true;
			automation = true;
			path = Path.GetFullPath (path);
			// all file references in this script are relative to the script
			Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (path));
			StreamReader streamreader = new StreamReader (path);
			List<string> arguments = new List<string> ();
			automation_script_line = 0;
			while (!streamreader.EndOfStream)
			{
				arguments.Clear ();
				arguments.Add (path);
				string s = streamreader.ReadLine ();
				++automation_script_line;
				s = s.Replace ('\0', ' ');
				s = s.Replace ("\\\"", "\0");
				int istart = 0;
				int iend = 0;
				string arg;
				for (; ; )
				{
					// skip leading spaces
					while (istart < s.Length)
						if (' ' == s[istart])
							++istart;
						else
							break;
					if (istart == s.Length)
						break;
					if ('"' == s[istart])
					{
						++istart;
						iend = s.IndexOf ('\"', istart);
						if (0 > iend)
						{
							arguments.Add (s);
							DisplayArguments (arguments.ToArray (), "No terminating quote!");
							arguments.Clear ();
							break;
						}
						arg = s.Substring (istart, iend - istart);
						++iend;
						istart = iend;
						if (s.Length > iend && ' ' != s[iend])
						{
							arguments.Add (s);
							DisplayArguments (arguments.ToArray (), "Terminating quote not followed by a space!");
							arguments.Clear ();
							break;
						}
					}
					else
					{
						iend = s.IndexOf (' ', istart);
						if (0 > iend)
							iend = s.Length;
						arg = s.Substring (istart, iend - istart);
						istart = iend;
					}
					arg = arg.Replace ("\0", "\\\"");
					// special case to insert a quote from a command line
					arg = arg.Replace ("\\q", "\"");
					arguments.Add (arg);
				}
				if (0 == arguments.Count)
					break;
				if (1 == arguments.Count)
					continue;
				if (ProcessArguments (arguments.ToArray ()))
					DisplayArguments (arguments.ToArray (), "");
				if (2 == errorlevel)
					break;
			}
			streamreader.Close ();
			menuFileExit_Click (null, null);
		}
		#endregion
		#region File Menu Items
		private void menuFileNew_Click (object sender, System.EventArgs e)
		{
			if (DiscardChanges ())
				return;
			NewAll (true);
		}
		private void menuFileOpen_Click (object sender, System.EventArgs e)
		{
			if (DiscardChanges ())
				return;
			NewAll (false);
			OpenFileDialog openFileDialog = new OpenFileDialog ();

			openFileDialog.InitialDirectory = Environment.CurrentDirectory;
			// the first entries must match base_read_encodings array
			openFileDialog.Filter =
				"Binary (*.*)|*.*|"
				+ "ANSI encoding (*.*)|*.*|"
				+ "UTF-8 encoding (*.*)|*.*|"
				+ "Unicode encoding (*.*)|*.*|"
				+ "Unicode (Big-Endian) encoding (*.*)|*.*|"
				+ "Open read-only (*.*)|*.*|"
				+ "Encrypted documents (*.safe)|*.safe|"
				+ "Text Documents (*.txt)|*.txt|"
				+ "All documents (*.safe, *.txt)|*.safe;*.txt|"
				+ "All Files (*.*)|*.*";

			int i = base_read_encodings.Length + 2;
			if (null == encryption_string)
				++i;
			openFileDialog.FilterIndex = i;

			// can't use this because it causes an ugly old style dialog box
			// openFileDialog.ShowReadOnly = true;

			if (DialogResult.Cancel == openFileDialog.ShowDialog (this))
			{
				NewAll (true);
				return;
			}
			// Did they select read-only
			if (openFileDialog.FilterIndex == base_read_encodings.Length + 1)
				ReadOnly = true;
			else
				ReadOnly = false;
			// Did they select forced encoding
			Encoding force_encoding;
			bool binary_encoding = false;
			if (1 == openFileDialog.FilterIndex)
				binary_encoding = true;
			if (openFileDialog.FilterIndex < base_read_encodings.Length + 1)
				force_encoding = base_read_encodings[openFileDialog.FilterIndex - 1];
			else
				force_encoding = null;

			Environment.CurrentDirectory = Path.GetDirectoryName (openFileDialog.FileName);
			FileOpen (binary_encoding, force_encoding, openFileDialog.FileName);
		}
		private void menuFileSave_Click (object sender, System.EventArgs e)
		{
			FileSave ();
		}
		private void menuFileSaveAs_Click (object sender, System.EventArgs e)
		{
			FileSaveAs ();
		}
		private void menuFileReOpenBinary_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			string file_name = current_file_name;
			NewAll (false);
			FileOpen (true, base_read_encodings[0], file_name);
		}
		private void menuFileReOpenANSI_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			string file_name = current_file_name;
			NewAll (false);
			FileOpen (false, base_read_encodings[1], file_name);
		}
		private void menuFileReOpenUTF8_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			string file_name = current_file_name;
			NewAll (false);
			FileOpen (false, base_read_encodings[2], file_name);
		}
		private void menuFileReOpenUnicode_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			string file_name = current_file_name;
			NewAll (false);
			FileOpen (false, base_read_encodings[3], file_name);
		}
		private void menuFileReOpenUnicodeBE_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			string file_name = current_file_name;
			NewAll (false);
			FileOpen (false, base_read_encodings[4], file_name);
		}
		private void menuFileForceANSI_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.ANSI));
			txtBody.Modified = true;
		}
		private void menuFileForceASCII_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.UTF_8_ASCII));
			txtBody.Modified = true;
		}
		private void menuFileForceUTF8_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.Unicode_UTF_8));
			txtBody.Modified = true;
		}
		private void menuFileForceUnicode_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.Unicode));
			txtBody.Modified = true;
		}
		private void menuFileForceUnicodeBOM_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.Unicode_BOM));
			txtBody.Modified = true;
		}
		private void menuFileForceUnicodeBE_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.Unicode_Big_Endian));
			txtBody.Modified = true;
		}
		private void menuFileForceUnicodeBEBOM_Click (object sender, EventArgs e)
		{
			UpdateEncoding (Encode (Encodes.Unicode_Big_Endian_BOM));
			txtBody.Modified = true;
		}
		private void menuFileEncryption_Click (object sender, EventArgs e)
		{
			uint save_checksum = encryption_checksum;
			encryption_checksum = 0;
			if (DialogResult.Cancel == Encryption ())
				encryption_checksum = save_checksum;
			else
				txtBody.Modified = true;
		}
		private void menuFileExit_Click (object sender, EventArgs e)
		{
			if (DiscardChanges ())
				return;
			Application.Exit ();
		}
		#endregion
		#region Edit Menu Items
		private void menuEdit_Popup (object sender, EventArgs e)
		{
			menuEditUndo.Enabled = !ReadOnly & txtBody.CanUndo;
			menuEditPaste.Enabled = !ReadOnly & Clipboard.ContainsText ();
			bool f = (0 < txtBody.SelectionLength);
			menuEditCut.Enabled = !ReadOnly & f;
			menuEditCopy.Enabled = f;
			menuEditDelete.Enabled = !ReadOnly & f;
			menuEditCapitalize.Enabled = !ReadOnly & f;
			menuEditLowercase.Enabled = !ReadOnly & f;
			menuEditUppercase.Enabled = !ReadOnly & f;
			menuEditTranspose.Enabled = !ReadOnly & !f;
		}
		private void menuEditUndo_Click (object sender, EventArgs e)
		{
			txtBody.Undo ();
		}
		private void menuEditCut_Click (object sender, System.EventArgs e)
		{
			txtBody.Cut ();
		}
		private void menuEditCopy_Click (object sender, System.EventArgs e)
		{
			txtBody.Copy ();
		}
		private void menuEditPaste_Click (object sender, System.EventArgs e)
		{
			txtBody.Paste ();
		}
		private void menuEditDelete_Click (object sender, EventArgs e)
		{
			txtBody.Paste ("");
		}
		private void menuEditUppercase_Click (object sender, EventArgs e)
		{
			if (0 == txtBody.SelectionLength)
				return;
			int s = txtBody.SelectionStart;
			int l = txtBody.SelectionLength;
			txtBody.Paste (txtBody.SelectedText.ToUpper ());
			txtBody.Select (s, l);
		}
		private void menuEditLowercase_Click (object sender, EventArgs e)
		{
			if (0 == txtBody.SelectionLength)
				return;
			int s = txtBody.SelectionStart;
			int l = txtBody.SelectionLength;
			txtBody.Paste (txtBody.SelectedText.ToLower ());
			txtBody.Select (s, l);
		}
		private void menuEditCapitalize_Click (object sender, EventArgs e)
		{
			int i;

			if (0 == txtBody.SelectionLength)
				return;
			String s = " " + txtBody.SelectedText.ToLower ();
			i = 0;
			for (; ; )
			{
				i = s.IndexOf (' ', i);
				if (0 > i || i == s.Length - 1)
					break;
				++i;
				s = s.Substring (0, i) + s.Substring (i, 1).ToUpper () + s.Substring (i + 1);
			}
			i = txtBody.SelectionStart;
			int l = txtBody.SelectionLength;
			txtBody.Paste (s.Substring (1));
			txtBody.Select (i, l);
		}
		private void menuEditTranspose_Click (object sender, EventArgs e)
		{
			if (0 < txtBody.SelectionLength)
				return;
			int i = txtBody.SelectionStart;
			if (0 == i || txtBody.Text.Length == i)
				return;
			txtBody.Select (i - 1, 2);
			String s = txtBody.SelectedText;
			if (!isASCII (s[0]) || !isASCII (s[1]))
				return;
			s = s[1].ToString () + s[0].ToString ();
			txtBody.Paste (s);
			txtBody.SelectionStart = i;
		}
		public Form find_Form;	// non-modal return here
		private void menuEditFind_Click (object sender, EventArgs e)
		{
			if (txtBody.Text.Length == 0)
				return;
			if (null != replace_Form)
				replace_Form.Close ();
			if (null == find_Form)
			{
				find_Form = new Find ();
				find_Form.Show (this);
			}
			else
				find_Form.Focus ();
		}
		private void menuEditFindNext_Click (object sender, EventArgs e)
		{
			FindNext (true);
		}
		private void menuEditFindPrevious_Click (object sender, EventArgs e)
		{
			FindNext (false);
		}
		public Form replace_Form;	// non-modal return here
		private void menuEditReplace_Click (object sender, EventArgs e)
		{
			if (txtBody.Text.Length == 0)
				return;
			if (null != find_Form)
				find_Form.Close ();
			if (null == replace_Form)
			{
				replace_Form = new Replace ();
				replace_Form.Show (this);
			}
			else
				replace_Form.Focus ();
		}
		private void menuEditGoTo_Click (object sender, EventArgs e)
		{
			GoTo form = new GoTo ();
			form.ShowDialog (this);
			UpdateStatusBar ();
		}
		private void menuEditSelectAll_Click (object sender, System.EventArgs e)
		{
			txtBody.SelectAll ();
		}
		private void menuEditTimeDate_Click (object sender, EventArgs e)
		{
			txtBody.Paste (ISO8601.DateTimeHM ());
		}
		#endregion
		#region Tools Menu Items
		private void menuToolsPassword_Click (object sender, EventArgs e)
		{
			string s = "";
			Random r = new Random ();
			for (int i = 0; i < 16; ++i)
			{
				int j = r.Next (62);
				if (j < 10)
					s += (char) ('0' + j);
				else if (j < 36)
					s += (char) ('A' + j - 10);
				else
					s += (char) ('a' + j - 36);
			}
			txtBody.Paste (s);
		}
		private void menuToolsQuickCrypt_Click (object sender, EventArgs e)
		{
			string s;

			Cursor.Current = Cursors.WaitCursor;

			if (txtBody.Text.StartsWith (encryption_prefix)
				|| txtBody.Text.StartsWith (encryption_prefix2))
				s = Decrypt (txtBody.Text);
			else
				s = Encrypt (txtBody.Text);
			if (null == s)
				return;
			txtBody.SelectAll ();
			txtBody.Paste (s);
			txtBody.SelectionStart = 0;	// keep text from being selected
		}
		private void menuToolsASCII_Click (object sender, EventArgs e)
		{
			int i, j;

			Cursor.Current = Cursors.WaitCursor;
			string s = txtBody.Text;
			j = txtBody.Text.Length;
			for (i = txtBody.SelectionStart + txtBody.SelectionLength; i < j; ++i)
			{
				if (127 < s[i])
					break;
			}
			if (i == j)
			{
				Messagebox.Show ("No non-ASCII characters found!");
				return;
			}
			SelectScroll (i, 1);
		}
		private void menuToolsLong_Click (object sender, EventArgs e)
		{
			int i, j, l;

			Cursor.Current = Cursors.WaitCursor;

			string[] s = txtBody.Lines;
			j = s.Length;
			int length = 0;
			int index = -1;
			int select = 0;
			for (i = 0; i < j; ++i)
			{
				l = s[i].Length;
				select += l + 2;
				if (length < l)
				{
					length = l;
					index = select;
				}
			}
			SelectScroll (index - 2, 0);
		}
		private void menuToolsBlank_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			int i = Ctrl (txtBody.Text, txtBody.SelectionStart);
			if (0 > i)
			{
				RestoreCaretPosition ();
				Messagebox.Show ("No ANSI Blank found!");
				return;
			}
			txtBody.Select (i, 1);
			int j = txtBody.Text[i];
			string s = "\\x" + j.ToString ("X2");
			txtBody.Paste (s);
			SelectScroll (i, 4);
		}
		private void menuToolsCR_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;
			s = s.Replace ("\r\n", "\n");
			s = s.Replace ("\r", "\n");
			s = s.Replace ("\n", "\r\n");
			txtBody.SelectAll ();
			txtBody.Paste (s);
			RestoreCaretPosition ();
		}
		private void menuToolsLF_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;
			s = s.Replace ("\r\n", "\n");
			s = s.Replace ("\r", "\n");
			if ('\n' != s[s.Length - 1])
				s += "\n";
			txtBody.SelectAll ();
			txtBody.Paste (s);
			RestoreCaretPosition ();
		}
		private void menuToolsDangle_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;
			int i = CRLF (s);
			if (0 > i)
			{
				RestoreCaretPosition ();
				Messagebox.Show ("No Dangling CR or LF found!");
				return;
			}
			txtBody.Select (i, 1);
			if ('\r' == s[i])
				s = "\\r";
			else
				s = "\\n";
			txtBody.Paste (s);
			SelectScroll (i, 2);
		}
		private void menuToolsWrap_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;
			for (int line = txtBody.GetLineFromCharIndex (txtBody.TextLength); line > 0; --line)
			{
				int i = txtBody.GetFirstCharIndexFromLine (line);
				if (' ' == s[i - 1])
					s = s.Insert (i, "\r\n");
			}
			txtBody.SelectAll ();
			txtBody.Paste (s);
			RestoreCaretPosition ();
		}
		private void menuToolsUnwrap_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;

			s = s.Replace (" \r\n", " ");
			txtBody.SelectAll ();
			txtBody.Paste (s);
			RestoreCaretPosition ();
		}
		private void menuToolsTrim_Click (object sender, EventArgs e)
		{
			RememberCaretPosition ();
			string s = txtBody.Text;

			s = s.Replace ("  \r\n", "\r\n");
			s = s.Replace (" \r\n", "\r\n");
			s = s.Replace ("\r\n   ", "\r\n");
			s = s.Replace ("\r\n ", "\r\n");
			txtBody.SelectAll ();
			txtBody.Paste (s);
			RestoreCaretPosition ();
		}
		private void menuToolsCommandLine_Click (object sender, EventArgs e)
		{
			DisplayArguments (Environment.GetCommandLineArgs (), null);
		}
		private void menuToolsBackup0_Click (object sender, EventArgs e)
		{
			Backup = 0;
		}
		private void menuToolsBackup1_Click (object sender, EventArgs e)
		{
			Backup = 1;
		}
		private void menuToolsBackup2_Click (object sender, EventArgs e)
		{
			Backup = 2;
		}
		private void menuToolsBackup3_Click (object sender, EventArgs e)
		{
			Backup = 3;
		}
		#endregion
		#region Format Menu Items
		private void menuFormatWordWrap_Click (object sender, EventArgs e)
		{
			int i = txtBody.SelectionStart;
			WordWrap = !(menuFormatWordWrap.Checked);
			txtBody.DeselectAll ();
			SelectScroll (i, 0);
		}
		private void menuFormatFont_Click (object sender, System.EventArgs e)
		{
			FontDialog dlgFont = new FontDialog ();
			dlgFont.Font = CurrentFont;
			dlgFont.FontMustExist = true;
			dlgFont.ShowEffects = false;
			dlgFont.AllowScriptChange = false;
			dlgFont.AllowSimulations = false;
			dlgFont.AllowVectorFonts = false;
			dlgFont.AllowVerticalFonts = false;
			if (DialogResult.Cancel == dlgFont.ShowDialog (this))
				return;
			CurrentFont = dlgFont.Font;
		}
		private void menuFormatFontColor_Click (object sender, EventArgs e)
		{
			ColorDialog dlgColor = new ColorDialog ();
			dlgColor.Color = FontColor;
			dlgColor.AnyColor = true;
			if (DialogResult.Cancel == dlgColor.ShowDialog (this))
				return;
			FontColor = dlgColor.Color;
		}
		private void menuFormatColor_Click (object sender, EventArgs e)
		{
			ColorDialog dlgColor = new ColorDialog ();
			dlgColor.Color = BackgroundColor;
			dlgColor.AnyColor = true;
			if (DialogResult.Cancel == dlgColor.ShowDialog (this))
				return;
			BackgroundColor = dlgColor.Color;
		}
		#endregion
		#region View Menu Items
		private void menuViewStatus_Click (object sender, EventArgs e)
		{
			IsStatusBarVisible = !menuViewStatus.Checked;
			UpdateStatusBar ();
		}
		private void menuViewBefore_Click (object sender, EventArgs e)
		{
			DisplayBefore = !menuViewBefore.Checked;
			UpdateStatusBar ();
		}
		private void menuViewLines_Click (object sender, EventArgs e)
		{
			DisplayLines = !menuViewLines.Checked;
			UpdateStatusBar ();
		}
		private void menuViewEncoding_Click (object sender, EventArgs e)
		{
			DisplayEncoding = !menuViewEncoding.Checked;
		}
		#endregion
		#region Help Menu Items
		private void menuHelpOnline_Click (object sender, EventArgs e)
		{
			Process process = new Process ();
			process.StartInfo.FileName = CRYPTPAD_URL;
			bool result = process.Start ();
		}
		private void menuHelpLocal_Click (object sender, EventArgs e)
		{
			string path = AppDomain.CurrentDomain.BaseDirectory + "\\CryptPad.html";
			if (!File.Exists (path))
			{
				string[] lines = { "Open online location ", CRYPTPAD_URL, " and save a local copy to ", path };
				System.IO.File.WriteAllLines (path, lines);
			}
			Process process = new Process ();
			process.StartInfo.FileName = path;
			bool result = process.Start ();
		}
		private void menuHelpAbout_Click (object sender, EventArgs e)
		{
			About form = new About ();
			form.ShowDialog (this);
		}
		#endregion
		#region Settings code
		private int Backup
		{
			get
			{
				return Settings.Backup;
			}
			set
			{
				Settings.Backup = value;
				menuToolsBackup0.Checked = false;
				menuToolsBackup1.Checked = false;
				menuToolsBackup2.Checked = false;
				menuToolsBackup3.Checked = false;
				switch (value)
				{
					default:
					case 0:
						menuToolsBackup0.Checked = true;
						break;
					case 1:
						menuToolsBackup1.Checked = true;
						break;
					case 2:
						menuToolsBackup2.Checked = true;
						break;
					case 3:
						menuToolsBackup3.Checked = true;
						break;
				}
				Settings.Save ();
			}
		}
		private Font CurrentFont
		{
			get
			{
				return Settings.CurrentFont;
			}
			set
			{
				txtBody.Font = Settings.CurrentFont = value;
				Settings.Save ();
			}
		}
		private Color FontColor
		{
			get
			{
				return Settings.FontColor;
			}
			set
			{
				txtBody.ForeColor = Settings.FontColor = value;
				Settings.Save ();
			}
		}
		private Color BackgroundColor
		{
			get
			{
				return Settings.BackColor;
			}
			set
			{
				txtBody.BackColor = Settings.BackColor = value;
				Settings.Save ();
			}
		}
		public bool DisplayBefore
		{
			get
			{
				return Settings.DisplayBefore;
			}
			set
			{
				menuViewBefore.Checked = Settings.DisplayBefore = value;
				Settings.Save ();
			}
		}
		public bool DisplayEncoding
		{
			get
			{
				return Settings.DisplayEncoding;
			}
			set
			{
				menuViewEncoding.Checked = Settings.DisplayEncoding = value;
				Settings.Save ();
				if (menuViewEncoding.Checked)
				{
					statusStrip.Visible = true;
					statusStripLabel4.Visible = true;
				}
				else
					statusStripLabel4.Visible = false;
			}
		}
		public bool DisplayLines
		{
			get
			{
				return Settings.DisplayLines;
			}
			set
			{
				menuViewLines.Checked = Settings.DisplayLines = value;
				Settings.Save ();
			}
		}
		/// <summary> Display Line/Char
		/// </summary>
		public bool IsStatusBarVisible
		{
			get
			{
				return Settings.IsStatusBarVisible;
			}
			set
			{
				menuViewStatus.Checked = Settings.IsStatusBarVisible = value;
				Settings.Save ();
			}
		}
		public bool WordWrap
		{
			get
			{
				return Settings.WordWrap;
			}
			set
			{
				menuFormatWordWrap.Checked = txtBody.WordWrap = Settings.WordWrap = value;
				Settings.Save ();
			}
		}
		#endregion
		#region Shared functions
		/// <summary> Set original blank file
		/// </summary>
		/// <param name="clear_encrypt">Clear any previous encryption string</param>
		private void NewAll (bool clear_encrypt)
		{
			if (clear_encrypt)
			{
				encryption_string = null;
				encryption_checksum = 0;
			}
			if (null != file_stream)
			{
				file_stream.Close ();
				file_stream = null;
			}
			ReadOnly = false;
			UpdateEncoding (Encode (Encodes.UTF_8_ASCII));
			txtBody.Clear ();
			txtBody.SelectionStart = 0;	// keep text from being selected
			Title (null);
		}
		private bool FileOpen (bool binary_encoding, Encoding use_encoding, string original_filename)
		{
			int i;
			string s;
			string filename = Path.GetFullPath (original_filename);
			if (!File.Exists (filename))
			{
				Messagebox.Show ("File does not exist!\r\n\r\n" + filename);
				return true;
			}
			// refuse to open a file with a backup extension of 1, 2, or 3.
			s = Path.GetExtension (filename);
			if (s == ".1" || s == ".2" || s == ".3")
			{
				Messagebox.Show ("Cannot open a backup file!\r\n\r\n" + filename);
				return true;
			}

			if (!ReadOnly)
			{
				s = null;
				try
				{
					file_stream = new FileStream (filename, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
					backup = true;
				}
				catch (Exception err)
				{
					s = err.Message;
				}
				if (null != s)
				{
					// we don't do read-only in automation
					if (automation)
						return true;
					s += " Open read-only?";
					DialogResult code = Warning_Popup (s);
					if (DialogResult.Cancel == code)
						return true;
					ReadOnly = true;
				}
			}

			if (ReadOnly)
			{
				s = null;
				try
				{
					file_stream = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				}
				catch (Exception err)
				{
					s = err.Message;
				}
				if (null != s)
				{
					Messagebox.Show (s);
					return true;
				}
			}

			if (file_stream.Length > SI.ParseI32 ("2M"))
			{
				s = "File size is about " + SI.ToString (file_stream.Length)
					+ "! While it may possible to edit a file of this size, it will be slow.";
				if (DialogResult.Cancel == Warning_Popup (s))
				{
					NewAll (false);
					return true;
				}
			}
			StreamReader sr;
			// is it forced encoding?
			if (null == use_encoding)
				// automatic attempt UTF-8 encoding with ANSI error exception, BOM detect
				sr = new StreamReader (file_stream, Encode (Encodes.UTF_8_ASCII), true);
			else
				sr = new StreamReader (file_stream, use_encoding, false);

			Cursor.Current = Cursors.WaitCursor;

			s = null;
			try
			{
				// fast part for big files
				s = sr.ReadToEnd ();
				UpdateEncoding (sr.CurrentEncoding);
			}
			catch
			{
				// ANSI error detection
				s = null;
			}
			if (s == null && file_stream.Position > 0)
			{
				// use ANSI				
				UpdateEncoding (Encode (Encodes.ANSI));
				file_stream.Position = 0;
				sr = new StreamReader (file_stream, saved_encoding);
				s = sr.ReadToEnd ();
			}
			if (ReadOnly)
				sr.Close ();
			else
				// don't close or dispose because it closes the underlying file_stream
				sr.DiscardBufferedData ();
			// binary data from file
			byte[] file_bytes = null;
			// did they force binary?
			if (binary_encoding)
			{
				file_bytes = saved_encoding.GetBytes (s);
				UpdateEncoding (Encode (Encodes.Binary));
			}
			// is the encoding automatic and ambiguous and contains NUL?
			else if ((null == use_encoding) && (0 == saved_encoding.GetPreamble ().Length) && (0 <= s.IndexOf ("\x0")))
			{
				// ANSI and UTF-8 ASCII do not change bytes so no need to reread
				file_bytes = saved_encoding.GetBytes (s);
				// we are interested in the last two bytes
				i = file_bytes.Length - 2;
				// tentative assume binary for ANSI
				if (Encodes.ANSI == saved_encoding_enum)
					saved_encoding_enum = Encodes.Binary;
				// Unicode requires an even nmber of bytes
				if (0 == (i & 1))
				{
					if ('\n' == file_bytes[i] && '\x0' == file_bytes[i + 1])
					{
						if (TestEncode (Encode (Encodes.Unicode), file_bytes))
							s = base_read_encodings[3].GetString (file_bytes);
					}
					else if ('\x0' == file_bytes[i] && '\n' == file_bytes[i + 1])
					{
						if (TestEncode (Encode (Encodes.Unicode_Big_Endian), file_bytes))
							s = base_read_encodings[4].GetString (file_bytes);
					}
				}
				// final update if determined binary
				if (Encodes.Binary == saved_encoding_enum)
					UpdateEncoding (Encode (Encodes.Binary));
			}

			// only warn for non-binary
			if (null != saved_encoding)
			{
				// discard binary data
				file_bytes = null;
				i = s.IndexOf ("\x0");
				if (0 <= i)
				{
					string str2 = "NUL (zero value) character at index " + i + " found!"
					+ " Select \"OK\" to change all NUL to a space or \"Cancel\" to abort file open.";
					if (DialogResult.Cancel == Warning_Popup (str2))
					{
						NewAll (false);
						return true;
					}
					s = s.Replace ("\x0", " ");
				}
				i = CRLF (s);
				if (0 <= i)
				{
					string str2 = "A dangling CR or LF character at index " + i + " found! "
									+ "Select \"OK\" to Force CR/LF or \"Cancel\" to ignore.";
					if (DialogResult.Cancel != Warning_Popup (str2))
					{
						s = s.Replace ("\r", "");
						s = s.Replace ("\n", "\r\n");
					}
				}

				i = Ctrl (s, 0);
				if (0 <= i)
					Messagebox.Show ("ANSI Blank character at index " + i + " found!");
				s = Decrypt (s);
			}
			else
			{
				// discard encode
				s = null;
				Messagebox.Show ("Binary file displayed in hexadecimal and ASCII!");
				int size = file_bytes.Length;
				StringBuilder t = new StringBuilder (size * 5);
				for (int b = 0; b < size; b += 16)
				{
					for (i = 0; i < 16; ++i)
					{
						if (b + i < size)
						{
							t.AppendFormat ("{0:X2}", (int) file_bytes[b + i]);
							if (15 > i && 3 == i % 4)
								t.Append ('·');
							else
								t.Append (" ");
						}
						else
							t.Append ("\x20\x20\x20");
					}
					for (i = 0; i < 16; ++i)
						if (b + i < size)
						{
							if ((file_bytes[b + i] >= 32) & (file_bytes[b + i] < 128))
								t.Append ((char) file_bytes[b + i]);
							else
								t.Append ('·');
						}
						else
							t.Append (" ");
					t.AppendLine ();
				}
				s = t.ToString ();
			}

			// slowest part for big files
			txtBody.Text = s;
			txtBody.SelectionStart = 0;	// keep text from being selected
			Title (filename);
			return false;
		}
		/// <summary> Test good encoding or bad accepted
		/// </summary>
		/// <param name="encoding"></param>
		/// <param name="t"></param>
		/// <returns></returns>
		bool TestEncode (Encoding encoding, byte[] t)
		{
			string s = null;
			try
			{
				encoding.GetString (t);
				UpdateEncoding (encoding);
				return true;
			}
			catch (Exception exception)
			{
				s = exception.Message;
			}
			if (DialogResult.OK != Warning_Popup (s))
				return false;
			UpdateEncoding (encoding);
			return true;
		}
		/// <summary> Return an encoding matching the enumeration
		/// </summary>
		/// <param name="encode"></param>
		/// <returns></returns>
		public Encoding Encode (Encodes encode)
		{
			return base_write_encodings[(int) encode];
		}
		private void Title (string filename)
		{
			current_file_name = filename;
			// can't ReOpen what ain't a file name
			menuFileReOpen.Enabled = null != filename;
			Text = (null == current_file_name ? "Untitled" : Path.GetFileName (filename))
				+ " - " + Name
				+ (ReadOnly ? " (read-only)" : "")
				+ (0 < encryption_checksum ? " (Encrypted)" : "");
			UpdateStatusBar ();
		}
		/// <summary> Decrypt if encrypted and ask for encryption phrase, as needed
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		private string Decrypt (string s)
		{
			bool v2;
			if (s.StartsWith (encryption_prefix2))
				v2 = true;
			else if (s.StartsWith (encryption_prefix))
				v2 = false;
			else
			{
				encryption_string = null;
				encryption_checksum = 0;
				return s;
			}

			StringBuilder st;
			int j;

			// distinguish version 1 and version 2
			if (v2)
			{
				uint data_checksum = 0;
				if (Checksum (s.Substring (5, 8), out data_checksum))
				{
					encryption_string = null;
					encryption_checksum = 0;
					return s;
				}

				j = s.Length;
				uint checksum = 0;
				for (int i = 13; i < j; ++i)
				{
					// checksum
					uint t = 0;
					if (0 < (0x80000000 & checksum))
						t = 1;
					checksum <<= 1;
					checksum |= t;
					checksum ^= (uint) s[i];
				}
				if (data_checksum != checksum)
				{
					Messagebox.Show ("Encrypted data corruption found!");
					encryption_string = null;
					encryption_checksum = 0;
					return s;
				}
				st = new StringBuilder (s.Substring (21));
			}
			else
				st = new StringBuilder (s.Substring (12));

			if (Checksum (s.Substring (v2 ? 13 : 4, 8), out encryption_checksum))
			{
				encryption_string = null;
				encryption_checksum = 0;
				return s;
			}
			// it will return immediately, if we already have encryption_string
			Encryption ();
			if (null == encryption_string)
				return s;
			j = st.Length;
			for (int i = 0; i < j; ++i)
			{
				int chr = st[i];
				// decrypt
				if (isASCII ((char) chr))
				{
					chr = ' ' + ((chr - ' ') - E (i) + 95) % 95;
					st[i] = (char) chr;
				}
			}
			return st.ToString ();
		}
		private bool Checksum (string s, out uint checksum)
		{
			string str2;

			// not an encryption_checksum
			if (!UInt32.TryParse (s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out checksum))
				return true;

			str2 = checksum.ToString ("X8");

			// strings don't match!
			if (s != str2)
				return true;

			return false;
		}
		private bool FileSaveAs ()
		{
			bool error_flag = false;
			SaveFileDialog saveFileDialog = new SaveFileDialog ();
			saveFileDialog.FileName = current_file_name;

			saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
			// the first entries must match base_write_encodings array
			saveFileDialog.Filter =
				"ANSI encoding (*.*)|*.*|"
				+ "UTF-8 (ASCII) encoding (*.*)|*.*|"
				+ "Unicode (UTF-8) encoding (*.*)|*.*|"
				+ "Unicode encoding (*.*)|*.*|"
				+ "Unicode (BOM) encoding (*.*)|*.*|"
				+ "Unicode (Big-Endian) encoding (*.*)|*.*|"
				+ "Unicode (Big-Endian BOM) encoding (*.*)|*.*|"
				+ "Encrypted documents (*.safe)|*.safe|"
				+ "Text Documents (*.txt)|*.txt|"
				+ "All documents (*.safe, *.txt)|*.safe;*.txt|"
				+ "All Files (*.*)|*.*";

			int i = base_write_encodings.Length;
			if (null == encryption_string)
				++i;
			saveFileDialog.FilterIndex = i;

			if (DialogResult.Cancel == saveFileDialog.ShowDialog ())
				return true;

			if (base_write_encodings.Length >= saveFileDialog.FilterIndex)
				UpdateEncoding (base_write_encodings[saveFileDialog.FilterIndex]);

			if (null != file_stream)
				file_stream.Close ();

			Environment.CurrentDirectory = Path.GetDirectoryName (saveFileDialog.FileName);

			try
			{
				file_stream = new FileStream (saveFileDialog.FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
			}
			catch (Exception err)
			{
				Messagebox.Show (err.Message);
				error_flag = true;
			}
			if (error_flag)
				return true;
			ReadOnly = false;
			Title (saveFileDialog.FileName);
			backup = true;	// we could be overwriting!
			bool result = FileSave ();
			backup = true;
			return result;
		}
		private bool FileSave ()
		{
			if (null == current_file_name)
				return FileSaveAs ();

			Cursor.Current = Cursors.WaitCursor;

			string s = txtBody.Text;
			if (0<s.Length&&'\n' != s[s.Length - 1])
				s += "\r\n";
			if (null != encryption_string)
				s = Encrypt (s);

			if (null == saved_encoding)
				saved_encoding = Encode (Encodes.ANSI);

			// will unicode save as possible ANSI encoding?
			string err_str = null;
			byte[] test = null;
			try
			{
				test = saved_encoding.GetBytes (s);
			}
			catch (Exception err)
			{
				err_str = err.Message;
			}
			if (null != err_str)
			{
				if (Encodes.ANSI != saved_encoding_enum)
				{
					Messagebox.Show (err_str + "\nUnexpected Encoding Error!");
					return true;
				}
				if (!automation)
				{
					if (DialogResult.Cancel == Warning_Popup (err_str + "\n\nThis file appears to contain"
						+ " characters in Unicode format which may be lost if you save this file as an"
						+ " ANSI encoded text file. To keep the Unicode information, click Cancel below and"
						+ " then select one of the Unicode options under \"File, Force Encoding\" or from the \"File, SaveAs, Save as Type\" dropdown list."
						+ "\r\n\r\nContinue anyway?"))
						return true;
				}
				// Local codepage no exceptions				
				UpdateEncoding (Encoding.Default);

				Cursor.Current = Cursors.WaitCursor;

				err_str = null;
				try
				{
					test = saved_encoding.GetBytes (s);
				}
				catch (Exception err)
				{
					err_str = err.Message;
				}
				if (null != err_str)
				{
					Messagebox.Show (err_str + "\nUnexpected Error!");
					return true;
				}

				// change the screen contents also
				s = saved_encoding.GetString (test);
				txtBody.Text = s;
				txtBody.SelectionStart = 0;
			}

			// is this the first save after open and backup generation selected
			if (backup && 0 < Backup)
			{
				backup = false;
				string filename = file_stream.Name;
				for (int i = Backup; i > 1; --i)
					Rename (filename + "." + (i - 1), filename + "." + i);
				file_stream.Close ();
				Rename (filename, filename + ".1");
				file_stream = new FileStream (filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
			}
			else
			{
				// I am having a problem with losing network connection but it is
				// a network or firewall or webserver problem.
				file_stream.Seek (0, SeekOrigin.Begin);

				// discard any previous possible contents
				file_stream.SetLength (0);
			}
			// use the StreamWriter to automatically handle the optional BOM
			StreamWriter sw = new StreamWriter (file_stream, saved_encoding);
			sw.Write (s);
			sw.Flush ();
			txtBody.Modified = false;
			errorlevel = 0;	// a successful save
			return false;
		}
		/// <summary> Rename backup files with error handling
		/// </summary>
		/// <param name="source">file path</param>
		/// <param name="destination">file path</param>
		private void Rename (string source, string destination)
		{
			if (!File.Exists (source))
				return;
			if (File.Exists (destination))
			{
				try
				{
					File.Delete (destination);
				}
				catch (Exception e)
				{
					Messagebox.Show ("Error replacing " + destination + "\r\n\r\n" + e.Message);
					return;
				}
			}
			try
			{
				File.Move (source, destination);
			}
			catch (Exception e)
			{
				Messagebox.Show ("Error moving " + source + "\r\n\r\n" + e.Message);
				return;
			}
		}
		private string Encrypt (string s)
		{
			if (null == encryption_string)
				if (DialogResult.Cancel == Encryption ())
					return s;
			StringBuilder st = new StringBuilder (s);
			int j = st.Length;
			for (int i = 0; i < j; ++i)
			{
				int chr = st[i];
				// encrypt
				if (isASCII ((char) chr))
				{
					chr = ' ' + (((chr - ' ') + E (i)) % 95);
					st[i] = (char) chr;
				}
			}

			s = encryption_checksum.ToString ("X8") + st;

			j = s.Length;
			uint checksum = 0;
			for (int i = 0; i < j; ++i)
			{
				// checksum
				uint t = 0;
				if (0 < (0x80000000 & checksum))
					t = 1;
				checksum <<= 1;
				checksum |= t;
				checksum ^= (uint) s[i];
			}
			return encryption_prefix2 + checksum.ToString ("X8") + s;
		}
		/// <summary> Calculate psuedo-random encryption value by position
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		private int E (int i)
		{
			return (encryption_string[i % encryption_string.Length] + i + i * random_seed) % 95;
		}
		private DialogResult Encryption ()
		{
			Encryption form = new Encryption ();
			DialogResult t = form.ShowDialog (this);
			return t;
		}
		private bool DiscardChanges ()
		{
			DialogResult result;
			if (ReadOnly)
			{
				txtBody.Modified = false;
				return false;
			}
			if (txtBody.Modified)
			{
				if (automation)
					result = DialogResult.Yes;
				else
					result = Messagebox.Show (
								  "Save " + current_file_name + "?",
								  null,
								  MessageBoxButtons.YesNoCancel);
				switch (result)
				{
					case DialogResult.Yes:
						if (FileSave ())
							return true;
						break;
					case DialogResult.No:
						break;
					case DialogResult.Cancel:
						return true;
				}
				txtBody.Modified = false;
			}
			return false;
		}
		/// <summary> Change Status Bar to reflect changes in txtBody
		/// </summary>
		private void UpdateStatusBar ()
		{
			// is it more efficient to change statusStrip.Visible only once?
			bool visible = false;
			if (DisplayEncoding)
				visible = true;
			int caret = txtBody.SelectionStart;
			if (IsStatusBarVisible)
			{
				visible = true;
				statusStripLabel1.Visible = true;
				int line = txtBody.GetLineFromCharIndex (caret);
				int col = caret - txtBody.GetFirstCharIndexFromLine (line);
				++line;
				++col;
				statusStripLabel1.Text = "Line: " + line.ToString ("N0")
								+ " Char: " + col.ToString ("N0");
			}
			else
				statusStripLabel1.Visible = false;

			if (DisplayBefore)
			{
				visible = true;
				statusStripLabel2.Visible = true;
				statusStripLabel2.Text = "Before: " + caret.ToString ("N0")
								+ " After: " + (txtBody.TextLength - caret).ToString ("N0");
			}
			else
				statusStripLabel2.Visible = false;

			if (DisplayLines)
			{
				visible = true;
				statusStripLabel3.Visible = true;
				statusStripLabel3.Text = "Lines: " + txtBody.Lines.Length.ToString ("N0")
								+ " Chars: " + txtBody.TextLength.ToString ("N0");
			}
			else
				statusStripLabel3.Visible = false;
			statusStrip.Visible = visible;
		}
		/// <summary> changes rarely and would add to processing
		/// </summary>
		private void UpdateEncoding (Encoding new_encoding)
		{
#if false // values display for debugging 
			for (int i = 0; i < encodings.Length; ++i)
			{
				Encoding t = base_encodings[i];
				Console.WriteLine (encodings[i] + "\tEncodingName: " + t.EncodingName + "\tHeaderName: " + t.HeaderName + "\tCodePage: " + t.CodePage + "\tBOM: " + t.GetPreamble ().Length);				
			}
#endif
			// assume all are ok to start
			menuFileReOpenBinary.Enabled = true;
			menuFileReOpenANSI.Enabled = true;
			menuFileReOpenUTF8.Enabled = true;
			menuFileReOpenUnicode.Enabled = true;
			menuFileReOpenUnicodeBE.Enabled = true;

			saved_encoding = new_encoding;
			if (null == saved_encoding)
			{
				menuFileReOpenBinary.Enabled = false;
				saved_encoding_enum = Encodes.Binary;
				ReadOnly = true;
			}
			else
			{
				switch (saved_encoding.CodePage)
				{
					case 65001:
						menuFileReOpenUTF8.Enabled = false;
						saved_encoding_enum = Encodes.UTF_8_ASCII;
						break;
					case 1200:
						menuFileReOpenUnicode.Enabled = false;
						saved_encoding_enum = Encodes.Unicode;
						break;
					case 1201:
						menuFileReOpenUnicodeBE.Enabled = false;
						saved_encoding_enum = Encodes.Unicode_Big_Endian;
						break;
					default:
						menuFileReOpenANSI.Enabled = false;
						saved_encoding_enum = Encodes.ANSI;
						break;
				}
				if (0 < saved_encoding.GetPreamble ().Length)
					saved_encoding_enum++;
			}
			menuFileForceANSI.Enabled = Encodes.ANSI != saved_encoding_enum;
			menuFileForceASCII.Enabled = Encodes.UTF_8_ASCII != saved_encoding_enum;
			menuFileForceUTF8.Enabled = Encodes.Unicode_UTF_8 != saved_encoding_enum;
			menuFileForceUnicode.Enabled = Encodes.Unicode != saved_encoding_enum;
			menuFileForceUnicodeBOM.Enabled = Encodes.Unicode_BOM != saved_encoding_enum;
			menuFileForceUnicodeBE.Enabled = Encodes.Unicode_Big_Endian != saved_encoding_enum;
			menuFileForceUnicodeBEBOM.Enabled = Encodes.Unicode_Big_Endian_BOM != saved_encoding_enum;
			if (Encodes.ANSI == saved_encoding_enum)
				statusStripLabel4.Text = saved_encoding.HeaderName;
			else
				statusStripLabel4.Text = encodings[(int) saved_encoding_enum];
		}
		public DialogResult Warning_Popup (string s)
		{
			return Messagebox.Show (s, null, MessageBoxButtons.OKCancel);
		}
		/// <summary> Find and Replace non-modal dialogs need special positioning
		/// </summary>
		/// <param name="dialog"></param>
		public void Position (Form dialog)
		{
			// center horizontally
			dialog.Left = Left + (Width - dialog.Width) / 2;
			int t = Screen.GetWorkingArea (this).Right;
			if (0 > dialog.Left)
				dialog.Left = 0;
			else if (t < dialog.Right)
				dialog.Left -= dialog.Right - t;

			// Center vertically dodging the caret
			dialog.Top = Top + (Height - dialog.Height) / 2;
			t = txtBody.PointToScreen (txtBody.GetPositionFromCharIndex (txtBody.SelectionStart)).Y;
			int h = (int) txtBody.Font.Height * 3;

			// is the selection area covered by the dialog
			if (t + h > dialog.Top && t - h < dialog.Bottom)
				// is it closer to move the dialog down
				if (t - h < dialog.Bottom - dialog.Height)
					dialog.Top = t + h;
				else
					dialog.Top = t - h - dialog.Height;
		}
		/// <summary> Find string
		/// </summary>
		/// <param name="next">next or false means previous</param>
		public void FindNext (bool next)
		{
			Match match;
			if (null == find_string)
			{
				menuEditFind_Click (null, null);
				return;
			}

			Cursor.Current = Cursors.WaitCursor;

			int i = txtBody.SelectionStart;
			// is regex set?
			if (null != find_regex)
			{
				// searching right to left?
				if (next)
					match = find_regex.Match (txtBody.Text, i + txtBody.SelectionLength);
				else
					match = findr_regex.Match (txtBody.Text, i);
				if (match.Success)
					if (0 == match.Groups[0].Value.Length)
						Messagebox.Show ("Incorrect or incomplete Regex pattern:\r\n\r\n\"" + find_string + "\"!");
					else
						SelectScroll (match.Groups[0].Index, match.Groups[0].Value.Length);
				else
					NotFound ();
				return;
			}

			string str_find = Unescape (find_string);
			StringComparison t;
			if (checkBoxMatchCase)
				t = StringComparison.CurrentCulture;
			else
				t = StringComparison.CurrentCultureIgnoreCase;

			if (next)
			{
				i += txtBody.SelectionLength;
				i = txtBody.Text.IndexOf (str_find, i, t);
			}
			else
			{
				--i;
				if (0 <= i)
					i = txtBody.Text.LastIndexOf (str_find, i, t);
			}
			if (i < 0)
				NotFound ();
			else
				SelectScroll (i, str_find.Length);
			return;
		}
		public void NotFound ()
		{
			Messagebox.Show ("Cannot find \"" + find_string + "\"!");
		}
		public bool isASCII (char chr)
		{
			if (' ' <= chr && '~' >= chr)
				return true;
			return false;
		}
		// do not change this variable directly!
		// use the ReadOnly property
		private bool read_only = false;
		public bool ReadOnly
		{
			get
			{
				return read_only;
			}
			set
			{
				txtBody.ReadOnly = read_only = value;
				menuFileSave.Enabled =
				menuFileForce.Enabled =
				menuFileEncryption.Enabled =
				menuEditUndo.Enabled =
				menuEditCut.Enabled =
				menuEditPaste.Enabled =
				menuEditDelete.Enabled =
				menuEditUppercase.Enabled =
				menuEditLowercase.Enabled =
				menuEditCapitalize.Enabled =
				menuEditTranspose.Enabled =
				menuEditReplace.Enabled =
				menuEditTimeDate.Enabled =
				menuToolsPassword.Enabled =
				menuToolsQuickCrypt.Enabled =
				menuToolsCR.Enabled =
				menuToolsLF.Enabled =
				menuToolsDangle.Enabled =
				menuToolsCtrl.Enabled =
				menuToolsTrim.Enabled =
				menuToolsUnwrap.Enabled =
				menuToolsWrap.Enabled = !read_only;
			}
		}
		private int CRLF (string s)
		{
			// Find dangerous dangling CR or LF characters
			int i = 0;
			for (; ; )
			{
				int j = s.IndexOf ('\r', i);
				i = s.IndexOf ('\n', i);
				// is i == -1 and j== -1
				if (i == j)
					return i;
				i++;
				// is '\r' followed by '\n'
				if (i == j + 2)
					continue;
				if (0 > j)
					return i - 1;
				if (0 >= i || j < i)
					return j;
				return i - 1;
			}
		}
		private int Ctrl (string s, int i)
		{
			// Find next ANSI Blank characters
			for (; i < s.Length; ++i)
			{
				int j = s[i];
				switch (j)
				{
					default:
						break;
					case '\x1':
						return i;
					case '\x1c':
						return i;
					case '\x1d':
						return i;
					case '\x1e':
						return i;
					case '\x1f':
						return i;
					case '\x81':
						return i;
					case '\x8d':
						return i;
					case '\x8f':
						return i;
					case '\x90':
						return i;
					case '\x9d':
						return i;
					case '\xa0':
						return i;
				}
			}
			return -1;
		}
		/// <summary> Select text and scroll into view plus a couple of lines
		/// </summary>
		/// <param name="index">select start point</param>
		/// <param name="length">select length</param>
		public void SelectScroll (int index, int length)
		{
			// first make sure we are visible, if at top
			txtBody.Select (index, 0);
			txtBody.ScrollToCaret ();

			// then make sure there are at least two lines underneath us
			int i = txtBody.GetLineFromCharIndex (index);
			int lines = txtBody.GetLineFromCharIndex (txtBody.TextLength);
			i += 2;
			if (i > lines)
				i = lines;
			i = txtBody.GetFirstCharIndexFromLine (i);
			txtBody.Select (i, 0);
			txtBody.ScrollToCaret ();
			txtBody.Select (index, length);
			UpdateStatusBar ();
		}
		/// <summary> Remember where we were when we started
		/// </summary>
		public void RememberCaretPosition ()
		{
			int index = txtBody.SelectionStart;
			remember_start.startpoint = txtBody.GetPositionFromCharIndex (index).Y;
			remember_start.startline = txtBody.GetLineFromCharIndex (index);
			remember_start.startcol = index - txtBody.GetFirstCharIndexFromLine (remember_start.startline);
		}
		/// <summary> Restore where we were when we started
		/// as far as practical
		/// </summary>
		public void RestoreCaretPosition ()
		{
			// did the end of the file move up too much?
			int last_char = txtBody.Text.Length;
			int last = txtBody.GetLineFromCharIndex (last_char);
			if (last < remember_start.startline)
				remember_start.startline = last;
			int index = txtBody.GetFirstCharIndexFromLine (remember_start.startline);
			index += remember_start.startcol;
			if (last_char < index)
				index = last_char;

			// test for restoring the y
			for (int line = remember_start.startline; line >= 0; --line)
			{
				txtBody.SelectionStart = txtBody.GetFirstCharIndexFromLine (line);
				txtBody.ScrollToCaret ();
				if (remember_start.startpoint <= txtBody.GetPositionFromCharIndex (index).Y)
					break;
			}
			txtBody.Select (index, 0);
		}
		/// <summary> called by Find or Replace to change find_string
		/// checks regex flag and sets regex
		/// </summary>
		/// <param name="flag">checkBoxRegEx status</param>
		public bool checkBoxRegEx (bool flag, string s)
		{
			find_string = s;
			find_regex = null;
			findr_regex = null;
			if (flag)
			{

				RegexOptions options = RegexOptions.Compiled
					| RegexOptions.Multiline;
				if (!checkBoxMatchCase)
					options |= RegexOptions.IgnoreCase;
				try
				{
					find_regex = new Regex (find_string, options);
				}
				catch (Exception ex)
				{
					Messagebox.Show ("Regex Exception: " + ex.Message);
					return true;
				}
				// now set previous search option
				options |= RegexOptions.RightToLeft;
				// should not fail when other checked?
				findr_regex = new Regex (find_string, options);
			}
			return false;
		}
		public void comboBoxPatterns_Click (object sender, EventArgs e)
		{
			ComboBox comboBoxPatterns = (ComboBox) sender;
			comboBoxPatterns.DroppedDown = true;
		}
		/// <summary> called by Find or Replace when the combobox selection changes
		/// </summary>
		public void comboBoxPatterns_SelectedIndexChanged (object sender, EventArgs e)
		{
			ComboBox comboBoxPatterns = (ComboBox) sender;
			TextBox txtbox = (TextBox) comboBoxPatterns.Tag;
			if (0 < comboBoxPatterns.SelectedIndex)
			{
				string s = (string) comboBoxPatterns.SelectedItem;
				txtbox.SelectedText = s.Substring (0, s.IndexOf (' '));
			}
			comboBoxPatterns.Text = (string) comboBoxPatterns.Items[0];
			txtbox.Focus ();
		}
		#endregion
		#region Clipboard escape functions non-Regex
		/// <summary> add backslash
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		public string Escape (string s)
		{
			if (null == s)
				return null;
			s = s.Replace ("\\", "\\\\");
			s = s.Replace ("\n", "\\n");
			s = s.Replace ("\r", "\\r");
			s = s.Replace ("\t", "\\t");
			return s;
		}
		/// <summary> remove backslash
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		public string Unescape (string s)
		{
			if (null == s)
				return null;
			s = s.Replace ("\\\\", "\x0");
			s = s.Replace ("\\n", "\n");
			s = s.Replace ("\\r", "\r");
			s = s.Replace ("\\t", "\t");
			s = s.Replace ("\x0", "\\");
			return s;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer IAUA End Time Ministry
United States United States
I am a retired Software Systems Design Engineer experienced with IEEE standards and the entire IEEE software development life cycle. Concept Exploration, Requirements, Design, Implementation, Test, Installation and Checkout, Operation and Maintenance. I enjoy working with people and solving customer problems.

I am currently a writer for my personal ministry: IAUA End Time Ministry

Comments and Discussions