Browse Source

Merge branch 'develop' of tig:migueldeicaza/gui.cs into develop

Charlie Kindel 2 years ago
parent
commit
9259a9b995

+ 8 - 1
Terminal.Gui/Core/Application.cs

@@ -638,6 +638,11 @@ namespace Terminal.Gui {
 			}
 			RootMouseEvent?.Invoke (me);
 			if (mouseGrabView != null) {
+				if (view == null) {
+					UngrabMouse ();
+					return;
+				}
+
 				var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
 				var nme = new MouseEvent () {
 					X = newxy.X,
@@ -653,7 +658,9 @@ namespace Terminal.Gui {
 				// System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
 				if (mouseGrabView != null) {
 					mouseGrabView.OnMouseEvent (nme);
-					return;
+					if (mouseGrabView != null) {
+						return;
+					}
 				}
 			}
 

+ 19 - 7
Terminal.Gui/Core/MainLoop.cs

@@ -6,6 +6,7 @@
 //
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -61,6 +62,11 @@ namespace Terminal.Gui {
 
 		internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
 		object timeoutsLockToken = new object ();
+
+		/// <summary>
+		/// The idle handlers and lock that must be held while manipulating them
+		/// </summary>
+		object idleHandlersLock = new object ();
 		internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
 
 		/// <summary>
@@ -71,9 +77,15 @@ namespace Terminal.Gui {
 		public SortedList<long, Timeout> Timeouts => timeouts;
 
 		/// <summary>
-		/// Gets the list of all idle handlers.
+		/// Gets a copy of the list of all idle handlers.
 		/// </summary>
-		public List<Func<bool>> IdleHandlers => idleHandlers;
+		public ReadOnlyCollection<Func<bool>> IdleHandlers {
+			get {
+				lock (idleHandlersLock) {
+					return new List<Func<bool>> (idleHandlers).AsReadOnly ();
+				}
+			}
+		}
 
 		/// <summary>
 		/// The current IMainLoopDriver in use.
@@ -123,7 +135,7 @@ namespace Terminal.Gui {
 		/// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
 		public Func<bool> AddIdle (Func<bool> idleHandler)
 		{
-			lock (idleHandlers) {
+			lock (idleHandlersLock) {
 				idleHandlers.Add (idleHandler);
 			}
 
@@ -139,7 +151,7 @@ namespace Terminal.Gui {
 		///  This method also returns <c>false</c> if the idle handler is not found.
 		public bool RemoveIdle (Func<bool> token)
 		{
-			lock (token)
+			lock (idleHandlersLock)
 				return idleHandlers.Remove (token);
 		}
 
@@ -242,14 +254,14 @@ namespace Terminal.Gui {
 		void RunIdle ()
 		{
 			List<Func<bool>> iterate;
-			lock (idleHandlers) {
+			lock (idleHandlersLock) {
 				iterate = idleHandlers;
 				idleHandlers = new List<Func<bool>> ();
 			}
 
 			foreach (var idle in iterate) {
 				if (idle ())
-					lock (idleHandlers)
+					lock (idleHandlersLock)
 						idleHandlers.Add (idle);
 			}
 		}
@@ -294,7 +306,7 @@ namespace Terminal.Gui {
 
 			Driver.MainIteration ();
 
-			lock (idleHandlers) {
+			lock (idleHandlersLock) {
 				if (idleHandlers.Count > 0)
 					RunIdle ();
 			}

+ 12 - 0
Terminal.Gui/Core/TextFormatter.cs

@@ -835,6 +835,18 @@ namespace Terminal.Gui {
 			return max;
 		}
 
+		/// <summary>
+		/// Determines the line with the highest width in the 
+		/// <paramref name="text"/> if it contains newlines.
+		/// </summary>
+		/// <param name="text">Text, may contain newlines.</param>
+		/// <returns>The highest line width.</returns>
+		public static int MaxWidthLine (ustring text)
+		{
+			var result = TextFormatter.SplitNewLine (text);
+			return result.Max (x => x.ConsoleWidth);
+		}
+
 		/// <summary>
 		/// Gets the total width of the passed text.
 		/// </summary>

+ 228 - 11
Terminal.Gui/Views/ComboBox.cs

@@ -16,6 +16,161 @@ namespace Terminal.Gui {
 	/// </summary>
 	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;
 		/// <summary>
 		/// Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.
@@ -61,6 +216,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action<ListViewItemEventArgs> SelectedItemChanged;
 
+		/// <summary>
+		/// This event is raised when the drop-down list is expanded.
+		/// </summary>
+		public event Action Expanded;
+
+		/// <summary>
+		/// This event is raised when the drop-down list is collapsed.
+		/// </summary>
+		public event Action Collapsed;
+
 		/// <summary>
 		/// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.
 		/// </summary>
@@ -69,7 +234,7 @@ namespace Terminal.Gui {
 		readonly IList searchset = new List<object> ();
 		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);
+		}
+
+		/// <summary>
+		/// Initialize with the source.
+		/// </summary>
+		/// <param name="source">The source.</param>
+		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;
 
 		/// <summary>
 		/// Gets the index of the currently selected item in the <see cref="Source"/>
@@ -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 {
 			}
 		}
 
+		/// <summary>
+		/// Gets or sets if the drop-down list can be hide with a button click event.
+		/// </summary>
+		public bool HideDropdownListOnClick {
+			get => hideDropdownListOnClick;
+			set => hideDropdownListOnClick = listview.HideDropdownListOnClick = value;
+		}
+
 		///<inheritdoc/>
 		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 ();
+		}
+
+		/// <summary>
+		/// Virtual method which invokes the <see cref="Expanded"/> event.
+		/// </summary>
+		public virtual void OnExpanded ()
+		{
+			Expanded?.Invoke ();
+		}
+
+		/// <summary>
+		/// Virtual method which invokes the <see cref="Collapsed"/> event.
+		/// </summary>
+		public virtual void OnCollapsed ()
+		{
+			Collapsed?.Invoke ();
 		}
 
 		///<inheritdoc/>
@@ -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 ();
 		}
 
 		/// <summary>

+ 4 - 4
Terminal.Gui/Views/ListView.cs

@@ -140,7 +140,7 @@ namespace Terminal.Gui {
 		/// </remarks>
 		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 {

+ 0 - 1
Terminal.Gui/Views/ScrollView.cs

@@ -517,7 +517,6 @@ namespace Terminal.Gui {
 				horizontal.MouseEvent (me);
 			} else if (IsOverridden (me.View)) {
 				Application.UngrabMouse ();
-				return false;
 			}
 			return true;
 		}

+ 1 - 2
Terminal.Gui/Windows/Dialog.cs

@@ -160,7 +160,7 @@ namespace Terminal.Gui {
 			switch (ButtonAlignment) {
 			case ButtonAlignments.Center:
 				// Center Buttons
-				shiftLeft = Math.Max ((Bounds.Width - buttonsWidth - buttons.Count - 2) / 2 + 1, 0);
+				shiftLeft = (Bounds.Width - buttonsWidth - buttons.Count - 2) / 2 + 1;
 				for (int i = buttons.Count - 1; i >= 0; i--) {
 					Button button = buttons [i];
 					shiftLeft += button.Frame.Width + (i == buttons.Count - 1 ? 0 : 1);
@@ -231,6 +231,5 @@ namespace Terminal.Gui {
 			}
 			return base.ProcessKey (kb);
 		}
-
 	}
 }

+ 27 - 15
Terminal.Gui/Windows/MessageBox.cs

@@ -1,7 +1,6 @@
 using NStack;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -240,7 +239,16 @@ namespace Terminal.Gui {
 			int defaultButton = 0, Border border = null, params ustring [] buttons)
 		{
 			const int defaultWidth = 50;
-			int textWidth = TextFormatter.MaxWidth (message, width == 0 ? defaultWidth : width);
+			int maxWidthLine = TextFormatter.MaxWidthLine (message);
+			if (maxWidthLine > Application.Driver.Cols) {
+				maxWidthLine = Application.Driver.Cols;
+			}
+			if (width == 0) {
+				maxWidthLine = Math.Max (maxWidthLine, defaultWidth);
+			} else {
+				maxWidthLine = width;
+			}
+			int textWidth = TextFormatter.MaxWidth (message, maxWidthLine);
 			int textHeight = TextFormatter.MaxLines (message, textWidth); // message.Count (ustring.Make ('\n')) + 1;
 			int msgboxHeight = Math.Max (1, textHeight) + 4; // textHeight + (top + top padding + buttons + bottom)
 
@@ -262,10 +270,11 @@ namespace Terminal.Gui {
 			// Create Dialog (retain backwards compat by supporting specifying height/width)
 			Dialog d;
 			if (width == 0 & height == 0) {
-				d = new Dialog (title, buttonList.ToArray ());
-				d.Height = msgboxHeight;
+				d = new Dialog (title, buttonList.ToArray ()) {
+					Height = msgboxHeight
+				};
 			} else {
-				d = new Dialog (title, Math.Max (width, textWidth) + 4, height, buttonList.ToArray ());
+				d = new Dialog (title, width, Math.Max (height, 4), buttonList.ToArray ());
 			}
 
 			if (border != null) {
@@ -277,19 +286,22 @@ namespace Terminal.Gui {
 			}
 
 			if (message != null) {
-				var l = new Label (textWidth > width ? 0 : (width - 4 - textWidth) / 2, 1, message);
-				l.LayoutStyle = LayoutStyle.Computed;
-				l.TextAlignment = TextAlignment.Centered;
-				l.X = Pos.Center ();
-				l.Y = Pos.Center ();
-				l.Width = Dim.Fill (2);
-				l.Height = Dim.Fill (1);
+				var l = new Label (message) {
+					LayoutStyle = LayoutStyle.Computed,
+					TextAlignment = TextAlignment.Centered,
+					X = Pos.Center (),
+					Y = Pos.Center (),
+					Width = Dim.Fill (),
+					Height = Dim.Fill (1),
+					AutoSize = false
+				};
 				d.Add (l);
 			}
 
-			// Dynamically size Width
-			int msgboxWidth = Math.Max (defaultWidth, Math.Max (title.RuneCount + 8, Math.Max (textWidth + 4, d.GetButtonsWidth ()) + 8)); // textWidth + (left + padding + padding + right)
-			d.Width = msgboxWidth;
+			if (width == 0 & height == 0) {
+				// Dynamically size Width
+				d.Width = Math.Max (maxWidthLine, Math.Max (title.ConsoleWidth, Math.Max (textWidth + 2, d.GetButtonsWidth ()))); // textWidth + (left + padding + padding + right)
+			}
 
 			// Setup actions
 			Clicked = -1;

+ 2 - 1
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);
 

+ 5 - 1
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -135,11 +135,15 @@ namespace UICatalog.Scenarios {
 
 			void Top_Unloaded ()
 			{
+				if (_fractionTimer != null) {
+					_fractionTimer.Dispose ();
+					_fractionTimer = null;
+				}
 				if (_pulseTimer != null) {
 					_pulseTimer.Dispose ();
 					_pulseTimer = null;
-					Top.Unloaded -= Top_Unloaded;
 				}
+				Top.Unloaded -= Top_Unloaded;
 			}
 		}
 	}

+ 82 - 0
UnitTests/ApplicationTests.cs

@@ -1408,5 +1408,87 @@ namespace Terminal.Gui.Core {
 
 			Application.Shutdown ();
 		}
+
+		[Fact, AutoInitShutdown]
+		public void MouseGrabView_WithNullMouseEventView ()
+		{
+			var tf = new TextField () { Width = 10 };
+			var sv = new ScrollView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				ContentSize = new Size (100, 100)
+			};
+
+			sv.Add (tf);
+			Application.Top.Add (sv);
+
+			var iterations = -1;
+
+			Application.Iteration = () => {
+				iterations++;
+				if (iterations == 0) {
+					Assert.True (tf.HasFocus);
+					Assert.Null (Application.mouseGrabView);
+
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 5,
+							Y = 5,
+							Flags = MouseFlags.ReportMousePosition
+						});
+
+					Assert.Equal (sv, Application.mouseGrabView);
+
+					MessageBox.Query ("Title", "Test", "Ok");
+
+					Assert.Null (Application.mouseGrabView);
+				} else if (iterations == 1) {
+					Assert.Equal (sv, Application.mouseGrabView);
+
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 5,
+							Y = 5,
+							Flags = MouseFlags.ReportMousePosition
+						});
+
+					Assert.Null (Application.mouseGrabView);
+
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 40,
+							Y = 12,
+							Flags = MouseFlags.ReportMousePosition
+						});
+
+					Assert.Null (Application.mouseGrabView);
+
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 0,
+							Y = 0,
+							Flags = MouseFlags.Button1Pressed
+						});
+
+					Assert.Null (Application.mouseGrabView);
+
+					Application.RequestStop ();
+				} else if (iterations == 2) {
+					Assert.Null (Application.mouseGrabView);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
 	}
 }

+ 695 - 0
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<string> () { "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<string> () { "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<string> () { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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);
+		}
 	}
 }

+ 20 - 0
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<string> { "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<string> { "One", "Two" });
+			Assert.NotNull (lv.Source);
+
+			lv.SetSourceAsync (null);
+			Assert.NotNull (lv.Source);
+		}
 	}
 }

