File: Rules\Design\Dialogs\IntellisenseTextBox.cs
Project: ndp\cdf\src\WF\Activities\System.Workflow.Activities.csproj (System.Workflow.Activities)
// ---------------------------------------------------------------------------
// Copyright (C) 2006 Microsoft Corporation All Rights Reserved
// ---------------------------------------------------------------------------
 
#define CODE_ANALYSIS
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
 
namespace System.Workflow.Activities.Rules.Design
{
    #region IntellisenseTextBox
 
    internal partial class IntellisenseTextBox : TextBox
    {
        #region members and constructors
 
        private ListView listBoxAutoComplete = new ListView();
        public event EventHandler<AutoCompletionEventArgs> PopulateAutoCompleteList;
        public event EventHandler<AutoCompletionEventArgs> PopulateToolTipList;
        int oldSelectionStart;
 
        enum memberIcons
        {
            Default = 0,
            Type = 1,
            PublicMethod = 2,
            PrivateMethod = 3,
            InternalMethod = 4,
            ProtectedMethod = 5,
            PublicProperty = 6,
            PrivateProperty = 7,
            InternalProperty = 8,
            ProtectedProperty = 9,
            PublicField = 10,
            PrivateField = 11,
            InternalField = 12,
            ProtectedField = 13,
            Keyword = 14,
            ExtensionMethod = 15
        }
 
        public IntellisenseTextBox()
        {
            InitializeComponent();
 
            this.AcceptsReturn = true;
 
            this.listBoxAutoComplete.FullRowSelect = true;
            this.listBoxAutoComplete.MultiSelect = false;
            this.listBoxAutoComplete.SmallImageList = this.autoCompletionImageList;
            this.listBoxAutoComplete.LargeImageList = this.autoCompletionImageList;
            this.listBoxAutoComplete.View = System.Windows.Forms.View.Details;
            this.listBoxAutoComplete.HeaderStyle = ColumnHeaderStyle.None;
            this.listBoxAutoComplete.Columns.Add(Messages.No, this.listBoxAutoComplete.Size.Width);
            this.listBoxAutoComplete.CausesValidation = false;
            this.listBoxAutoComplete.Sorting = SortOrder.Ascending;
 
            this.listBoxAutoComplete.Visible = false;
            this.KeyPress += new KeyPressEventHandler(IntellisenseTextBox_KeyPress);
            this.HandleCreated += new EventHandler(IntellisenseTextBox_HandleCreated);
 
        }
 
 
        #endregion
 
        #region IntellisenseTextBox event handlers
 
        private void IntellisenseTextBox_HandleCreated(object sender, EventArgs e)
        {
            if (this.TopLevelControl != null)
            {
                this.TopLevelControl.Controls.Add(this.listBoxAutoComplete);
                this.listBoxAutoComplete.DoubleClick += new EventHandler(listBoxAutoComplete_DoubleClick);
                this.listBoxAutoComplete.SelectedIndexChanged += new EventHandler(listBoxAutoComplete_SelectedIndexChanged);
                this.listBoxAutoComplete.Enter += new EventHandler(listBoxAutoComplete_Enter);
            }
        }
 
        void IntellisenseTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            string currentValue = this.Text;
            int selectionStart = this.SelectionStart;
            int selectionLength = this.SelectionLength;
            StringBuilder projectedValue = new StringBuilder(currentValue.Substring(0, selectionStart));
            projectedValue.Append(currentValue.Substring(selectionStart + selectionLength));
 
