Click here to Skip to main content
15,886,565 members
Articles / Desktop Programming / Windows Forms

Storm - the world's best IDE framework for .NET

Rate me:
Please Sign up or sign in to vote.
4.96/5 (82 votes)
4 Feb 2010LGPL311 min read 274.7K   6.5K   340  
Create fast, flexible, and extensible IDE applications easily with Storm - it takes nearly no code at all!
//
//    ___ _____ ___  ___ __  __ 
//   / __|_   _/ _ \| _ \  \/  |
//   \__ \ | || (_) |   / |\/| |
//   |___/ |_| \___/|_|_\_|  |_|
// 
//   Storm.TextEditor.dll
//   ��������������������
//     Storm.TabControl.dll was created under the LGPL 
//     license. Some of the code was created from scratch, 
//     some was not. Code not created from scratch was 
//     based on the DotNetFireball framework and evolved 
//     from that. 
//     
//     What I mostly did in this library was that I 
//     cleaned up the code, structured it, documentated 
//     it and added new features. 
//     
//     Although it might not sound like it, it was very
//     hard and took a long (pretty much a shitload)
//     time. Why? Well, this was* some of the crappiest,
//     most unstructured, undocumentated, ugly code I've
//     ever seen. It would actually have taken me less 
//     time to create it from scratch, but I figured that
//     out too late. Figuring out what the code actually
//     did and then documentating it was (mostly) a 
//     day-long process. Well, I hope you enjoy some of
//     my hard work. /rant
//     
//     Of course some/most of it is made from scratch by me.
//     *Yes, was. It isn't now. :) Some of the naming 
//     conventions might still seem a little bit off though.
//
//   What is Storm?
//   ��������������
//     Storm is a set of dynamic link libraries used in 
//     Theodor "Vestras" Storm Kristensen's application 
//     "Moonlite".
//     
//
//   Thanks:
//   �������
//     - The DotNetFireball team for creating and publishing 
//       DotNetFireball which I based some of my code on.
//
//
//   Copyright (c) Theodor "Vestras" Storm Kristensen 2009
//   �����������������������������������������������������
//


namespace Storm.TextEditor.Interacting
{
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Diagnostics.Design;
    using System.Diagnostics.SymbolStore;
    using System.IO;
    using System.Reflection;
    using System.Resources;
    using System.Text;
    using System.Windows.Forms;

    using Storm.TextEditor;
    using Storm.TextEditor.Controls;
    using Storm.TextEditor.Controls.Core;
    using Storm.TextEditor.Controls.Core.Globalization;
    using Storm.TextEditor.Controls.Core.Timers;
    using Storm.TextEditor.Controls.IntelliMouse;
    using Storm.TextEditor.Document;
    using Storm.TextEditor.Document.Exporters;
    using Storm.TextEditor.Forms;
    using Storm.TextEditor.Interacting;
    using Storm.TextEditor.Painting;
    using Storm.TextEditor.Parsing;
    using Storm.TextEditor.Parsing.Base;
    using Storm.TextEditor.Parsing.Classes;
    using Storm.TextEditor.Parsing.Language;
    using Storm.TextEditor.Preset;
    using Storm.TextEditor.Preset.Painting;
    using Storm.TextEditor.Preset.TextDraw;
    using Storm.TextEditor.Printing;
    using Storm.TextEditor.Utilities;
    using Storm.TextEditor.Win32;

	/// <summary>
	/// A class used for defining selection in a TextEditor.
	/// </summary>
	public class Selection
	{
        #region Members
        // Basic members.
        private TextRange _bounds;
        private TextEditorBase _control;

        /// <summary>
        /// Event fired when the selection has changed.
        /// </summary>
        public event EventHandler Change = null;
        #endregion

