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

XSL Editor Control

Rate me:
Please Sign up or sign in to vote.
4.84/5 (14 votes)
13 Jun 2009CPOL7 min read 48.3K   2.8K   64  
A basic WinForms XSL editor control
#region Using Directives
using Common.Library.Controls;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using XSL.Library.RegularExpressions;
#endregion Using Directives

namespace XSL.Library
{
    //TODO: add <!-- comment to tag list
    //TODO: add </closing tag to tag list
    //TODO: add xsl functions
    //TODO: add xsl attributes
    //TODO: owner-drawn listbox w/graphic icons

    public class IntellisenseMgr
    {
        #region Private Declarations
        private bool _enableIntellisense = true;
        private bool _inAttributeValue = false;
        private string _currentTagText = string.Empty;
        private string _currentTagName = string.Empty;
        private bool _isContextInfoCurrent = false;
        private int _currentTagLineStartPos = 0;
        
        private List<XSLTag> _xslTags;
        private List<string> _xslFunctions;

        private XSLRichTextBox _xslRichTextBox;
        private ListBox _intellisenseListBox;
        private ToolTip _intellisenseTooltip;

        private KeyPressEventArgs _lastKeyPress = null;
        private string _typed = string.Empty;
        private bool _wordMatched = false;
        
        private enum DataSourceModes
        {
            Undefined,
            Tag,
            Attribute
        }
        #endregion

        #region Constructors
        public IntellisenseMgr(XSLRichTextBox xslRichTextBox)
        {
            _xslRichTextBox = xslRichTextBox;            
            _xslRichTextBox.KeyPress += new KeyPressEventHandler(_xslRichTextBox_KeyPress);
            //_xslRichTextBox.TextChanged += new EventHandler(_xslRichTextBox_TextChanged);
            _xslRichTextBox.ContentChanged += new ContentChangedHandler(_xslRichTextBox_ContentChanged);
        }
        #endregion Constructors

        //*********************************************************************
        //* PROPERTIES
        //*********************************************************************
                                
        #region Private Properties
        private DataSourceModes DataSourceMode
        {
            get
            {
                string typeName = _intellisenseListBox.DataSource.GetType().FullName;

                if (typeName.Contains("XSLTag"))
                    return DataSourceModes.Tag;
                else if (typeName.Contains("XSLAttribute"))
                    return DataSourceModes.Attribute;
                else
                    return DataSourceModes.Undefined;
            }
        }

        private bool IsContextInfoCurrent
        {
            get
            {
                return _isContextInfoCurrent;
                //return null != _lastKeyPress;
            }
            set
            {
                _isContextInfoCurrent = value;
            }
        }
        #endregion Private Properties

        #region Public Virtual Properties
        public virtual string CurrentTagText
        {
            get
            {
                if (!IsContextInfoCurrent) _currentTagText = GetCurrentTagText();
                return _currentTagText;
            }
        }

        /// <summary>
        /// Returns the name of the current tag at textbox position
        /// If cursor is not within a tag, returns empty string
        /// </summary>
        public virtual string CurrentTagName
        {
            get
            {
                if (!IsContextInfoCurrent) _currentTagName = GetCurrentTagName();
                return _currentTagName;
            }
        }

        public virtual bool InAttributeValue
        {
            get
            {
                if (!IsContextInfoCurrent) _inAttributeValue = GetInAttributeValue();
                return _inAttributeValue;
            }
        }
        #endregion Public Virtual Properties

        #region Public Properties
        public XSLTag CurrentListTag
        {
            get
            {
                return FindTagByName(_intellisenseListBox.Text);
            }
        }

        public XSLAttribute CurrentListAttribute
        {
            get
            {
                return FindAttributeByName(_intellisenseListBox.Text);
            }
        }

        public XSLTag CurrentTag
        {
            get
            {
                string curTagName = this.CurrentTagName;
                return (0 == curTagName.Length) ? null : FindTagByName(curTagName);
            }
        }

        public bool IntellisenseInitialized
        {
            get
            {
                return _xslTags != null && _xslTags.Count > 0
                    && _intellisenseListBox != null;
            }
        }

        public bool EnableIntellisense
        {
            get { return _enableIntellisense; }
            set { _enableIntellisense = value; }
        }

        public List<string> XslFunctions
        {
            get { return _xslFunctions; }
            protected set { _xslFunctions = value; }
        }

        public List<XSLTag> XslTags
        {
            get { return _xslTags; }
            protected set { _xslTags = value; }
        }
        #endregion Public Properties

        //*********************************************************************
        //* METHODS
        //*********************************************************************

