Click here to Skip to main content
15,894,630 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 277.2K   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 that simulates the Windows/RichTextBox caret. 
	/// </summary>
	public sealed class Caret
	{
		#region Members
        // Basic members.
        private bool _blink;
		private int _oldLogicalXPos = 0;

		private TextPoint _position;
		private TextEditorBase _control;

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

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

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

        #region Properties
        /// <summary>
        /// Gets or sets the position of the caret.
        /// </summary>
        public TextPoint Position
        {
            get { return _position; }
            set
            {
                _position = value;
                _position.Change += new EventHandler(PositionChange);
                OnChange();
            }
        }

        /// <summary>
        /// Gets or sets whether the caret should blink.
        /// </summary>
        public bool Blink
        {
            get { return _blink; }
            set { _blink = value; }
        }

        /// <summary>
        /// Gets the word that the caret is placed on.
        /// This only applies if the active row is fully parsed.
        /// </summary>
        public Word CurrentWord
        { get { return _control.Document.GetWordFromPos(Position); } }

        /// <summary>
        /// Gets the row that the caret is placed on.
        /// </summary>
        public Row CurrentRow
        { get { return _control.Document[Position.Y]; } }

        /// <summary>
        /// Gets the current offset of the caret.
        /// </summary>
        public int Offset
        {
            get
            {
                if (Position.Y >= _control.Document.Lines.Length)
                    return 0;
                TextRange tr = new TextRange(0, 0, Position.X, Position.Y);
                int l = _control.Document.GetRange(tr).Length;
                int tl = _control.Document.Text.Length;
                return Math.Min(tl, l);
            }
        }

        /// <summary>
        /// Gets or sets the logical position of the caret.
        /// </summary>
        public TextPoint LogicalPosition
        {
            get
            {
                if (Position.X < 0)
                    return new TextPoint(0, Position.Y);

                Row xtr = CurrentRow;
                int x = 0;
                if (xtr == null)
                    return new TextPoint(0, 0);

                int max = xtr.Text.Length;
                int Padd = Math.Max(Position.X - xtr.Text.Length, 0);
                string PaddStr = new String(' ', Padd);
                string TotStr = xtr.Text + PaddStr;

                foreach (char c in TotStr.ToCharArray(0, Position.X))
                {
                    if (c == '\t')
                        x += _control.TabSpaces - (x % _control.TabSpaces);
                    else
                        x++;
                }

                return new TextPoint(x, Position.Y);
            }
            set
            {
                Row xtr = CurrentRow;
                int x = 0;
                int xx = 0;

                if (value.X > 0)
                {
                    char[] chars = xtr.Text.ToCharArray();
                    int i = 0;

                    while (x < value.X)
                    {
                        char c;
                        if (i < chars.Length)
                            c = chars[i];
                        else
                            c = ' ';

                        xx++;
                        if (c == '\t')
                            x += _control.TabSpaces - (x % _control.TabSpaces);
                        else
                            x++;

                        i++;
                    }
                }

                Position.Y = value.Y;
                Position.X = xx;
            }
        }
        #endregion

        #region Helpers
        private void RememberXPos()
		{ _oldLogicalXPos = LogicalPosition.X; }

		/// <summary>
		/// Confines the caret to a valid position within the active document.
		/// </summary>
		public void CropPosition()
		{
			if (Position.X < 0)
				Position.X = 0;

			if (Position.Y >= _control.Document.Count)
				Position.Y = _control.Document.Count - 1;

			if (Position.Y < 0)
				Position.Y = 0;

			Row xtr = CurrentRow;
			if (Position.X > xtr.Text.Length && !_control.VirtualWhitespace)
				Position.X = xtr.Text.Length;
		}
		#endregion

		#region Methods
		/// <summary>
		/// Moves the caret right one step.
		/// </summary>
		public void MoveRight(bool select)
		{
			CropPosition();
			Position.X ++;

			if (CurrentRow.IsCollapsed)
			{
				if (Position.X > CurrentRow.Expansion_EndChar)
				{
					Position.Y = CurrentRow.Expansion_EndRow.Index;
					Position.X = CurrentRow.Expansion_EndRow.Expansion_StartChar;
					CropPosition();
				}

				RememberXPos();
				CaretMoved(select);
			}
			else
			{
				Row xtr = CurrentRow;
				if (Position.X > xtr.Text.Length && !_control.VirtualWhitespace)
				{
					if (Position.Y < _control.Document.Count - 1)
					{
						MoveDown(select);
						Position.X = 0;
						CropPosition();
					}
					else
						CropPosition();
				}

				RememberXPos();
				CaretMoved(select);
			}
		}

		/// <summary>
		/// Moves the caret up one row.
		/// </summary>
		/// <param name="Select">True if a selection should be created from the current caret pos to the new pos</param>
		public void MoveUp(bool select)
		{
			CropPosition();
			int x = _oldLogicalXPos;
			
			if (CurrentRow != null && CurrentRow.PrevVisibleRow != null)
			{
				Position.Y = CurrentRow.PrevVisibleRow.Index;
				if (CurrentRow.IsCollapsed)
					x = 0;
			}
	
			CropPosition();
			LogicalPosition = new TextPoint(x, Position.Y);
			CropPosition();
	        CaretMoved(select);
		}

		/// <summary>
		/// Moves the caret up x rows.
		/// </summary>
		/// <param name="rows">Number of rows the caret should be moved up.</param>
        public void MoveUp(int rows, bool select)
        {
            CropPosition();
            int x = _oldLogicalXPos;

            int pos = CurrentRow.VisibleIndex;
            pos -= rows;
            if (pos < 0)
                pos = 0;

            Row r = _control.Document.VisibleRows[pos];
            pos = r.Index;


            Position.Y = pos;
            if (CurrentRow.IsCollapsed)
                x = 0;

            CropPosition();
            LogicalPosition = new TextPoint(x, Position.Y);
            CropPosition();
            CaretMoved(select);
        }

		/// <summary>
		/// Moves the caret down x rows.
		/// </summary>
		/// <param name="rows">The number of rows the caret should be moved down.</param>
        public void MoveDown(int rows, bool select)
        {
            int x = _oldLogicalXPos;
            CropPosition();

            try
            {
                int pos = CurrentRow.VisibleIndex;
                pos += rows;
                if (pos > _control.Document.VisibleRows.Count - 1)
                    pos = _control.Document.VisibleRows.Count - 1;

                Row r = _control.Document.VisibleRows[pos];
                pos = r.Index;
                Position.Y = pos;
                if (CurrentRow.IsCollapsed)
                    x = 0;
            }
            finally
            {
                CropPosition();
                LogicalPosition = new TextPoint(x, Position.Y);
                CropPosition();
                CaretMoved(select);
            }
        }


		/// <summary>
		/// Moves the caret down one row.
		/// </summary>
        public void MoveDown(bool select)
        {
            CropPosition();
            int x = _oldLogicalXPos;

            Row r = CurrentRow;
            Row r2 = r.NextVisibleRow;
            if (r2 != null)
            {
                Position.Y = r2.Index;
                if (CurrentRow.IsCollapsed)
                    x = 0;
            }

            CropPosition();
            LogicalPosition = new TextPoint(x, Position.Y);
            CropPosition();
            CaretMoved(select);
        }

		/// <summary>
		/// Moves the caret left one step.
		/// </summary>
		public void MoveLeft(bool select)
		{
			CropPosition();
			Position.X--;

			if (CurrentRow.IsCollapsedEndPart)
			{
				if (Position.X < CurrentRow.Expansion_StartChar)
				{
					if (CurrentRow.Expansion_StartRow.Index == -1)
						Debugger.Break();

					Position.Y = CurrentRow.Expansion_StartRow.Index;
					Position.X = CurrentRow.Expansion_StartRow.Expansion_EndChar;
					CropPosition();
				}

				RememberXPos();
				CaretMoved(select);
			}
			else
			{
				if (Position.X < 0)
				{
					if (Position.Y > 0)
					{
						MoveUp(select);
						CropPosition();

						Row xtr = CurrentRow;
						Position.X = xtr.Text.Length;
						if (CurrentRow.IsCollapsed)
						{
							Position.Y = CurrentRow.Expansion_EndRow.Index;
							Position.X = CurrentRow.Text.Length;
						}
					}
					else
						CropPosition();
				}

				RememberXPos();
				CaretMoved(select);
			}
		}

		/// <summary>
		/// Moves the caret to the first non whitespace column at the active row.
		/// </summary>
		public void MoveHome(bool select)
		{
			CropPosition();
			if (CurrentRow.IsCollapsedEndPart)
			{
				Position.Y = CurrentRow.Expansion_StartRow.Index;
				MoveHome(select);
			}
			else
			{
				int i = CurrentRow.GetLeadingWhitespace().Length;
				if (Position.X == i)
					Position.X = 0;
				else
					Position.X = i;

				RememberXPos();
				CaretMoved(select);
			}
		}

		/// <summary>
		/// Moves the caret to the end of a row ignoring any whitespace characters at the end of the row.
		/// </summary>
		public void MoveEnd(bool select)
		{
			if (CurrentRow.IsCollapsed)
			{
				Position.Y = CurrentRow.Expansion_EndRow.Index;
				MoveEnd(select);
			}
			else
			{
				CropPosition();
				Row xtr = CurrentRow;
				Position.X = xtr.Text.Length;

				RememberXPos();
				CaretMoved(select);
			}
		}

		public void CaretMoved(bool select)
		{
			_control.ScrollIntoView();
			if (!select)
				_control.Selection.ClearSelection();
			else
				_control.Selection.MakeSelection();
		}

		/// <summary>
		/// Moves the caret to the first column of the active row.
		/// </summary>
		public void MoveAbsoluteHome(bool select)
		{
			Position.X = 0;
			Position.Y = 0;
			RememberXPos();
			CaretMoved(select);
		}

		/// <summary>
		/// Moves the caret to the absolute end of the active row.
		/// </summary>
		public void MoveAbsoluteEnd(bool select)
		{
			Position.X = _control.Document[_control.Document.Count - 1].Text.Length;
			Position.Y = _control.Document.Count - 1;
			RememberXPos();
			CaretMoved(select);
		}

        public Segment CurrentSegment()
        { return _control.Document.GetSegmentFromPos(Position); }

        /// <summary>
        /// Sets the position of the caret.
        /// </summary>
        public void SetPos(TextPoint pos)
        {
            Position = pos;
            RememberXPos();
        }
		#endregion

        /// <summary>
        /// Initializes the Caret.
        /// </summary>
        public Caret(TextEditorBase control)
        {
            Position = new TextPoint(0, 0);
            _control = control;
        }

	}
}

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