2008-12-10 Carlos Alberto Cortez <calberto.cortez@gmail.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextBox.cs
index 100a6c532abfa648cb59ef3df56d798a73bc7c69..f26b1bd5ffad0874a441ec4de7434bd5b1daf517 100644 (file)
@@ -59,6 +59,9 @@ namespace System.Windows.Forms {
                private AutoCompleteMode auto_complete_mode = AutoCompleteMode.None;
                private AutoCompleteSource auto_complete_source = AutoCompleteSource.None;
                private AutoCompleteListBox auto_complete_listbox;
+               private string auto_complete_original_text;
+               private int auto_complete_selected_index = -1;
+               private List<string> auto_complete_matches;
                private ComboBox auto_complete_cb_source;
 #endif
                #endregion      // Variables
@@ -71,7 +74,7 @@ namespace System.Windows.Forms {
                        this.LostFocus +=new EventHandler(TextBox_LostFocus);
                        this.RightToLeftChanged += new EventHandler (TextBox_RightToLeftChanged);
 #if NET_2_0
-                       TextChanged += new EventHandler (TextBox_TextChanged);
+                       MouseWheel += new MouseEventHandler (TextBox_MouseWheel);
 #endif
 
                        BackColor = SystemColors.Window;
@@ -121,59 +124,98 @@ namespace System.Windows.Forms {
                }
 
 #if NET_2_0
-               void TextBox_TextChanged (object o, EventArgs args)
+               private void TextBox_MouseWheel (object o, MouseEventArgs args)
                {
-                       if (auto_complete_mode == AutoCompleteMode.None || auto_complete_source == AutoCompleteSource.None)
+                       if (auto_complete_listbox == null || !auto_complete_listbox.Visible)
                                return;
 
+                       int lines = args.Delta / 120;
+                       auto_complete_listbox.Scroll (-lines);
+               }
+
+               private void ShowAutoCompleteListBox (bool is_backspace)
+               {
+                       // 
                        // We only support CustomSource by now
+                       //
+
                        IList source;
                        if (auto_complete_cb_source == null)
                                source = auto_complete_custom_source;
                        else
                                source = auto_complete_cb_source.Items;
 
-                       if (auto_complete_source != AutoCompleteSource.CustomSource ||
-                               source == null || source.Count == 0)
+                       if (source == null || source.Count == 0)
                                return;
 
+                       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_listbox == null)
-                               auto_complete_listbox = new AutoCompleteListBox (this);
-
-                       // If the text was just set by the auto complete listbox, ignore it
-                       if (auto_complete_listbox.WasTextSet) {
-                               auto_complete_listbox.WasTextSet = false;
-                               return;
-                       }
+                       if (auto_complete_matches == null)
+                               auto_complete_matches = new List<string> ();
 
                        string text = Text;
-                       auto_complete_listbox.Items.Clear ();
+                       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_listbox.Items.Add (item_text);
+                                       auto_complete_matches.Add (item_text);
                        }
 
-                       IList<string> matches = auto_complete_listbox.Items;
-                       if ((matches.Count == 0) ||
-                               (matches.Count == 1 && matches [0].Equals (text, StringComparison.CurrentCultureIgnoreCase))) { // Exact single match
+                       auto_complete_matches.Sort ();
 
-                               if (auto_complete_listbox.Visible)
+                       // 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;
                        }
 
-                       // Show or update auto complete listbox contents
-                       auto_complete_listbox.Location = PointToScreen (new Point (0, Height));
-                       auto_complete_listbox.ShowListBox ();
+                       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 && !is_backspace)
+                               AppendAutoCompleteMatch (0);
+
+                       document.MoveCaret (CaretDirection.End);
+               }
+
+               internal void HideAutoCompleteList ()
+               {
+                       if (auto_complete_listbox != null)
+                               auto_complete_listbox.HideListBox (false);
+               }
+
+               bool IsAutoCompleteAvailable {
+                       get {
+                               if (auto_complete_source == AutoCompleteSource.None || auto_complete_mode == AutoCompleteMode.None)
+                                       return false;
+
+                               // We only support CustomSource by now
+                               if (auto_complete_source != AutoCompleteSource.CustomSource || auto_complete_custom_source == null ||
+                                               auto_complete_custom_source.Count == 0)
+                                       return false;
+
+                               return true;
+                       }
                }
 
                internal ComboBox AutoCompleteInternalSource {
@@ -184,6 +226,98 @@ namespace System.Windows.Forms {
                                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;
+               }
+
 #endif
 
                private void UpdateAlignment ()
@@ -322,6 +456,7 @@ namespace System.Windows.Forms {
                                                document.PasswordChar = PasswordChar.ToString ();
                                        else
                                                document.PasswordChar = string.Empty;
+                                       Invalidate ();
                                }
                        }
                }
