// --------------------------------------------------------------------------- // 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 PopulateAutoCompleteList; public event EventHandler 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 }