        #region Private Initialization Methods
        private void AddXSLTags()
        {
            _xslTags = XSLTags.GetXSLTags();
        }

        private void AddXSLFunctions()
        {
            _xslFunctions = XSLFunctions.GetXslFunctions();
        }

        private void InitializeIntellisense()
        {
            AddXSLTags();
            AddXSLFunctions();

            _intellisenseListBox = new ListBox();
            _intellisenseListBox.Name = "_intellisenseListBox";
            _intellisenseListBox.Size = new Size(250, 100);
            _intellisenseListBox.Visible = false;
            _intellisenseListBox.DataSource = _xslTags;
            _intellisenseListBox.DisplayMember = "TagName";
            _intellisenseListBox.Leave += new EventHandler(_intellisenseListBox_Leave);
            _intellisenseListBox.KeyDown += new KeyEventHandler(_intellisenseListBox_KeyDown);
            _intellisenseListBox.DoubleClick += new EventHandler(_intellisenseListBox_DoubleClick);
            _intellisenseListBox.SelectedValueChanged += new EventHandler(
                _intellisenseListBox_SelectedValueChanged);
            _intellisenseTooltip = new ToolTip();
            _intellisenseListBox.Cursor = Cursors.Arrow;
            _intellisenseListBox.Sorted = true;
            
            _xslRichTextBox.Controls.Add(_intellisenseListBox);
        }

        private bool PrepareIntellisense(string charPressed)
        {
            //ensure one-time, initial setup has been performed
            if (!IntellisenseInitialized) InitializeIntellisense();

            this.IsContextInfoCurrent = false;

            if (" " == charPressed)
            {
                XSLTag curTag = this.CurrentTag;

                if (null != curTag)
                {                    
                    if (!InAttributeValue)
                    {
                        _intellisenseListBox.DataSource = curTag.Attributes;
                        _intellisenseListBox.DisplayMember = "Name";
                        _intellisenseListBox.SelectedIndex = -1;
                    }
                    else
                    {
                        //TODO: functions(), variable list etc.
                        return false; // for now until ready
                    }
                }
                else
                {
                    return false;
                }
            }
            else if ("<" == charPressed)
            {
                _intellisenseListBox.DataSource = _xslTags;
                _intellisenseListBox.DisplayMember = "TagName";
            }

            this.IsContextInfoCurrent = true;
            return true;
        }
        #endregion

        #region Private Prep Methods
        private string GetCurrentTagName()
        {
            string tagName = string.Empty;
            string currentTagText = this.CurrentTagText;

            if (currentTagText.Length > 0)
            {
                int endPos = currentTagText.IndexOf(" ", 1);

                if (endPos == -1)
                {
                    endPos = currentTagText.Length - 1;
                }

                tagName = currentTagText.Substring(1, endPos - 1);
            }

            return tagName;
        }

        private string GetCurrentTagText()
        {
            string currentLine = _xslRichTextBox.CurrentLine;

            string tagText = string.Empty;
            int selStart = _xslRichTextBox.ColumnNumber - 1;

            if (selStart > currentLine.Length) return string.Empty;

            int tagStart = currentLine.LastIndexOf("<", (selStart > 0) ? selStart - 1 : selStart);

            if (tagStart > -1 && tagStart <= selStart)
            {
                int tagEnd = currentLine.IndexOf(">", tagStart);
                string buffer = string.Empty;

                if (tagEnd > tagStart)
                {
                    buffer = currentLine.Substring(tagStart, tagEnd - tagStart + 1);
                    int otherTagStart = buffer.LastIndexOf("<");

                    if (-1 != otherTagStart && otherTagStart > tagStart)
                    {
                        tagEnd = otherTagStart;
                    }

                    if (selStart > tagEnd)
                    {
                        tagText = string.Empty;
                    }
                    else
                    {
                        tagText = currentLine.Substring(tagStart, tagEnd - tagStart + 1);
                    }
                }
                else if (-1 == tagEnd)
                {
                    int otherTagStart = currentLine.IndexOf("<", tagStart + 1);

                    if (-1 != otherTagStart)
                    {
                        tagEnd = otherTagStart;
                    }
                    else
                    {
                        tagEnd = currentLine.Length - 1;
                    }

                    if (tagEnd > tagStart)
                    {
                        tagText = currentLine.Substring(tagStart, tagEnd - tagStart + 1);
                    }
                }
            }

            _currentTagLineStartPos = tagStart;
            return tagText;
        }