		#region Properties
		/// <summary>
		/// Gets or sets the currently selected text. If some text is already selected, overwrites that text.
		/// </summary>
		public String Text
		{
			get
			{
				if (!IsValid)
					return "";
				else
					return _control.Document.GetRange(LogicalBounds);
			}
			set
			{
				if (Text == value) return;

                // Bug fix.
				string tmp = value.Replace(Environment.NewLine, "\n");
				tmp = tmp.Replace("\n", Environment.NewLine);
				value = tmp;

				TextPoint oCaretPos = _control.Caret.Position;
				int nCaretX = oCaretPos.X;
				int nCaretY = oCaretPos.Y;

				_control.Document.StartUndoCapture();
				DeleteSelection();

				_control.Document.InsertText(value, oCaretPos.X, oCaretPos.Y);
				SelLength = value.Length;

				if (nCaretX != oCaretPos.X || nCaretY != oCaretPos.Y)
					_control.Caret.Position = new TextPoint(Bounds.LastColumn, Bounds.LastRow);

				_control.Document.EndUndoCapture();
				_control.Document.InvokeChange();
			}
		}

		/// <summary>
		/// Returns the normalized positions of the selection.
		/// Swapping start and end values if the selection is reversed.
		/// </summary>
		public TextRange LogicalBounds
		{
			get
			{
				TextRange r = new TextRange();
				if (Bounds.FirstRow < Bounds.LastRow)
					return Bounds;
				else if (Bounds.FirstRow == Bounds.LastRow && 
                    Bounds.FirstColumn < Bounds.LastColumn)

					return Bounds;
				else
				{
					r.FirstColumn = Bounds.LastColumn;
					r.FirstRow = Bounds.LastRow;

					r.LastColumn = Bounds.FirstColumn;
					r.LastRow = Bounds.FirstRow;
					return r;
				}
			}
		}

		/// <summary>
		/// Gets whether the current selection contains one or more characters.
		/// </summary>
		public bool IsValid
		{
			get
			{
				return (LogicalBounds.FirstColumn != LogicalBounds.LastColumn ||
					LogicalBounds.FirstRow != LogicalBounds.LastRow);
			}
		}

		/// <summary>
		/// Gets or sets the length of the selection.
		/// </summary>
		public int SelLength
		{
			get
			{
				TextPoint p1 = new TextPoint(Bounds.FirstColumn, Bounds.FirstRow);
				TextPoint p2 = new TextPoint(Bounds.LastColumn, Bounds.LastRow);
				
                int i1 = _control.Document.PointToIntPos(p1);
				int i2 = _control.Document.PointToIntPos(p2);

				return i2 - i1;
			}
			set { SelEnd = SelStart + value; }
		}

		/// <summary>
		/// Gets or sets the current selection end index. (SelStart + SelLength basically)
		/// </summary>
		public int SelEnd
		{
			get
			{
				TextPoint p = new TextPoint(Bounds.LastColumn, Bounds.LastRow);
				return _control.Document.PointToIntPos(p);
			}
			set
			{
				TextPoint p = _control.Document.IntPosToPoint(value);

				Bounds.LastColumn = p.X;
				Bounds.LastRow = p.Y;
			}
		}


		/// <summary>
		/// Gets or sets the current selection start index.
		/// </summary>
		public int SelStart
		{
			get
			{
				TextPoint p = new TextPoint(Bounds.FirstColumn, Bounds.FirstRow);
				return _control.Document.PointToIntPos(p);
			}
			set
			{
				TextPoint p = _control.Document.IntPosToPoint(value);

				Bounds.FirstColumn = p.X;
				Bounds.FirstRow = p.Y;
			}
		}

		/// <summary>
		/// Gets or sets the logical selection start index.
		/// </summary>
		public int LogicalSelStart
		{
			get
			{
				TextPoint p = new TextPoint(LogicalBounds.FirstColumn, LogicalBounds.FirstRow);
				return _control.Document.PointToIntPos(p);
			}
			set
			{
				TextPoint p = _control.Document.IntPosToPoint(value);

				Bounds.FirstColumn = p.X;
				Bounds.FirstRow = p.Y;
			}
		}