@@ -512,6 +647,55 @@ namespace System.Windows.Forms {
                protected override void WndProc (ref Message m)
                {
                        switch ((Msg)m.Msg) {
+#if NET_2_0
+                               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;
+                                               default:
+                                                       break;
+                                       }
+                                       break;
+                               case Msg.WM_CHAR:
+                                       if (!IsAutoCompleteAvailable)
+                                               break;
+
+                                       bool is_backspace = m.WParam.ToInt32 () == 8;
+                                       if (!Char.IsLetterOrDigit ((char)m.WParam) && !is_backspace)
+                                               break;
+                                       
+                                       if (!is_backspace)
+                                               Text = auto_complete_original_text;
+
+                                       document.MoveCaret (CaretDirection.End);
+
+                                       // 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 (is_backspace);
+                                       return;
+#endif
                                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.
@@ -633,15 +817,13 @@ namespace System.Windows.Forms {
                {
                        TextBox owner;
                        VScrollBar vscroll;
-                       List<string> items;
                        int top_item;
                        int last_item;
-                       int page_size;
+                       internal int page_size;
                        int item_height;
                        int highlighted_index = -1;
                        bool user_defined_size;
                        bool resizing;
-                       bool was_text_set;
                        Rectangle resizer_bounds;
 
                        const int DefaultDropDownItems = 7;
@@ -649,7 +831,6 @@ namespace System.Windows.Forms {
                        public AutoCompleteListBox (TextBox tb)
                        {
                                owner = tb;
-                               items = new List<string> ();
                                item_height = FontHeight + 2;
 
                                vscroll = new VScrollBar ();
@@ -672,12 +853,6 @@ namespace System.Windows.Forms {
                                }
                        }
 
-                       public IList<string> Items {
-                               get {
-                                       return items;
-                               }
-                       }
-
                        public int HighlightedIndex {
                                get {
                                        return highlighted_index;
@@ -691,15 +866,35 @@ namespace System.Windows.Forms {
                                        highlighted_index = value;
                                        if (highlighted_index != -1)
                                                Invalidate (GetItemBounds (highlighted_index));
+
+                                       if (highlighted_index != -1)
+                                               EnsureVisible (highlighted_index);
                                }
                        }
 
-                       public bool WasTextSet {
-                               get {
-                                       return was_text_set;
-                               }
-                               set {
-                                       was_text_set = value;
+                       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;
+                                       }
                                }
                        }
 
@@ -723,13 +918,13 @@ namespace System.Windows.Forms {
                        {
                                int top_y = Height;
 
-                               for (int i = top_item; i < items.Count; i++) {
+                               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 items.Count - 1;
+                               return owner.auto_complete_matches.Count - 1;
                        }
 
                        Rectangle GetItemBounds (int index)
@@ -755,13 +950,13 @@ namespace System.Windows.Forms {
 
                        void LayoutListBox ()
                        {
-                               int total_height = items.Count * item_height;
+                               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 = items.Count - 1;
+                                       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;
@@ -774,11 +969,8 @@ namespace System.Windows.Forms {
 
                        public void HideListBox (bool set_text)
                        {
-                               if (set_text) {
-                                       was_text_set = true;
-                                       owner.Text = items [HighlightedIndex];
-                                       owner.SelectAll ();
-                               }
+                               if (set_text)
+                                       owner.Text = owner.auto_complete_matches [HighlightedIndex];
 
                                Capture = false;
                                Hide ();
@@ -788,8 +980,8 @@ namespace System.Windows.Forms {
                        {
                                if (!user_defined_size) {
                                        // This should call the Layout routine for us
-                                       int height = items.Count > DefaultDropDownItems ? DefaultDropDownItems * item_height : 
-                                               (items.Count + 1) * item_height;
+                                       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 ();
@@ -874,9 +1066,9 @@ namespace System.Windows.Forms {
 
                                        if (i == highlighted_idx) {
                                                g.FillRectangle (SystemBrushes.Highlight, item_bounds);
-                                               g.DrawString (items [i], Font, SystemBrushes.HighlightText, item_bounds);
+                                               g.DrawString (owner.auto_complete_matches [i], Font, SystemBrushes.HighlightText, item_bounds);
                                        } else 
-                                               g.DrawString (items [i], Font, brush, item_bounds);
+                                               g.DrawString (owner.auto_complete_matches [i], Font, brush, item_bounds);
 
                                        y += item_height;
                                }