X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FManaged.Windows.Forms%2FSystem.Windows.Forms%2FTextBox.cs;h=d7c548ca0a2aa00a52e785e6e8ff86c1fcaeb61c;hb=fe405951f51ed81b13956a3188460c9c281f605f;hp=5c592244d01b4c178933bccd59f6c5ce9e75dc38;hpb=c5191f66a55d83bd4d37530264f04fcfba9257a6;p=mono.git diff --git a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextBox.cs b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextBox.cs index 5c592244d01..d7c548ca0a2 100644 --- a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextBox.cs +++ b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/TextBox.cs @@ -27,20 +27,18 @@ // NOT COMPLETE using System; +using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; -#if NET_2_0 +using System.Collections.Generic; using System.Runtime.InteropServices; -#endif namespace System.Windows.Forms { -#if NET_2_0 [ComVisible(true)] [ClassInterface (ClassInterfaceType.AutoDispatch)] [Designer ("System.Windows.Forms.Design.TextBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] -#endif public class TextBox : TextBoxBase { #region Variables private ContextMenu menu; @@ -51,12 +49,15 @@ namespace System.Windows.Forms { private MenuItem delete; private MenuItem select_all; -#if NET_2_0 - private bool use_system_password_char = false; - private AutoCompleteStringCollection auto_complete_custom_source = null; + private bool use_system_password_char; + private AutoCompleteStringCollection auto_complete_custom_source; private AutoCompleteMode auto_complete_mode = AutoCompleteMode.None; private AutoCompleteSource auto_complete_source = AutoCompleteSource.None; -#endif + private AutoCompleteListBox auto_complete_listbox; + private string auto_complete_original_text; + private int auto_complete_selected_index = -1; + private List auto_complete_matches; + private ComboBox auto_complete_cb_source; #endregion // Variables #region Public Constructors @@ -66,6 +67,7 @@ namespace System.Windows.Forms { alignment = HorizontalAlignment.Left; this.LostFocus +=new EventHandler(TextBox_LostFocus); this.RightToLeftChanged += new EventHandler (TextBox_RightToLeftChanged); + MouseWheel += new MouseEventHandler (TextBox_MouseWheel); BackColor = SystemColors.Window; ForeColor = SystemColors.WindowText; @@ -104,9 +106,217 @@ namespace System.Windows.Forms { UpdateAlignment (); } - private void TextBox_LostFocus(object sender, EventArgs e) { + private void TextBox_LostFocus (object sender, EventArgs e) { if (hide_selection) document.InvalidateSelectionArea (); + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + } + + private void TextBox_MouseWheel (object o, MouseEventArgs args) + { + if (auto_complete_listbox == null || !auto_complete_listbox.Visible) + return; + + int lines = args.Delta / 120; + auto_complete_listbox.Scroll (-lines); + } + + // Receives either WM_KEYDOWN or WM_CHAR that will likely need the generation/lookup + // of new matches + private void ProcessAutoCompleteInput (ref Message m, bool deleting_chars) + { + // Need to call base.WndProc before to have access to + // the updated Text property value + base.WndProc (ref m); + auto_complete_original_text = Text; + ShowAutoCompleteListBox (deleting_chars); + } + + private void ShowAutoCompleteListBox (bool deleting_chars) + { + // + // We only support CustomSource by now + // + + IList source = auto_complete_cb_source == null ? auto_complete_custom_source : (IList)auto_complete_cb_source.Items; + + bool append = auto_complete_mode == AutoCompleteMode.Append || auto_complete_mode == AutoCompleteMode.SuggestAppend; + bool suggest = auto_complete_mode == AutoCompleteMode.Suggest || auto_complete_mode == AutoCompleteMode.SuggestAppend; + + if (Text.Length == 0) { + if (auto_complete_listbox != null) + auto_complete_listbox.HideListBox (false); + return; + } + + if (auto_complete_matches == null) + auto_complete_matches = new List (); + + string text = Text; + auto_complete_matches.Clear (); + + for (int i = 0; i < source.Count; i++) { + string item_text = auto_complete_cb_source == null ? auto_complete_custom_source [i] : + auto_complete_cb_source.GetItemText (auto_complete_cb_source.Items [i]); + if (item_text.StartsWith (text, StringComparison.CurrentCultureIgnoreCase)) + auto_complete_matches.Add (item_text); + } + + auto_complete_matches.Sort (); + + // Return if we have a single exact match + if ((auto_complete_matches.Count == 0) || (auto_complete_matches.Count == 1 && + auto_complete_matches [0].Equals (text, StringComparison.CurrentCultureIgnoreCase))) { + + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + return; + } + + auto_complete_selected_index = suggest ? -1 : 0; + + if (suggest) { + if (auto_complete_listbox == null) + auto_complete_listbox = new AutoCompleteListBox (this); + + // Show or update auto complete listbox contents + auto_complete_listbox.Location = PointToScreen (new Point (0, Height)); + auto_complete_listbox.ShowListBox (); + } + + if (append && !deleting_chars) + AppendAutoCompleteMatch (0); + + document.MoveCaret (CaretDirection.End); + } + + internal void HideAutoCompleteList () + { + if (auto_complete_listbox != null) + auto_complete_listbox.HideListBox (false); + } + + internal bool IsAutoCompleteAvailable { + get { + if (auto_complete_source == AutoCompleteSource.None || auto_complete_mode == AutoCompleteMode.None) + return false; + + // We only support CustomSource by now, as well as an internal custom source used by ComboBox + if (auto_complete_source != AutoCompleteSource.CustomSource) + return false; + IList custom_source = auto_complete_cb_source == null ? auto_complete_custom_source : (IList)auto_complete_cb_source.Items; + if (custom_source == null || custom_source.Count == 0) + return false; + + return true; + } + } + + internal ComboBox AutoCompleteInternalSource { + get { + return auto_complete_cb_source; + } + set { + auto_complete_cb_source = value; + } + } + + internal bool CanNavigateAutoCompleteList { + get { + if (auto_complete_mode == AutoCompleteMode.None) + return false; + if (auto_complete_matches == null || auto_complete_matches.Count == 0) + return false; + + bool suggest_window_visible = auto_complete_listbox != null && auto_complete_listbox.Visible; + if (auto_complete_mode == AutoCompleteMode.Suggest && !suggest_window_visible) + return false; + + return true; + } + } + + bool NavigateAutoCompleteList (Keys key) + { + if (auto_complete_matches == null || auto_complete_matches.Count == 0) + return false; + + bool suggest_window_visible = auto_complete_listbox != null && auto_complete_listbox.Visible; + if (!suggest_window_visible && auto_complete_mode == AutoCompleteMode.Suggest) + return false; + + int index = auto_complete_selected_index; + + switch (key) { + case Keys.Up: + index -= 1; + if (index < -1) + index = auto_complete_matches.Count - 1; + break; + case Keys.Down: + index += 1; + if (index >= auto_complete_matches.Count) + index = -1; + break; + case Keys.PageUp: + if (auto_complete_mode == AutoCompleteMode.Append || !suggest_window_visible) + goto case Keys.Up; + + if (index == -1) + index = auto_complete_matches.Count - 1; + else if (index == 0) + index = -1; + else { + index -= auto_complete_listbox.page_size - 1; + if (index < 0) + index = 0; + } + break; + case Keys.PageDown: + if (auto_complete_mode == AutoCompleteMode.Append || !suggest_window_visible) + goto case Keys.Down; + + if (index == -1) + index = 0; + else if (index == auto_complete_matches.Count - 1) + index = -1; + else { + index += auto_complete_listbox.page_size - 1; + if (index >= auto_complete_matches.Count) + index = auto_complete_matches.Count - 1; + } + break; + default: + break; + } + + // In SuggestAppend mode the navigation mode depends on the visibility of the suggest lb. + bool suggest = auto_complete_mode == AutoCompleteMode.Suggest || auto_complete_mode == AutoCompleteMode.SuggestAppend; + if (suggest && suggest_window_visible) { + Text = index == -1 ? auto_complete_original_text : auto_complete_matches [index]; + auto_complete_listbox.HighlightedIndex = index; + } else + // Append only, not suggest at all + AppendAutoCompleteMatch (index < 0 ? 0 : index); + + auto_complete_selected_index = index; + document.MoveCaret (CaretDirection.End); + + return true; + } + + void AppendAutoCompleteMatch (int index) + { + Text = auto_complete_original_text + auto_complete_matches [index].Substring (auto_complete_original_text.Length); + SelectionStart = auto_complete_original_text.Length; + SelectionLength = auto_complete_matches [index].Length - auto_complete_original_text.Length; + } + + // this is called when the user selects a value from the autocomplete list + // *with* the mouse + internal virtual void OnAutoCompleteValueSelected (EventArgs args) + { } private void UpdateAlignment () @@ -144,28 +354,23 @@ namespace System.Windows.Forms { internal override Color ChangeBackColor (Color backColor) { if (backColor == Color.Empty) { -#if NET_2_0 if (!ReadOnly) backColor = SystemColors.Window; -#else - backColor = SystemColors.Window; -#endif + backcolor_set = false; } + return backColor; } -#if NET_2_0 void OnAutoCompleteCustomSourceChanged(object sender, CollectionChangeEventArgs e) { if(auto_complete_source == AutoCompleteSource.CustomSource) { //FIXME: handle add, remove and refresh events in AutoComplete algorithm. } } -#endif #endregion // Private & Internal Methods #region Public Instance Properties -#if NET_2_0 [MonoTODO("AutoCompletion algorithm is currently not implemented.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] [Browsable (true)] @@ -244,11 +449,11 @@ namespace System.Windows.Forms { if (!Multiline) document.PasswordChar = PasswordChar.ToString (); else - document.PasswordChar = ""; + document.PasswordChar = string.Empty; + Invalidate (); } } } -#endif [DefaultValue(false)] [MWFCategory("Behavior")] @@ -260,7 +465,7 @@ namespace System.Windows.Forms { set { if (value != accepts_return) { accepts_return = value; - } + } } } @@ -281,16 +486,12 @@ namespace System.Windows.Forms { [Localizable(true)] [DefaultValue('\0')] [MWFCategory("Behavior")] -#if NET_2_0 [RefreshProperties (RefreshProperties.Repaint)] -#endif public char PasswordChar { get { -#if NET_2_0 if (use_system_password_char) { return '*'; } -#endif return password_char; } @@ -300,7 +501,7 @@ namespace System.Windows.Forms { if (!Multiline) { document.PasswordChar = PasswordChar.ToString (); } else { - document.PasswordChar = ""; + document.PasswordChar = string.Empty; } this.CalculateDocument(); } @@ -327,19 +528,6 @@ namespace System.Windows.Forms { } } -#if ONLY_1_1 - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public override int SelectionLength { - get { - return base.SelectionLength; - } - set { - base.SelectionLength = value; - } - } -#endif - public override string Text { get { return base.Text; @@ -370,7 +558,6 @@ namespace System.Windows.Forms { } #endregion // Public Instance Properties -#if NET_2_0 public void Paste (string text) { document.ReplaceSelection (CaseAdjust (text), false); @@ -378,7 +565,6 @@ namespace System.Windows.Forms { ScrollToCaret(); OnTextChanged(EventArgs.Empty); } -#endif #region Protected Instance Methods protected override CreateParams CreateParams { get { @@ -386,49 +572,81 @@ namespace System.Windows.Forms { } } -#if ONLY_1_1 - protected override ImeMode DefaultImeMode { - get { - return base.DefaultImeMode; - } - } -#endif -#if NET_2_0 protected override void Dispose (bool disposing) { base.Dispose (disposing); } -#endif - protected override bool IsInputKey(Keys keyData) { + protected override bool IsInputKey (Keys keyData) + { return base.IsInputKey (keyData); } - protected override void OnGotFocus(EventArgs e) { + protected override void OnGotFocus (EventArgs e) + { base.OnGotFocus (e); if (selection_length == -1 && !has_been_focused) SelectAllNoScroll (); has_been_focused = true; } - protected override void OnHandleCreated(EventArgs e) { + protected override void OnHandleCreated (EventArgs e) + { base.OnHandleCreated (e); } -#if ONLY_1_1 - protected override void OnMouseUp(MouseEventArgs e) { - base.OnMouseUp (e); - } -#endif - - protected virtual void OnTextAlignChanged(EventArgs e) { + protected virtual void OnTextAlignChanged (EventArgs e) + { EventHandler eh = (EventHandler)(Events [TextAlignChangedEvent]); if (eh != null) eh (this, e); } - protected override void WndProc(ref Message m) { + protected override void WndProc (ref Message m) + { switch ((Msg)m.Msg) { + case Msg.WM_KEYDOWN: + if (!IsAutoCompleteAvailable) + break; + + Keys key_data = (Keys)m.WParam.ToInt32 (); + switch (key_data) { + case Keys.Down: + case Keys.Up: + case Keys.PageDown: + case Keys.PageUp: + if (NavigateAutoCompleteList (key_data)) { + m.Result = IntPtr.Zero; + return; + } + break; + case Keys.Enter: + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + SelectAll (); + break; + case Keys.Escape: + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + break; + case Keys.Delete: + ProcessAutoCompleteInput (ref m, true); + return; + default: + break; + } + break; + case Msg.WM_CHAR: + if (!IsAutoCompleteAvailable) + break; + + // Don't handle either Enter or Esc - they are handled in the WM_KEYDOWN case + int char_value = m.WParam.ToInt32 (); + if (char_value == 13 || char_value == 27) + break; + + ProcessAutoCompleteInput (ref m, char_value == 8); + return; case Msg.WM_LBUTTONDOWN: // When the textbox gets focus by LBUTTON (but not by middle or right) // it does not do the select all / scroll thing. @@ -464,6 +682,11 @@ namespace System.Windows.Forms { } } + internal void RestoreContextMenu () + { + ContextMenuInternal = menu; + } + private void menu_Popup(object sender, EventArgs e) { if (SelectionLength == 0) { cut.Enabled = false; @@ -507,7 +730,7 @@ namespace System.Windows.Forms { } private void delete_Click(object sender, EventArgs e) { - SelectedText = ""; + SelectedText = string.Empty; } private void select_all_Click(object sender, EventArgs e) { @@ -515,7 +738,6 @@ namespace System.Windows.Forms { } #endregion // Private Methods -#if NET_2_0 public override bool Multiline { get { return base.Multiline; @@ -540,12 +762,291 @@ namespace System.Windows.Forms { { base.OnHandleDestroyed (e); } -#endif + + class AutoCompleteListBox : Control + { + TextBox owner; + VScrollBar vscroll; + int top_item; + int last_item; + internal int page_size; + int item_height; + int highlighted_index = -1; + bool user_defined_size; + bool resizing; + Rectangle resizer_bounds; + + const int DefaultDropDownItems = 7; + + public AutoCompleteListBox (TextBox tb) + { + owner = tb; + item_height = FontHeight + 2; + + vscroll = new VScrollBar (); + vscroll.ValueChanged += VScrollValueChanged; + Controls.Add (vscroll); + + is_visible = false; + InternalBorderStyle = BorderStyle.FixedSingle; + } + + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + + cp.Style ^= (int)WindowStyles.WS_CHILD; + cp.Style ^= (int)WindowStyles.WS_VISIBLE; + cp.Style |= (int)WindowStyles.WS_POPUP; + cp.ExStyle |= (int)WindowExStyles.WS_EX_TOPMOST | (int)WindowExStyles.WS_EX_TOOLWINDOW; + return cp; + } + } + + public int HighlightedIndex { + get { + return highlighted_index; + } + set { + if (value == highlighted_index) + return; + + if (highlighted_index != -1) + Invalidate (GetItemBounds (highlighted_index)); + highlighted_index = value; + if (highlighted_index != -1) + Invalidate (GetItemBounds (highlighted_index)); + + if (highlighted_index != -1) + EnsureVisible (highlighted_index); + } + } + + public void Scroll (int lines) + { + int max = vscroll.Maximum - page_size + 1; + int val = vscroll.Value + lines; + if (val > max) + val = max; + else if (val < vscroll.Minimum) + val = vscroll.Minimum; + + vscroll.Value = val; + } + + public void EnsureVisible (int index) + { + if (index < top_item) { + vscroll.Value = index; + } else { + int max = vscroll.Maximum - page_size + 1; + int rows = Height / item_height; + if (index > top_item + rows - 1) { + index = index - rows + 1; + vscroll.Value = index > max ? max : index; + } + } + } + + internal override bool ActivateOnShow { + get { + return false; + } + } + + void VScrollValueChanged (object o, EventArgs args) + { + if (top_item == vscroll.Value) + return; + + top_item = vscroll.Value; + last_item = GetLastVisibleItem (); + Invalidate (); + } + + int GetLastVisibleItem () + { + int top_y = Height; + + for (int i = top_item; i < owner.auto_complete_matches.Count; i++) { + int pos = i - top_item; // relative to visible area + if ((pos * item_height) + item_height >= top_y) + return i; + } + + return owner.auto_complete_matches.Count - 1; + } + + Rectangle GetItemBounds (int index) + { + int pos = index - top_item; + Rectangle bounds = new Rectangle (0, pos * item_height, Width, item_height); + if (vscroll.Visible) + bounds.Width -= vscroll.Width; + + return bounds; + } + + int GetItemAt (Point loc) + { + if (loc.Y > (last_item - top_item) * item_height + item_height) + return -1; + + int retval = loc.Y / item_height; + retval += top_item; + + return retval; + } + + void LayoutListBox () + { + int total_height = owner.auto_complete_matches.Count * item_height; + page_size = Math.Max (Height / item_height, 1); + last_item = GetLastVisibleItem (); + + if (Height < total_height) { + vscroll.Visible = true; + vscroll.Maximum = owner.auto_complete_matches.Count - 1; + vscroll.LargeChange = page_size; + vscroll.Location = new Point (Width - vscroll.Width, 0); + vscroll.Height = Height - item_height; + } else + vscroll.Visible = false; + + resizer_bounds = new Rectangle (Width - item_height, Height - item_height, + item_height, item_height); + } + + public void HideListBox (bool set_text) + { + if (set_text) + owner.Text = owner.auto_complete_matches [HighlightedIndex]; + + Capture = false; + Hide (); + } + + public void ShowListBox () + { + if (!user_defined_size) { + // This should call the Layout routine for us + int height = owner.auto_complete_matches.Count > DefaultDropDownItems ? + DefaultDropDownItems * item_height : (owner.auto_complete_matches.Count + 1) * item_height; + Size = new Size (owner.Width, height); + } else + LayoutListBox (); + + vscroll.Value = 0; + HighlightedIndex = -1; + + Show (); + // make sure we are on top - call the raw routine, since we are parentless + XplatUI.SetZOrder (Handle, IntPtr.Zero, true, false); + Invalidate (); + } + + protected override void OnResize (EventArgs args) + { + base.OnResize (args); + + LayoutListBox (); + Refresh (); + } + + protected override void OnMouseDown (MouseEventArgs args) + { + base.OnMouseDown (args); + + if (!resizer_bounds.Contains (args.Location)) + return; + + user_defined_size = true; + resizing = true; + Capture = true; + } + + protected override void OnMouseMove (MouseEventArgs args) + { + base.OnMouseMove (args); + + if (resizing) { + Point mouse_loc = Control.MousePosition; + Point ctrl_loc = PointToScreen (Point.Empty); + + Size new_size = new Size (mouse_loc.X - ctrl_loc.X, mouse_loc.Y - ctrl_loc.Y); + if (new_size.Height < item_height) + new_size.Height = item_height; + if (new_size.Width < item_height) + new_size.Width = item_height; + + Size = new_size; + return; + } + + Cursor = resizer_bounds.Contains (args.Location) ? Cursors.SizeNWSE : Cursors.Default; + + int item_idx = GetItemAt (args.Location); + if (item_idx != -1) + HighlightedIndex = item_idx; + } + + protected override void OnMouseUp (MouseEventArgs args) + { + base.OnMouseUp (args); + + int item_idx = GetItemAt (args.Location); + if (item_idx != -1 && !resizing) + HideListBox (true); + + owner.OnAutoCompleteValueSelected (EventArgs.Empty); // internal + resizing = false; + Capture = false; + } + + internal override void OnPaintInternal (PaintEventArgs args) + { + Graphics g = args.Graphics; + Brush brush = ThemeEngine.Current.ResPool.GetSolidBrush (ForeColor); + + int highlighted_idx = HighlightedIndex; + + int y = 0; + int last = GetLastVisibleItem (); + for (int i = top_item; i <= last; i++) { + Rectangle item_bounds = GetItemBounds (i); + if (!item_bounds.IntersectsWith (args.ClipRectangle)) + continue; + + if (i == highlighted_idx) { + g.FillRectangle (SystemBrushes.Highlight, item_bounds); + g.DrawString (owner.auto_complete_matches [i], Font, SystemBrushes.HighlightText, item_bounds); + } else + g.DrawString (owner.auto_complete_matches [i], Font, brush, item_bounds); + + y += item_height; + } + + ThemeEngine.Current.CPDrawSizeGrip (g, SystemColors.Control, resizer_bounds); + } + } } -#if NET_2_0 - internal class TextBoxAutoCompleteSourceConverter : TypeConverter + internal class TextBoxAutoCompleteSourceConverter : EnumConverter { + public TextBoxAutoCompleteSourceConverter(Type type) + : base(type) + { } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection stdv = base.GetStandardValues(context); + AutoCompleteSource[] arr = new AutoCompleteSource[stdv.Count]; + stdv.CopyTo(arr, 0); + AutoCompleteSource[] arr2 = Array.FindAll(arr, delegate (AutoCompleteSource value) { + // No "ListItems" in a TextBox. + return value != AutoCompleteSource.ListItems; + }); + return new StandardValuesCollection(arr2); + } } -#endif }