            char c = e.KeyChar;
            if (c == '.')
            {
                if (this.listBoxAutoComplete.Visible)
                {
                    this.SelectItem();
                    HideIntellisenceDropDown();
                    IntellisenseTextBox_KeyPress(sender, e);
                }
                else
                {
                    projectedValue.Insert(selectionStart, '.');
                    UpdateIntellisenceDropDown(projectedValue.ToString().Substring(0, selectionStart + 1));
                    ShowIntellisenceDropDown(selectionStart);
                    IntellisenseTextBox_KeyDown(sender, new KeyEventArgs(Keys.Down)); // fake down arrow to select first item
                }
            }
            else if (c == '(')
            {
                if (listBoxAutoComplete.Visible)
                {
                    this.SelectItem();
                    HideIntellisenceDropDown();
                    IntellisenseTextBox_KeyPress(sender, e);
                }
                else
                {
                    projectedValue.Insert(selectionStart, '(');
                    ShowToolTip(selectionStart, projectedValue.ToString().Substring(0, selectionStart + 1));
                }
            }
            else if (!this.listBoxAutoComplete.Visible
                && CurrentPrefix.Length == 0
                && (c == '_' || char.IsLetter(c) || char.GetUnicodeCategory(c) == UnicodeCategory.LetterNumber))
            {
                projectedValue.Insert(selectionStart, c);
                UpdateIntellisenceDropDown(projectedValue.ToString().Substring(0, selectionStart + 1));
                ShowIntellisenceDropDown(selectionStart);
                if (this.listBoxAutoComplete.Visible)
                    IntellisenseTextBox_KeyDown(sender, new KeyEventArgs(Keys.Down)); // fake down arrow to select first item
            }
            else if (this.listBoxAutoComplete.Visible)
            {
                projectedValue.Insert(selectionStart, c);
                UpdateAutoCompleteSelection(CurrentPrefix + c);
            }
        }
 
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private void IntellisenseTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            string currentValue = this.Text;
            int selectionStart = this.SelectionStart;
            int selectionLength = this.SelectionLength;
 
            StringBuilder removedString = new StringBuilder(currentValue.Substring(selectionStart, selectionLength));
 
            StringBuilder projectedValue = new StringBuilder(currentValue.Substring(0, selectionStart));
            projectedValue.Append(currentValue.Substring(selectionStart + selectionLength));
 