        private bool GetInAttributeValue()
        {            
            AttributeNameValueRegex attrRegEx = new AttributeNameValueRegex();
            string curTagText = this.CurrentTagText;

            for (Match attrMatch = attrRegEx.Match(curTagText);
                attrMatch.Success; attrMatch = attrMatch.NextMatch())
            {
                int quoteStart = attrMatch.Groups[0].Value.IndexOf("\"");
                int quoteEnd = attrMatch.Groups[0].Value.IndexOf("\"", quoteStart + 1);

                quoteStart += attrMatch.Index;
                quoteEnd += attrMatch.Index;

                if (quoteStart > -1 && quoteEnd > -1 && quoteEnd > quoteStart)
                {
                    int colPos = _xslRichTextBox.ColumnNumber -1;
                    if (colPos > quoteStart && colPos < quoteEnd)
                    {
                        return true;
                    }
                }
            }

            return false;
        }
        #endregion

        #region Private Show/Hide Methods
        private void HideIntellisense()
        {
            _intellisenseListBox.Visible = false;
            _typed = string.Empty;
            _wordMatched = false;
            HideIntellisenseToolTip();
            _xslRichTextBox.Focus();
        }

        private void HideIntellisenseToolTip()
        {
            _intellisenseTooltip.Hide(_xslRichTextBox);
        }

        private void ShowIntellisense(string charPressed)
        {
            const int MARGIN = 5;
            if (!PrepareIntellisense(charPressed)) return;

            Point intellisensePos = _xslRichTextBox.GetPositionFromCharIndex(
                _xslRichTextBox.SelectionStart);
            intellisensePos.Y = intellisensePos.Y + _xslRichTextBox.Font.Height + MARGIN;

            //calculate whether proposed intellisense position will be 
            //off-screen beneath the last visible line
            int lastVisibleLineStart = _xslRichTextBox.GetFirstCharIndexFromLine(
                _xslRichTextBox.LastVisibleLine);
            Point lastVisibleLineStartPos = _xslRichTextBox.GetPositionFromCharIndex(
                lastVisibleLineStart);

            if (intellisensePos.Y + _intellisenseListBox.Height > lastVisibleLineStartPos.Y)
            {
                // just show intellisense on top of text if at bottom
                // (scrolling down then showing beneath does not appear to work)
                intellisensePos.Y -= _intellisenseListBox.Height + MARGIN + _xslRichTextBox.Font.Height;
            }

            // see if intellisense x pos will put it off screen to the right
            if (intellisensePos.X + _intellisenseListBox.Width > _xslRichTextBox.Width)
            {
                int diff = (intellisensePos.X + _intellisenseListBox.Width) - _xslRichTextBox.Width;
                intellisensePos.X -= diff + 25;
            }

            _intellisenseListBox.Location = intellisensePos;
            _intellisenseListBox.Visible = true;
            _intellisenseListBox.Focus();
        }

        private string GetIntellisenseToolTip()
        {
            if (DataSourceMode == DataSourceModes.Tag)
            {
                XSLTag tag = CurrentListTag;
                return (null == tag) ? string.Empty : tag.ToolTip;
            }
            else if (DataSourceMode == DataSourceModes.Attribute)
            {
                XSLAttribute attr = CurrentListAttribute;
                return (null == attr) ? string.Empty : attr.ToolTip;
            }

            return string.Empty;
        }

        private void ShowIntellisenseToolTip()
        {
            int xPos = _intellisenseListBox.Location.X + _intellisenseListBox.Width;
            int yPos = _intellisenseListBox.Location.Y;
            int duration = 30 * 1000;

            _intellisenseTooltip.Show(GetIntellisenseToolTip(), _xslRichTextBox, 
                xPos, yPos, duration);
        }
        #endregion

        #region Private Methods
        private XSLTag FindTagByName(string tagName)
        {
            XSLTag tag = _xslTags.Find(
                delegate(XSLTag t)
                {
                    return 0 == string.Compare(t.TagName, tagName, true);
                });
            return tag;
        }

        private XSLAttribute FindAttributeByName(string name)
        {
            XSLAttribute attr = CurrentTag.Attributes.Find(
                delegate(XSLAttribute a)
                {
                    return 0 == string.Compare(a.Name, name, true);
                });
            return attr;
        }