        /// <summary>
        /// Gets or sets the selection bounds as a TextRange. 
        /// </summary>
        public TextRange Bounds
        {
            get { return _bounds; }
            set
            {
                if (_bounds != null)
                    _bounds.Change -= new EventHandler(Bounds_Change);

                _bounds = value;
                _bounds.Change += new EventHandler(Bounds_Change);
                OnChange();
            }
        }
		#endregion

        #region EventHandlers
        private void Bounds_Change(object s, EventArgs e)
        { OnChange(); }

        private void PositionChange(object s, EventArgs e)
        { OnChange(); }

        private void OnChange()
        {
            if (Change != null)
                Change(this, null);
        }
        #endregion

		#region Methods
		/// <summary>
		/// Increases the level of indentation at the current selection by one.
		/// </summary>
		public void Indent()
		{
			if (!IsValid)
				return;

			Row xtr = null;
			UndoBlockCollection ActionGroup = new UndoBlockCollection();
			for (int i = LogicalBounds.FirstRow; i <= LogicalBounds.LastRow; i++)
			{
				xtr = _control.Document[i];
				UndoBlock b = new UndoBlock();
				b.Action = UndoAction.InsertRange;

				if (_control.KeepTabs == true)
				{
					xtr.Text = "\t" + xtr.Text;
					b.Text = "\t";
				}
				else
				{
					string indent = new string(' ', _control.TabSpaces);
					xtr.Text = indent + xtr.Text;
					b.Text = indent;
				}

				b.Position.X = 0;
				b.Position.Y = i;

				ActionGroup.Add(b);
			}

			if (ActionGroup.Count > 0)
				_control.Document.AddToUndoList(ActionGroup);

			Bounds = LogicalBounds;
			Bounds.FirstColumn = 0;
			Bounds.LastColumn = xtr.Text.Length;

			_control.Caret.Position.X = LogicalBounds.LastColumn;
			_control.Caret.Position.Y = LogicalBounds.LastRow;
		}

		/// <summary>
        /// Decreases the level of indentation at the current selection by one.
		/// </summary>
		public void Outdent()
		{
			if (!IsValid)
				return;

			Row xtr = null;
			UndoBlockCollection ActionGroup = new UndoBlockCollection();
			for (int i = LogicalBounds.FirstRow; i <= LogicalBounds.LastRow; i++)
			{
				xtr = _control.Document[i];
				UndoBlock b = new UndoBlock();

				b.Action = UndoAction.DeleteRange;
				b.Position.X = 0;
				b.Position.Y = i;

				ActionGroup.Add(b);

				string s = xtr.Text;
				if (s.StartsWith("\t"))
				{
					b.Text = s.Substring(0, 1);
					s = s.Substring(1);
				}

				if (s.StartsWith(new string(' ', _control.TabSpaces)))
				{
					b.Text = s.Substring(0, _control.TabSpaces);
					s = s.Substring(_control.TabSpaces);
				}

				xtr.Text = s;
			}

			if (ActionGroup.Count > 0)
				_control.Document.AddToUndoList(ActionGroup);

			Bounds = LogicalBounds;
			Bounds.FirstColumn = 0;
			Bounds.LastColumn = xtr.Text.Length;

			_control.Caret.Position.X = LogicalBounds.LastColumn;
			_control.Caret.Position.Y = LogicalBounds.LastRow;
		}