            System.Diagnostics.Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "KeyCode:{0}, KeyData:{1}, KeyValue:{2}", e.KeyCode, e.KeyData, e.KeyValue));
            this.toolTip.Hide(this);
            if (e.KeyData == (Keys.Control | Keys.Space))
            {
                if (!this.listBoxAutoComplete.Visible)
                {
                    UpdateIntellisenceDropDown(this.Text.Substring(0, selectionStart - CurrentPrefix.Length));
                    ShowIntellisenceDropDown(selectionStart);
                    UpdateAutoCompleteSelection(CurrentPrefix);
                    e.SuppressKeyPress = true;
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.Back)
            {
                if (this.Text.Length > 0)
                {
                    if (removedString.Length == 0 && selectionStart > 0)
                    {
                        removedString.Append(projectedValue[selectionStart - 1]);
                        projectedValue.Length = projectedValue.Length - 1;
                    }
 
                    if (CurrentPrefix.Length <= 1)
                        HideIntellisenceDropDown();
 
                    if (removedString.ToString().IndexOfAny(". ()[]\t\n".ToCharArray()) >= 0)
                        HideIntellisenceDropDown();
                    else if (this.listBoxAutoComplete.Visible)
                        UpdateAutoCompleteSelection(CurrentPrefix.Substring(0, CurrentPrefix.Length - 1));
                }
 
            }
            else if (e.KeyCode == Keys.Up)
            {
                if (this.listBoxAutoComplete.Visible)
                {
                    if (this.listBoxAutoComplete.SelectedIndices.Count > 0 && this.listBoxAutoComplete.SelectedIndices[0] > 0)
                    {
                        this.listBoxAutoComplete.Items[this.listBoxAutoComplete.SelectedIndices[0] - 1].Selected = true;
                        this.listBoxAutoComplete.Items[this.listBoxAutoComplete.SelectedIndices[0]].Focused = true;
                    }
 
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.Down)
            {
                if (this.listBoxAutoComplete.Visible)
                {
                    if (this.listBoxAutoComplete.SelectedIndices.Count == 0)
                    {
                        if (this.listBoxAutoComplete.Items.Count > 0)
                        {
                            this.listBoxAutoComplete.Items[0].Selected = true;
                            this.listBoxAutoComplete.Items[0].Focused = true;
                        }
                    }
                    else if (this.listBoxAutoComplete.SelectedIndices[0] < this.listBoxAutoComplete.Items.Count - 1)
                    {
                        this.listBoxAutoComplete.Items[this.listBoxAutoComplete.SelectedIndices[0] + 1].Selected = true;
                        this.listBoxAutoComplete.Items[this.listBoxAutoComplete.SelectedIndices[0]].Focused = true;
                    }
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.ShiftKey
                || e.KeyCode == Keys.ControlKey
                || e.KeyCode == Keys.OemPeriod)
            {
                //DO nothing
            }
            else if ((e.KeyValue < 48 || (e.KeyValue >= 58 && e.KeyValue <= 64) || (e.KeyValue >= 91 && e.KeyValue <= 96) || e.KeyValue > 122) &&
                e.KeyData != (Keys.Shift | Keys.OemMinus))
            {
                if (this.listBoxAutoComplete.Visible)
                {
                    if (e.KeyCode == Keys.Return || e.KeyCode == Keys.Space)
                    {
                        this.SelectItem();
                        e.Handled = true;
                    }
 
                    HideIntellisenceDropDown();
                }
            }
        }
 
        private void IntellisenseTextBox_Leave(object sender, EventArgs e)
        {
            // remmember caret position before leaving
            this.oldSelectionStart = this.SelectionStart;
            this.toolTip.Hide(this);
 
 
            // make sure to close intellisense dropdown
            if ((this.listBoxAutoComplete.Focused == false) && (this.Focused == false))
                this.listBoxAutoComplete.Visible = false;
        }
 
        private void IntellisenseTextBox_Enter(object sender, EventArgs e)
        {
            // regain caret position
            if (this.oldSelectionStart >= 0)
                this.SelectionStart = this.oldSelectionStart;
        }
 
        void IntellisenseTextBox_MouseClick(object sender, MouseEventArgs e)
        {
            HideIntellisenceDropDown();
        }
 
        #endregion
 
        #region dropdown event handlers
 
        void listBoxAutoComplete_Enter(object sender, EventArgs e)
        {
            // we want to make sure the dropdown does not contain the focus at all times
            this.CausesValidation = false;
            this.Focus();
            this.CausesValidation = true;
        }
 
        private void listBoxAutoComplete_SelectedIndexChanged(object sender, EventArgs e)
        {
            foreach (ListViewItem listViewItem in this.listBoxAutoComplete.Items)
            {
                // make sure selection looks in focus (note: original
                // selection color for non focused listview is grayed)
 
                if (listViewItem.Selected)
                {
                    listViewItem.ForeColor = SystemColors.HighlightText;
                    listViewItem.BackColor = SystemColors.Highlight;
                    listViewItem.EnsureVisible();
                }
                else
                {
                    listViewItem.ForeColor = SystemColors.ControlText;
                    listViewItem.BackColor = SystemColors.Window;
                }
            }
        }
 
        private void listBoxAutoComplete_DoubleClick(object sender, EventArgs e)
        {
            // Item double clicked, select it
            if (this.listBoxAutoComplete.SelectedItems.Count == 1)
            {
                this.SelectItem();
                HideIntellisenceDropDown();
            }
        }
 
        #endregion
 
        #region helpers
 
        private void SelectItem()
        {
            if (this.listBoxAutoComplete.SelectedItems.Count > 0)
            {
                int selectionStart = this.SelectionStart;
                int prefixEnd = selectionStart - CurrentPrefix.Length;
                int suffixStart = selectionStart;
 
                if (suffixStart >= this.Text.Length)
                    suffixStart = this.Text.Length;
 
                string prefix = this.Text.Substring(0, prefixEnd);
                string fill = this.listBoxAutoComplete.SelectedItems[0].Text;
                string suffix = this.Text.Substring(suffixStart, this.Text.Length - suffixStart);
 
                this.Text = prefix + fill + suffix;
                this.SelectionStart = prefix.Length + fill.Length;
                this.ScrollToCaret();
                this.oldSelectionStart = this.SelectionStart;
            }
        }
 
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        private void PopulateListBox(ICollection list)
        {
            this.listBoxAutoComplete.Items.Clear();
            if (list != null && list.Count > 0)
            {
                foreach (object item in list)
                {
                    ListViewItem listViewItem = null;
                    if (item is string)
                    {
                        listViewItem = new ListViewItem(item as string);
                        listViewItem.ImageIndex = (int)memberIcons.Default;
                    }
                    else if (item is IntellisenseKeyword)
                    {
                        listViewItem = new ListViewItem(((IntellisenseKeyword)item).Name);
                        listViewItem.ImageIndex = (int)memberIcons.Keyword;
                    }
                    else if (item is MemberInfo)
                    {
                        listViewItem = new ListViewItem(((MemberInfo)item).Name as string);
                        if (item is PropertyInfo)
                        {
                            MethodInfo mi = ((PropertyInfo)item).GetGetMethod(true);
                            if (mi == null)
                                mi = ((PropertyInfo)item).GetSetMethod(true);
                            if (mi.IsPublic)
                                listViewItem.ImageIndex = (int)memberIcons.PublicProperty;
                            else if (mi.IsPrivate)
                                listViewItem.ImageIndex = (int)memberIcons.PrivateProperty;
                            else if (mi.IsFamily || mi.IsFamilyAndAssembly || mi.IsFamilyOrAssembly)
                                listViewItem.ImageIndex = (int)memberIcons.ProtectedProperty;
                            else // mi.IsAssembly
                                listViewItem.ImageIndex = (int)memberIcons.InternalProperty;
                        }
                        else if (item is FieldInfo)
                        {
                            FieldInfo fi = (FieldInfo)item;
                            if (fi.IsPublic)
                                listViewItem.ImageIndex = (int)memberIcons.PublicField;
                            else if (fi.IsPrivate)
                                listViewItem.ImageIndex = (int)memberIcons.PrivateField;
                            else if (fi.IsFamily || fi.IsFamilyAndAssembly || fi.IsFamilyOrAssembly)
                                listViewItem.ImageIndex = (int)memberIcons.ProtectedField;
                            else // fi.IsAssembly
                                listViewItem.ImageIndex = (int)memberIcons.InternalField;
                        }
                        else if (item is ExtensionMethodInfo)
                        {
                            listViewItem.ImageIndex = (int)memberIcons.ExtensionMethod;
                        }
                        else if (item is MethodInfo)
                        {
                            MethodInfo mi = (MethodInfo)item;
                            if (mi.IsPublic)
                                listViewItem.ImageIndex = (int)memberIcons.PublicMethod;
                            else if (mi.IsPrivate)
                                listViewItem.ImageIndex = (int)memberIcons.PrivateMethod;
                            else if (mi.IsFamily || mi.IsFamilyAndAssembly || mi.IsFamilyOrAssembly)
                                listViewItem.ImageIndex = (int)memberIcons.ProtectedMethod;
                            else // mi.IsAssembly
                                listViewItem.ImageIndex = (int)memberIcons.InternalMethod;
                        }
                        else if (item is Type)
                            listViewItem.ImageIndex = (int)memberIcons.Type;
                    }
                    this.listBoxAutoComplete.Items.Add(listViewItem);
                }
            }
            this.listBoxAutoComplete.Sort();
 
            if (this.listBoxAutoComplete.Items.Count > 0)
            {
                this.listBoxAutoComplete.Columns[0].Width = -2; // this will set the column size to the longest value
                this.listBoxAutoComplete.Size = new Size(this.listBoxAutoComplete.Items[0].Bounds.Width + 30, 72);
            }
        }
 
        internal void HideIntellisenceDropDown()
        {
            this.listBoxAutoComplete.Hide();
            this.toolTip.Hide(this);
        }
 
        private void ShowIntellisenceDropDown(int charIndex)
        {
            if (this.listBoxAutoComplete.Items.Count > 0)
            {
                // Find the position of the caret          
                Point clientPoint = this.GetPositionFromCharIndex(charIndex - 1);
                clientPoint.Y += (int)Math.Ceiling(this.Font.GetHeight()) + 2;
                clientPoint.X -= 6;
                if (charIndex > 0 && this.Text[charIndex - 1] == '\n')
                {
                    clientPoint.Y += (int)Math.Ceiling(this.Font.GetHeight());
                    clientPoint.X = this.GetPositionFromCharIndex(0).X - 6;
                }
 
                Point parentScreenLocation = TopLevelControl.PointToScreen(new Point(0, 0));
                Point locationInDialog = PointToScreen(clientPoint);
                locationInDialog.Offset(-parentScreenLocation.X, -parentScreenLocation.Y);
 
                //Fix location and size to avoid clipping
                Size topLevelControlSize = (TopLevelControl is Form) ? ((Form)TopLevelControl).ClientSize : TopLevelControl.Size;
                Rectangle listboxRectangle = new Rectangle(locationInDialog, this.listBoxAutoComplete.Size);
 
                if (listboxRectangle.Right > topLevelControlSize.Width)
                {
                    if (this.listBoxAutoComplete.Size.Width > topLevelControlSize.Width)
                        this.listBoxAutoComplete.Size = new Size(topLevelControlSize.Width, this.listBoxAutoComplete.Height);
 
                    locationInDialog = new Point(topLevelControlSize.Width - this.listBoxAutoComplete.Size.Width, locationInDialog.Y);
                }
                if (listboxRectangle.Bottom > topLevelControlSize.Height)
                    this.listBoxAutoComplete.Size = new Size(this.listBoxAutoComplete.Width, topLevelControlSize.Height - listboxRectangle.Top);
 
                // set position and show
                this.listBoxAutoComplete.Location = locationInDialog;
                this.listBoxAutoComplete.BringToFront();
                this.listBoxAutoComplete.Show();
            }
        }
 
        private void UpdateIntellisenceDropDown(string text)
        {
            AutoCompletionEventArgs autoCompletionEventArgs = new AutoCompletionEventArgs();
            autoCompletionEventArgs.Prefix = text;
            if (this.PopulateAutoCompleteList != null)
                this.PopulateAutoCompleteList(this, autoCompletionEventArgs);
 
            PopulateListBox(autoCompletionEventArgs.AutoCompleteValues);
        }
 
        private void UpdateAutoCompleteSelection(string currentValue)
        {
            bool wordMatched = false;
 
            if (string.IsNullOrEmpty(currentValue.Trim()) && this.listBoxAutoComplete.Items.Count > 0)
            {
                wordMatched = true;
                this.listBoxAutoComplete.Items[0].Selected = true;
                this.listBoxAutoComplete.Items[0].Focused = true;
            }
            else
            {
                for (int i = 0; i < this.listBoxAutoComplete.Items.Count; i++)
                {
                    if (this.listBoxAutoComplete.Items[i].Text.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase))
                    {
                        wordMatched = true;
                        this.listBoxAutoComplete.Items[i].Selected = true;
                        this.listBoxAutoComplete.Items[i].Focused = true;
                        break;
                    }
                }
            }
            if (!wordMatched && this.listBoxAutoComplete.SelectedItems.Count == 1)
                this.listBoxAutoComplete.SelectedItems[0].Selected = false;
        }
 
        private void ShowToolTip(int charIndex, string prefix)
        {
            Point clientPoint = this.GetPositionFromCharIndex(charIndex - 1);
            clientPoint.Y += (int)Math.Ceiling(this.Font.GetHeight()) + 2;
            clientPoint.X -= 6;
 
            AutoCompletionEventArgs autoCompletionEventArgs = new AutoCompletionEventArgs();
            autoCompletionEventArgs.Prefix = prefix;
 
            if (this.PopulateToolTipList != null)
            {
                this.PopulateToolTipList(this, autoCompletionEventArgs);
 
                if (autoCompletionEventArgs.AutoCompleteValues != null)
                {
                    StringBuilder toolTipText = new StringBuilder();
                    bool firstMethod = true;
                    foreach (MemberInfo memberInfo in autoCompletionEventArgs.AutoCompleteValues)
                    {
                        if (firstMethod)
                            firstMethod = false;
                        else
                            toolTipText.Append("\n");
 
                        ParameterInfo[] parameters = null;
 
                        MethodInfo methodInfo = memberInfo as MethodInfo;
                        if (methodInfo != null)
                        {
                            toolTipText.Append(RuleDecompiler.DecompileType(methodInfo.ReturnType));
                            toolTipText.Append(" ");
                            toolTipText.Append(methodInfo.Name);
                            toolTipText.Append("(");
 
                            parameters = methodInfo.GetParameters();
                        }
                        else
                        {
                            // Must be constructor... if not, the best thing to do is let it throw "invalid cast".
                            ConstructorInfo ctorInfo = (ConstructorInfo)memberInfo;
 
                            toolTipText.Append(RuleDecompiler.DecompileType(ctorInfo.DeclaringType));
                            toolTipText.Append("(");
 
                            parameters = ctorInfo.GetParameters();
                        }
 
 
                        if (parameters != null && parameters.Length > 0)
                        {
                            int lastParamIndex = parameters.Length - 1;
 
                            // Append the first parameter
                            AppendParameterInfo(toolTipText, parameters[0], 0 == lastParamIndex);
                            for (int i = 1; i < parameters.Length; ++i)
                            {
                                toolTipText.Append(", ");
                                AppendParameterInfo(toolTipText, parameters[i], i == lastParamIndex);
                            }
                        }
 
                        toolTipText.Append(")");
                    }
                    this.toolTip.Show(toolTipText.ToString(), this, clientPoint);
                }
            }
        }
 
        private static void AppendParameterInfo(StringBuilder toolTipText, ParameterInfo parameterInfo, bool isLastParameter)
        {
            Type paramType = parameterInfo.ParameterType;
            if (paramType != null)
            {
                if (paramType.IsByRef)
                {
                    if (parameterInfo.IsOut)
                        toolTipText.Append("out ");
                    else
                        toolTipText.Append("ref ");
 
                    paramType = paramType.GetElementType();
                }
                else if (isLastParameter && paramType.IsArray)
                {
                    object[] attrs = parameterInfo.GetCustomAttributes(typeof(ParamArrayAttribute), false);
                    if (attrs != null && attrs.Length > 0)
                        toolTipText.Append("params ");
                }
 
                toolTipText.Append(RuleDecompiler.DecompileType(paramType));
                toolTipText.Append(" ");
            }
            toolTipText.Append(parameterInfo.Name);
        }
 
        private string CurrentPrefix
        {
            get
            {
                string textTillCaret = this.Text.Substring(0, this.SelectionStart);
 
                int prefixStart = textTillCaret.LastIndexOfAny(" .()[]\t\r\n".ToCharArray());
                if (prefixStart >= 0)
                    return textTillCaret.Substring(prefixStart + 1);
                else
                    return textTillCaret;
            }
        }
 
        #endregion
 
        #region override members
 
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            // bail out an editcontrol before giving up on the dialog
            if (this.listBoxAutoComplete.Visible)
            {
                switch (keyData)
                {
                    case Keys.Enter:
                    case Keys.Tab:
                        this.SelectItem();
                        HideIntellisenceDropDown();
                        return true;
                    case Keys.Escape:
                        HideIntellisenceDropDown();
                        return true;
                    default:
                        break;
                }
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }
 
        #endregion
    }
 
    #endregion
 
    #region AutoCompletionEventArgs
 
    internal class AutoCompletionEventArgs : EventArgs
    {
        private string prefix;
        ICollection autoCompleteValues;
 
        public ICollection AutoCompleteValues
        {
            get
            {
                return autoCompleteValues;
            }
            set
            {
                autoCompleteValues = value;
            }
        }
 
        public string Prefix
        {
            get
            {
                return prefix;
            }
            set
            {
                prefix = value;
            }
        }
    }
 
    #endregion
}