+ 80 - 2
UnitTests/MainLoopTests.cs

@@ -29,12 +29,18 @@ namespace Terminal.Gui.Core {
 		{
 			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
-			Func<bool> fnTrue = () => { return true; };
-			Func<bool> fnFalse = () => { return false; };
+			Func<bool> fnTrue = () => true;
+			Func<bool> fnFalse = () => false;
+
 			ml.AddIdle (fnTrue);
 			ml.AddIdle (fnFalse);
 
+			Assert.Equal (2, ml.IdleHandlers.Count);
+			Assert.Equal (fnTrue, ml.IdleHandlers [0]);
+			Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
+
 			Assert.True (ml.RemoveIdle (fnTrue));
+			Assert.Single (ml.IdleHandlers);
 
 			// BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either 
 			// throw an exception in this case, or return an error.
@@ -52,8 +58,19 @@ namespace Terminal.Gui.Core {
 			ml.AddIdle (fnTrue);
 			ml.AddIdle (fnTrue);
 
+			Assert.Equal (2, ml.IdleHandlers.Count);
+			Assert.Equal (fnTrue, ml.IdleHandlers [0]);
+			Assert.True (ml.IdleHandlers [0] ());
+			Assert.Equal (fnTrue, ml.IdleHandlers [1]);
+			Assert.True (ml.IdleHandlers [1] ());
+
 			Assert.True (ml.RemoveIdle (fnTrue));
+			Assert.Single (ml.IdleHandlers);
+			Assert.Equal (fnTrue, ml.IdleHandlers [0]);
+			Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
+
 			Assert.True (ml.RemoveIdle (fnTrue));
+			Assert.Empty (ml.IdleHandlers);
 
 			// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either 
 			// throw an exception in this case, or return an error.
@@ -125,14 +142,17 @@ namespace Terminal.Gui.Core {
 			ml.AddIdle (fn);
 			ml.MainIteration ();
 			Assert.Equal (2, functionCalled);
+			Assert.Equal (2, ml.IdleHandlers.Count);
 
 			functionCalled = 0;
 			Assert.True (ml.RemoveIdle (fn));
+			Assert.Single (ml.IdleHandlers);
 			ml.MainIteration ();
 			Assert.Equal (1, functionCalled);
 
 			functionCalled = 0;
 			Assert.True (ml.RemoveIdle (fn));
+			Assert.Empty (ml.IdleHandlers);
 			ml.MainIteration ();
 			Assert.Equal (0, functionCalled);
 			Assert.False (ml.RemoveIdle (fn));
@@ -505,5 +525,63 @@ namespace Terminal.Gui.Core {
 		// - wait = false
 
 		// TODO: Add IMainLoop tests
+
+		volatile static int tbCounter = 0;
+
+		private static void Launch (Random r, TextField tf)
+		{
+			Task.Run (() => {
+				Thread.Sleep (r.Next (2, 4));
+				Application.MainLoop.Invoke (() => {
+					tf.Text = $"index{r.Next ()}";
+					Interlocked.Increment (ref tbCounter);
+				});
+			});
+		}
+
+		private static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs)
+		{
+			for (int j = 0; j < numPasses; j++) {
+				for (var i = 0; i < numIncrements; i++) {
+					Launch (r, tf);
+				}
+
+				while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
+				{
+					var tbNow = tbCounter;
+					Thread.Sleep (pollMs);
+					if (tbCounter == tbNow) {
+						// No change after sleep: Idle handlers added via Application.MainLoop.Invoke have gone missing
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+						throw new TimeoutException (
+							$"Timeout: Increment lost. tbCounter ({tbCounter}) didn't " +
+							$"change after waiting {pollMs} ms. Failed to reach {(j + 1) * numIncrements} on pass {j + 1}");
+					}
+				};
+			}
+			Application.MainLoop.Invoke (() => Application.RequestStop ());
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public async Task InvokeLeakTest ()
+		{
+			Random r = new ();
+			TextField tf = new ();
+			Application.Top.Add (tf);
+
+			const int numPasses = 10;
+			const int numIncrements = 10000;
+			const int pollMs = 500;
+
+			var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs));
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+
+			await task; // Propagate exception if any occurred
+
+			Assert.Equal ((numIncrements * numPasses), tbCounter);
+		}
 	}
 }

+ 158 - 0
UnitTests/MessageBoxTests.cs

@@ -0,0 +1,158 @@
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+using System.Text;
+
+namespace Terminal.Gui.Views {
+
+	public class MessageBoxTests {
+		readonly ITestOutputHelper output;
+
+		public MessageBoxTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_Empty_Size_Without_Buttons ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					MessageBox.Query ("Title", "Message");
+
+					Application.RequestStop ();
+
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+               ┌ Title ─────────────────────────────────────────┐
+               │                    Message                     │
+               │                                                │
+               │                                                │
+               └────────────────────────────────────────────────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_Empty_Size_With_Button ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					StringBuilder aboutMessage = new StringBuilder ();
+					aboutMessage.AppendLine (@"A comprehensive sample library for");
+					aboutMessage.AppendLine (@"");
+					aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
+					aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
+					aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
+					aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
+					aboutMessage.AppendLine (@"    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ");
+					aboutMessage.AppendLine (@"    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
+					aboutMessage.AppendLine (@"");
+					aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
+
+					MessageBox.Query ("About UI Catalog", aboutMessage.ToString (), "_Ok");
+
+					Application.RequestStop ();
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+         ┌ About UI Catalog ──────────────────────────────────────────┐
+         │             A comprehensive sample library for             │
+         │                                                            │
+         │  _______                  _             _   _____       _  │
+         │ |__   __|                (_)           | | / ____|     (_) │
+         │    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  │
+         │    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | │
+         │    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | │
+         │    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| │
+         │                                                            │
+         │           https://github.com/gui-cs/Terminal.Gui           │
+         │                                                            │
+         │                          [◦ Ok ◦]                          │
+         └────────────────────────────────────────────────────────────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_A_Lower_Fixed_Size ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					MessageBox.Query (7, 5, "Title", "Message", "_Ok");
+
+					Application.RequestStop ();
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                                    ┌─────┐
+                                    │Messa│
+                                    │ ge  │
+                                    │ Ok ◦│
+                                    └─────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_A_Enough_Fixed_Size ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					MessageBox.Query (11, 5, "Title", "Message", "_Ok");
+
+					Application.RequestStop ();
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                                  ┌ Title ──┐
+                                  │ Message │
+                                  │         │
+                                  │[◦ Ok ◦] │
+                                  └─────────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
+	}
+}

+ 35 - 0
UnitTests/ReflectionTools.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Reflection;
+
+public static class ReflectionTools {
+	// If the class is non-static
+	public static Object InvokePrivate (Object objectUnderTest, string method, params object [] args)
+	{
+		Type t = objectUnderTest.GetType ();
+		return t.InvokeMember (method,
+		    BindingFlags.InvokeMethod |
+		    BindingFlags.NonPublic |
+		    BindingFlags.Instance |
+		    BindingFlags.Static,
+		    null,
+		    objectUnderTest,
+		    args);
+	}
+	// if the class is static
+	public static Object InvokePrivate (Type typeOfObjectUnderTest, string method, params object [] args)
+	{
+		MemberInfo [] members = typeOfObjectUnderTest.GetMembers (BindingFlags.NonPublic | BindingFlags.Static);
+		foreach (var member in members) {
+			if (member.Name == method) {
+				return typeOfObjectUnderTest.InvokeMember (method,
+					BindingFlags.NonPublic |
+					BindingFlags.Static |
+					BindingFlags.InvokeMethod,
+					null,
+					typeOfObjectUnderTest,
+					args);
+			}
+		}
+		return null;
+	}
+}

+ 10 - 0
UnitTests/TextFormatterTests.cs

@@ -4054,5 +4054,15 @@ e
 			Assert.Equal ("Third Line 界", splited [2]);
 			Assert.Equal ("", splited [^1]);
 		}
+
+		[Fact]
+		public void MaxWidthLine_With_And_Without_Newlines ()
+		{
+			var text = "Single Line 界";
+			Assert.Equal (14, TextFormatter.MaxWidthLine (text));
+
+			text = $"First Line 界\nSecond Line 界\nThird Line 界\n";
+			Assert.Equal (14, TextFormatter.MaxWidthLine (text));
+		}
 	}
 }

+ 1 - 1
UnitTests/UnitTests.csproj

@@ -19,7 +19,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
-    <PackageReference Include="ReportGenerator" Version="5.1.9" />
+    <PackageReference Include="ReportGenerator" Version="5.1.10" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.2" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">