        /// <summary>
        /// Adds a string to the current selection.
        /// </summary>
        /// <param name="pattern">The string that will be added to the selection.</param>
		public void Indent(string pattern)
		{
			if (!IsValid)
				return;

			Row xtr = null;
			UndoBlockCollection ActionGroup = new UndoBlockCollection();
			for (int i = LogicalBounds.FirstRow; i <= LogicalBounds.LastRow; i++)
			{
				xtr = _control.Document[i];
				xtr.Text = pattern + xtr.Text;

				UndoBlock b = new UndoBlock();
				b.Action = UndoAction.InsertRange;
				b.Text = pattern;

				b.Position.X = 0;
				b.Position.Y = i;

				ActionGroup.Add(b);
			}

			if (ActionGroup.Count > 0)
				_control.Document.AddToUndoList(ActionGroup);

			Bounds = LogicalBounds;
			Bounds.FirstColumn = 0;
			Bounds.LastColumn = xtr.Text.Length;

			_control.Caret.Position.X = LogicalBounds.LastColumn;
			_control.Caret.Position.Y = LogicalBounds.LastRow;
		}

        /// <summary>
        /// Adds a string to the current selection.
        /// </summary>
        /// <param name="pattern">The string that will be added to the selection.</param>
		public void Outdent(string pattern)
		{
			if (!IsValid)
				return;

			Row xtr = null;
			UndoBlockCollection ActionGroup = new UndoBlockCollection();
			for (int i = LogicalBounds.FirstRow; i <= LogicalBounds.LastRow; i++)
			{
				xtr = _control.Document[i];
				UndoBlock b = new UndoBlock();

				b.Action = UndoAction.DeleteRange;
				b.Position.X = 0;
				b.Position.Y = i;

				ActionGroup.Add(b);

				string s = xtr.Text;
				if (s.StartsWith(pattern))
				{
					b.Text = s.Substring(0, pattern.Length);
					s = s.Substring(pattern.Length);
				}

				xtr.Text = s;
			}

			if (ActionGroup.Count > 0)
				_control.Document.AddToUndoList(ActionGroup);

			Bounds = LogicalBounds;
			Bounds.FirstColumn = 0;
			Bounds.LastColumn = xtr.Text.Length;

			_control.Caret.Position.X = LogicalBounds.LastColumn;
			_control.Caret.Position.Y = LogicalBounds.LastRow;
		}

		/// <summary>
		/// Delete the active selection.
		/// <seealso cref="ClearSelection"/>
		/// </summary>
		public void DeleteSelection()
		{
			TextRange r = LogicalBounds;

			int x = r.FirstColumn;
			int y = r.FirstRow;

			_control.Document.DeleteRange(r);
			_control.Caret.Position.X = x;
			_control.Caret.Position.Y = y;

			ClearSelection();
			_control.ScrollIntoView();
		}

		/// <summary>
		/// Clear the active selection.
		/// <seealso cref="DeleteSelection"/>
		/// </summary>
		public void ClearSelection()
		{
            Bounds.FirstColumn = _control.Caret.Position.X;
            Bounds.FirstRow = _control.Caret.Position.Y;

            Bounds.LastColumn = _control.Caret.Position.X;
            Bounds.LastRow = _control.Caret.Position.Y;
		}

		/// <summary>
		/// Sets the bounds of the selection to the Caret start and end positions.
		/// </summary>
		public void MakeSelection()
		{
			Bounds.LastColumn = _control.Caret.Position.X;
			Bounds.LastRow = _control.Caret.Position.Y;
		}

		/// <summary>
		/// Selects all text in the TextEditor.
		/// </summary>
		public void SelectAll()
		{
			Bounds.FirstColumn = 0;
			Bounds.FirstRow = 0;
			Bounds.LastColumn = _control.Document[_control.Document.Count - 1].Text.Length;
			Bounds.LastRow = _control.Document.Count - 1;
			_control.Caret.Position.X = Bounds.LastColumn;
			_control.Caret.Position.Y = Bounds.LastRow;
			_control.ScrollIntoView();
		}
		#endregion

        /// <summary>
        /// Initializes the Selection.
        /// </summary>
        public Selection(TextEditorBase control)
        {
            _control = control;
            Bounds = new TextRange();
        }
	}
}

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 GNU Lesser General Public License (LGPLv3)



Comments and Discussions