diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index efe228c78..c26a340ed 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -16,6 +16,161 @@ namespace Terminal.Gui { /// public class ComboBox : View { + private class ComboListView : ListView { + private int highlighted = -1; + private bool isFocusing; + private ComboBox container; + private bool hideDropdownListOnClick; + + public ComboListView (ComboBox container, bool hideDropdownListOnClick) + { + Initialize (container, hideDropdownListOnClick); + } + + public ComboListView (ComboBox container, Rect rect, IList source, bool hideDropdownListOnClick) : base (rect, source) + { + Initialize (container, hideDropdownListOnClick); + } + + public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick) : base (source) + { + Initialize (container, hideDropdownListOnClick); + } + + private void Initialize (ComboBox container, bool hideDropdownListOnClick) + { + if (container == null) + throw new ArgumentNullException ("ComboBox container cannot be null.", nameof (container)); + + this.container = container; + HideDropdownListOnClick = hideDropdownListOnClick; + } + + public bool HideDropdownListOnClick { + get => hideDropdownListOnClick; + set => hideDropdownListOnClick = WantContinuousButtonPressed = value; + } + + public override bool MouseEvent (MouseEvent me) + { + var res = false; + var isMousePositionValid = IsMousePositionValid (me); + + if (isMousePositionValid) { + res = base.MouseEvent (me); + } + + if (HideDropdownListOnClick && me.Flags == MouseFlags.Button1Clicked) { + if (!isMousePositionValid && !isFocusing) { + container.isShow = false; + container.HideList (); + } else if (isMousePositionValid) { + OnOpenSelectedItem (); + } else { + isFocusing = false; + } + return true; + } else if (me.Flags == MouseFlags.ReportMousePosition && HideDropdownListOnClick) { + if (isMousePositionValid) { + highlighted = Math.Min (TopItem + me.Y, Source.Count); + SetNeedsDisplay (); + } + isFocusing = false; + return true; + } + + return res; + } + + private bool IsMousePositionValid (MouseEvent me) + { + if (me.X >= 0 && me.X < Frame.Width && me.Y >= 0 && me.Y < Frame.Height) { + return true; + } + return false; + } + + public override void Redraw (Rect bounds) + { + var current = ColorScheme.Focus; + Driver.SetAttribute (current); + Move (0, 0); + var f = Frame; + var item = TopItem; + bool focused = HasFocus; + int col = AllowsMarking ? 2 : 0; + int start = LeftItem; + + for (int row = 0; row < f.Height; row++, item++) { + bool isSelected = item == container.SelectedItem; + bool isHighlighted = hideDropdownListOnClick && item == highlighted; + + Attribute newcolor; + if (isHighlighted || (isSelected && !hideDropdownListOnClick)) { + newcolor = focused ? ColorScheme.Focus : ColorScheme.HotNormal; + } else if (isSelected && hideDropdownListOnClick) { + newcolor = focused ? ColorScheme.HotFocus : ColorScheme.HotNormal; + } else { + newcolor = focused ? GetNormalColor () : GetNormalColor (); + } + + if (newcolor != current) { + Driver.SetAttribute (newcolor); + current = newcolor; + } + + Move (0, row); + if (Source == null || item >= Source.Count) { + for (int c = 0; c < f.Width; c++) + Driver.AddRune (' '); + } else { + var rowEventArgs = new ListViewRowEventArgs (item); + OnRowRender (rowEventArgs); + if (rowEventArgs.RowAttribute != null && current != rowEventArgs.RowAttribute) { + current = (Attribute)rowEventArgs.RowAttribute; + Driver.SetAttribute (current); + } + if (AllowsMarking) { + Driver.AddRune (Source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected)); + Driver.AddRune (' '); + } + Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start); + } + } + } + + public override bool OnEnter (View view) + { + if (hideDropdownListOnClick) { + isFocusing = true; + highlighted = container.SelectedItem; + Application.GrabMouse (this); + } + + return base.OnEnter (view); + } + + public override bool OnLeave (View view) + { + if (hideDropdownListOnClick) { + isFocusing = false; + highlighted = container.SelectedItem; + Application.UngrabMouse (); + } + + return base.OnLeave (view); + } + + public override bool OnSelectedChanged () + { + var res = base.OnSelectedChanged (); + + highlighted = SelectedItem; + + return res; + } + } + IListDataSource source; /// /// Gets or sets the backing this , enabling custom rendering. @@ -61,6 +216,16 @@ namespace Terminal.Gui { /// public event Action SelectedItemChanged; + /// + /// This event is raised when the drop-down list is expanded. + /// + public event Action Expanded; + + /// + /// This event is raised when the drop-down list is collapsed. + /// + public event Action Collapsed; + /// /// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item. /// @@ -69,7 +234,7 @@ namespace Terminal.Gui { readonly IList searchset = new List (); ustring text = ""; readonly TextField search; - readonly ListView listview; + readonly ComboListView listview; bool autoHide = true; int minimumHeight = 2; @@ -87,7 +252,7 @@ namespace Terminal.Gui { public ComboBox (ustring text) : base () { search = new TextField (""); - listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; + listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; Initialize (); Text = text; @@ -101,7 +266,20 @@ namespace Terminal.Gui { public ComboBox (Rect rect, IList source) : base (rect) { search = new TextField ("") { Width = rect.Width }; - listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + + Initialize (); + SetSource (source); + } + + /// + /// Initialize with the source. + /// + /// The source. + public ComboBox (IList source) : this (string.Empty) + { + search = new TextField (""); + listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; Initialize (); SetSource (source); @@ -133,7 +311,7 @@ namespace Terminal.Gui { listview.SelectedItemChanged += (ListViewItemEventArgs e) => { - if (searchset.Count > 0) { + if (!HideDropdownListOnClick && searchset.Count > 0) { SetValue (searchset [listview.SelectedItem]); } }; @@ -182,6 +360,8 @@ namespace Terminal.Gui { private bool isShow = false; private int selectedItem = -1; + private int lastSelectedItem = -1; + private bool hideDropdownListOnClick; /// /// Gets the index of the currently selected item in the @@ -193,7 +373,7 @@ namespace Terminal.Gui { if (selectedItem != value && (value == -1 || (source != null && value > -1 && value < source.Count))) { - selectedItem = value; + selectedItem = lastSelectedItem = value; if (selectedItem != -1) { SetValue (source.ToList () [selectedItem].ToString (), true); } else { @@ -236,6 +416,14 @@ namespace Terminal.Gui { } } + /// + /// Gets or sets if the drop-down list can be hide with a button click event. + /// + public bool HideDropdownListOnClick { + get => hideDropdownListOnClick; + set => hideDropdownListOnClick = listview.HideDropdownListOnClick = value; + } + /// public override bool MouseEvent (MouseEvent me) { @@ -268,10 +456,25 @@ namespace Terminal.Gui { private void FocusSelectedItem () { listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0; - if (SelectedItem > -1) { - listview.TabStop = true; - listview.SetFocus (); - } + listview.TabStop = true; + listview.SetFocus (); + OnExpanded (); + } + + /// + /// Virtual method which invokes the event. + /// + public virtual void OnExpanded () + { + Expanded?.Invoke (); + } + + /// + /// Virtual method which invokes the event. + /// + public virtual void OnCollapsed () + { + Collapsed?.Invoke (); } /// @@ -324,6 +527,7 @@ namespace Terminal.Gui { public virtual bool OnOpenSelectedItem () { var value = search.Text; + lastSelectedItem = SelectedItem; OpenSelectedItem?.Invoke (new ListViewItemEventArgs (SelectedItem, value)); return true; @@ -338,6 +542,7 @@ namespace Terminal.Gui { return; } + Driver.SetAttribute (ColorScheme.Focus); Move (Bounds.Right - 1, 0); Driver.AddRune (Driver.DownArrow); } @@ -362,8 +567,16 @@ namespace Terminal.Gui { bool CancelSelected () { search.SetFocus (); - search.Text = text = ""; - OnSelectedChanged (); + if (ReadOnly || HideDropdownListOnClick) { + SelectedItem = lastSelectedItem; + if (SelectedItem > -1 && listview.Source?.Count > 0) { + search.Text = text = listview.Source.ToList () [SelectedItem].ToString (); + } + } else if (!ReadOnly) { + search.Text = text = ""; + selectedItem = lastSelectedItem; + OnSelectedChanged (); + } Collapse (); return true; } @@ -635,12 +848,16 @@ namespace Terminal.Gui { /// Consider making public private void HideList () { + if (lastSelectedItem != selectedItem) { + OnOpenSelectedItem (); + } var rect = listview.ViewToScreen (listview.Bounds); Reset (SelectedItem > -1); listview.Clear (rect); listview.TabStop = false; SuperView?.SendSubviewToBack (this); SuperView?.SetNeedsDisplay (rect); + OnCollapsed (); } /// diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 11f625907..1a37fea3a 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -140,7 +140,7 @@ namespace Terminal.Gui { /// public void SetSource (IList source) { - if (source == null) + if (source == null && (Source == null || !(Source is ListWrapper))) Source = null; else { Source = MakeWrapper (source); @@ -157,7 +157,7 @@ namespace Terminal.Gui { public Task SetSourceAsync (IList source) { return Task.Factory.StartNew (() => { - if (source == null) + if (source == null && (Source == null || !(Source is ListWrapper))) Source = null; else Source = MakeWrapper (source); @@ -827,7 +827,7 @@ namespace Terminal.Gui { int GetMaxLengthItem () { - if (src?.Count == 0) { + if (src == null || src?.Count == 0) { return 0; } @@ -883,7 +883,7 @@ namespace Terminal.Gui { public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width, int start = 0) { container.Move (col, line); - var t = src [item]; + var t = src? [item]; if (t == null) { RenderUstr (driver, ustring.Make (""), col, line, width); } else { diff --git a/UICatalog/Scenarios/ComboBoxIteration.cs b/UICatalog/Scenarios/ComboBoxIteration.cs index f3b90d552..1c9e67002 100644 --- a/UICatalog/Scenarios/ComboBoxIteration.cs +++ b/UICatalog/Scenarios/ComboBoxIteration.cs @@ -33,7 +33,8 @@ namespace UICatalog.Scenarios { X = Pos.Right (listview) + 1, Y = Pos.Bottom (lbListView) + 1, Height = Dim.Fill (2), - Width = Dim.Percent (40) + Width = Dim.Percent (40), + HideDropdownListOnClick = true }; comboBox.SetSource (items); diff --git a/UnitTests/ComboBoxTests.cs b/UnitTests/ComboBoxTests.cs index 4a7005a16..e6e0f731e 100644 --- a/UnitTests/ComboBoxTests.cs +++ b/UnitTests/ComboBoxTests.cs @@ -21,18 +21,42 @@ namespace Terminal.Gui.Views { Assert.Null (cb.Source); Assert.False (cb.AutoSize); Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame); + Assert.Equal (-1, cb.SelectedItem); cb = new ComboBox ("Test"); Assert.Equal ("Test", cb.Text); Assert.Null (cb.Source); Assert.False (cb.AutoSize); Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame); + Assert.Equal (-1, cb.SelectedItem); cb = new ComboBox (new Rect (1, 2, 10, 20), new List () { "One", "Two", "Three" }); Assert.Equal (string.Empty, cb.Text); Assert.NotNull (cb.Source); Assert.False (cb.AutoSize); Assert.Equal (new Rect (1, 2, 10, 20), cb.Frame); + Assert.Equal (-1, cb.SelectedItem); + + cb = new ComboBox (new List () { "One", "Two", "Three" }); + Assert.Equal (string.Empty, cb.Text); + Assert.NotNull (cb.Source); + Assert.False (cb.AutoSize); + Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame); + Assert.Equal (-1, cb.SelectedItem); + } + + [Fact] + [AutoInitShutdown] + public void Constructor_With_Source_Initialize_With_The_Passed_SelectedItem () + { + var cb = new ComboBox (new List () { "One", "Two", "Three" }) { + SelectedItem = 1 + }; + Assert.Equal ("Two", cb.Text); + Assert.NotNull (cb.Source); + Assert.False (cb.AutoSize); + Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame); + Assert.Equal (1, cb.SelectedItem); } [Fact] @@ -240,5 +264,676 @@ Three Assert.Equal (-1, cb.SelectedItem); Assert.Equal ("", cb.Text); } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_Gets_Sets () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5 + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.False (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (0, cb.SelectedItem); + Assert.Equal ("One", cb.Text); + + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = 1, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = 1, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + cb.HideDropdownListOnClick = true; + + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = 2, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("Three", selected); + Assert.False (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = 2, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("Three", selected); + Assert.False (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("Three", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("One", selected); + Assert.False (cb.IsShow); + Assert.Equal (0, cb.SelectedItem); + Assert.Equal ("One", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_True_OpenSelectedItem_With_Mouse_And_Key_And_Mouse () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = true + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.True (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()))); + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_True_OpenSelectedItem_With_Mouse_And_Key_CursorDown_And_Esc () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = true + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.True (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_False_OpenSelectedItem_With_Mouse_And_Key_CursorDown_And_Esc () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = false + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.False (cb.HideDropdownListOnClick); + Assert.False (cb.ReadOnly); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (0, cb.SelectedItem); + Assert.Equal ("One", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_False_ReadOnly_True_OpenSelectedItem_With_Mouse_And_Key_CursorDown_And_Esc () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = false, + ReadOnly = true + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.False (cb.HideDropdownListOnClick); + Assert.True (cb.ReadOnly); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (0, cb.SelectedItem); + Assert.Equal ("One", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.Esc, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_True_OpenSelectedItem_With_Mouse_And_Key_F4 () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = true + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.True (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_False_OpenSelectedItem_With_Mouse_And_Key_F4 () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = false + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.False (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (0, cb.SelectedItem); + Assert.Equal ("One", cb.Text); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Two", selected); + Assert.False (cb.IsShow); + Assert.Equal (1, cb.SelectedItem); + Assert.Equal ("Two", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_True_Colapse_On_Click_Outside_Frame () + { + var selected = ""; + var cb = new ComboBox { + Height = 4, + Width = 5, + HideDropdownListOnClick = true + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.True (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = -1, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = -1, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = cb.Frame.Width, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + Assert.True (cb.Subviews [1].MouseEvent (new MouseEvent { + X = 0, + Y = cb.Frame.Height, + Flags = MouseFlags.Button1Clicked + })); + Assert.Equal ("", selected); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + } + + [Fact, AutoInitShutdown] + public void HideDropdownListOnClick_True_Highlight_Current_Item () + { + var selected = ""; + var cb = new ComboBox { + Width = 6, + Height = 4, + HideDropdownListOnClick = true, + }; + cb.SetSource (new List { "One", "Two", "Three" }); + cb.OpenSelectedItem += (e) => selected = e.Value.ToString (); + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.True (cb.HideDropdownListOnClick); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.MouseEvent (new MouseEvent { + X = cb.Bounds.Right - 1, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverContentsWithFrameAre (@" + ▼ +One +Two +Three ", output); + + var attributes = new Attribute [] { + // 0 + cb.Subviews [0].ColorScheme.Focus, + // 1 + cb.Subviews [1].ColorScheme.HotFocus, + // 2 + cb.Subviews [1].GetNormalColor () + }; + + GraphViewTests.AssertDriverColorsAre (@" +000000 +00000 +22222 +22222", attributes); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverColorsAre (@" +000000 +22222 +00000 +22222", attributes); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()))); + Assert.Equal ("", selected); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverColorsAre (@" +000000 +22222 +22222 +00000", attributes); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + Assert.Equal ("Three", selected); + Assert.False (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Three", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverColorsAre (@" +000000 +22222 +22222 +00000", attributes); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()))); + Assert.Equal ("Three", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverColorsAre (@" +000000 +22222 +00000 +11111", attributes); + + Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()))); + Assert.Equal ("Three", selected); + Assert.True (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + cb.Redraw (cb.Bounds); + GraphViewTests.AssertDriverColorsAre (@" +000000 +00000 +22222 +11111", attributes); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Equal ("Three", selected); + Assert.False (cb.IsShow); + Assert.Equal (2, cb.SelectedItem); + Assert.Equal ("Three", cb.Text); + } + + [Fact, AutoInitShutdown] + public void Expanded_Collapsed_Events () + { + var cb = new ComboBox { + Height = 4, + Width = 5 + }; + var list = new List { "One", "Two", "Three" }; + + cb.Expanded += () => cb.SetSource (list); + cb.Collapsed += () => cb.Source = null; + + Application.Top.Add (cb); + Application.Begin (Application.Top); + + Assert.Null (cb.Source); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.NotNull (cb.Source); + Assert.True (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + + Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); + Assert.Null (cb.Source); + Assert.False (cb.IsShow); + Assert.Equal (-1, cb.SelectedItem); + Assert.Equal ("", cb.Text); + } } } \ No newline at end of file diff --git a/UnitTests/ListViewTests.cs b/UnitTests/ListViewTests.cs index ee9a6be71..03b01547a 100644 --- a/UnitTests/ListViewTests.cs +++ b/UnitTests/ListViewTests.cs @@ -431,5 +431,25 @@ namespace Terminal.Gui.Views { │Line9 │ └──────────┘", output); } + + [Fact] + public void SetSource_Preserves_ListWrapper_Instance_If_Not_Null () + { + var lv = new ListView (new List { "One", "Two" }); + + Assert.NotNull (lv.Source); + + lv.SetSource (null); + Assert.NotNull (lv.Source); + + lv.Source = null; + Assert.Null (lv.Source); + + lv = new ListView (new List { "One", "Two" }); + Assert.NotNull (lv.Source); + + lv.SetSourceAsync (null); + Assert.NotNull (lv.Source); + } } }