        private void InsertIntellisenseText(Keys keyPressed)
        {
            bool hide = true;
            XSLTag currentTag = CurrentListTag;

            //OemPeriod = ">" w/shift (assumed)
            //OemQuestion = "/"
            if (keyPressed == Keys.OemPeriod || keyPressed == Keys.Enter)
            {
                if (currentTag.AllowsContent)
                {
                    _xslRichTextBox.SelectedText = _intellisenseListBox.Text +
                        "></" + _intellisenseListBox.Text + ">";
                    _xslRichTextBox.SelectionStart -= _intellisenseListBox.Text.Length + 3;
                }
                else
                {
                    _xslRichTextBox.SelectedText = _intellisenseListBox.Text + "/>";
                }
            }
            else if (keyPressed == Keys.OemQuestion) // "/"
            {
                _xslRichTextBox.SelectedText = _intellisenseListBox.Text + "/>";
            }
            else if (keyPressed == Keys.Tab || keyPressed == Keys.Space)
            {
                _xslRichTextBox.SelectedText = _intellisenseListBox.Text + " ";
                ShowIntellisense(" ");
                hide = false;
            }

            if (hide)
            {
                HideIntellisense();
            }
            //TODO: check tag and determine if <this/> or <this></this>
            //TODO: tag attributes?
        }                
        #endregion

        //*********************************************************************
        //* EVENTS
        //*********************************************************************

        #region XSLRichTextBox Events
        void _xslRichTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (EnableIntellisense)
            {
                //TODO: tag help/description?
                //TODO: tag attribute intellisense?
                if ("<" == e.KeyChar.ToString())
                {
                    //delay showing intellisense until after keypress and text changed
                    _lastKeyPress = e;
                    //ShowIntellisense("<");
                }
                else if (" " == e.KeyChar.ToString())
                {
                    //delay showing intellisense until after keypress and text changed
                    _lastKeyPress = e;
                    //ShowIntellisense(" "); //TODO: revisit soon
                }
                else
                {
                    _lastKeyPress = null;
                }
            }
        }

        /// <summary>
        /// Basically OnTextChanged but this is here b/c syntax rich textbox
        /// turns off text changed events in many cases
        /// </summary>
        void _xslRichTextBox_ContentChanged()
        {
            if (EnableIntellisense && null != _lastKeyPress)
            {
                ShowIntellisense(_lastKeyPress.KeyChar.ToString());
                _lastKeyPress = null;
            }
        }        
        #endregion

        #region Intellisense events
        void _intellisenseListBox_SelectedValueChanged(object sender, EventArgs e)
        {
            ShowIntellisenseToolTip();
        }

        void _intellisenseListBox_KeyDown(object sender, KeyEventArgs e)
        {
            //Debug.WriteLine(e.KeyCode);
            Debug.WriteLine(_typed);

            switch (e.KeyCode)
            {
                case Keys.Back:
                    if (_typed.Length > 0)
                    {
                        _typed = _typed.Substring(0, _typed.Length - 1);
                    }
                    break;
                case Keys.Delete:
                    _typed = string.Empty;
                    break;
                case Keys.Escape:
                    HideIntellisense();
                    break;
                case Keys.Enter:
                    InsertIntellisenseText(e.KeyCode);
                    break;
                case Keys.Tab:
                    e.Handled = true;
                    InsertIntellisenseText(e.KeyCode);
                    break;
                case Keys.Space:
                    InsertIntellisenseText(e.KeyCode);
                    break;
                case Keys.OemQuestion: // "/"
                    InsertIntellisenseText(e.KeyCode);
                    break;
                case Keys.OemPeriod:
                    if (e.Shift) // ">"
                    {
                        InsertIntellisenseText(Keys.OemPeriod);
                    }
                    break;
                case Keys.Oem1:
                    if (e.Shift) // ":"
                    {
                        _typed += ":";
                    }
                    break;
                default:                    

                    if (Char.IsLetterOrDigit((char)e.KeyValue))
                    {
                        char val = (char)e.KeyValue;
                        _typed += val;

                        _wordMatched = false;

                        // Loop through all the items in the listview, looking
                        // for one that starts with the letters typed
                        for (int i = 0; i < _intellisenseListBox.Items.Count; i++)
                        {
                            string itemText = _intellisenseListBox.GetItemText(_intellisenseListBox.Items[i]);
                            
                            if (itemText.ToLower().StartsWith(_typed.ToLower()))
                            {
                                _wordMatched = true;
                                _intellisenseListBox.SelectedIndex = i;
                                break;
                            }
                        }
                    }
                    //System.Diagnostics.Debug.WriteLine(e.KeyCode);
                    break;
            }            
        }

        void _intellisenseListBox_Leave(object sender, EventArgs e)
        {
            HideIntellisense();
        }

        void _intellisenseListBox_DoubleClick(object sender, EventArgs e)
        {
            InsertIntellisenseText(Keys.Enter);
        }
        #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
Software Developer (Senior) TeamHealth
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions