2
0
Эх сурвалжийг харах

Merge branch 'develop' into v_1_8_0

Tig Kindel 2 жил өмнө
parent
commit
fa09d6d474
56 өөрчлөгдсөн 3548 нэмэгдсэн , 538 устгасан
  1. 1 0
      .gitignore
  2. 6 42
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  3. 4 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
  4. 6 41
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  5. 6 40
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  6. 6 35
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  7. 19 2
      Terminal.Gui/Core/Application.cs
  8. 55 0
      Terminal.Gui/Core/ConsoleDriver.cs
  9. 19 7
      Terminal.Gui/Core/MainLoop.cs
  10. 71 7
      Terminal.Gui/Core/TextFormatter.cs
  11. 52 18
      Terminal.Gui/Core/View.cs
  12. 1 1
      Terminal.Gui/Terminal.Gui.csproj
  13. 228 11
      Terminal.Gui/Views/ComboBox.cs
  14. 12 4
      Terminal.Gui/Views/ListView.cs
  15. 15 6
      Terminal.Gui/Views/Menu.cs
  16. 43 15
      Terminal.Gui/Views/ScrollBarView.cs
  17. 0 1
      Terminal.Gui/Views/ScrollView.cs
  18. 9 1
      Terminal.Gui/Views/TextField.cs
  19. 123 50
      Terminal.Gui/Views/TextView.cs
  20. 1 1
      Terminal.Gui/Views/TreeView.cs
  21. 1 2
      Terminal.Gui/Windows/Dialog.cs
  22. 27 15
      Terminal.Gui/Windows/MessageBox.cs
  23. 0 5
      Terminal.Gui/Windows/Wizard.cs
  24. 9 2
      UICatalog/KeyBindingsDialog.cs
  25. 0 1
      UICatalog/Scenarios/AutoSizeAndDirectionText.cs
  26. 0 3
      UICatalog/Scenarios/BordersComparisons.cs
  27. 0 1
      UICatalog/Scenarios/BordersOnFrameView.cs
  28. 0 1
      UICatalog/Scenarios/BordersOnToplevel.cs
  29. 0 1
      UICatalog/Scenarios/BordersOnWindow.cs
  30. 2 1
      UICatalog/Scenarios/ComboBoxIteration.cs
  31. 0 1
      UICatalog/Scenarios/DynamicMenuBar.cs
  32. 0 1
      UICatalog/Scenarios/DynamicStatusBar.cs
  33. 34 27
      UICatalog/Scenarios/Editor.cs
  34. 11 18
      UICatalog/Scenarios/GraphViewExample.cs
  35. 0 1
      UICatalog/Scenarios/MessageBoxes.cs
  36. 5 1
      UICatalog/Scenarios/ProgressBarStyles.cs
  37. 59 63
      UICatalog/Scenarios/SyntaxHighlighting.cs
  38. 0 1
      UICatalog/Scenarios/Text.cs
  39. 1 1
      UICatalog/Scenarios/TextAlignmentsAndDirection.cs
  40. 0 6
      UICatalog/Scenarios/TextViewAutocompletePopup.cs
  41. 1 0
      UICatalog/Scenarios/Wizards.cs
  42. 157 2
      UnitTests/ApplicationTests.cs
  43. 38 18
      UnitTests/CheckboxTests.cs
  44. 695 0
      UnitTests/ComboBoxTests.cs
  45. 239 30
      UnitTests/ContextMenuTests.cs
  46. 321 0
      UnitTests/ListViewTests.cs
  47. 88 2
      UnitTests/MainLoopTests.cs
  48. 2 2
      UnitTests/MenuTests.cs
  49. 158 0
      UnitTests/MessageBoxTests.cs
  50. 35 0
      UnitTests/ReflectionTools.cs
  51. 202 23
      UnitTests/ScrollBarViewTests.cs
  52. 149 24
      UnitTests/TextFormatterTests.cs
  53. 537 1
      UnitTests/TextViewTests.cs
  54. 9 0
      UnitTests/TreeViewTests.cs
  55. 2 2
      UnitTests/UnitTests.csproj
  56. 89 0
      UnitTests/ViewTests.cs

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@ packages
 docfx/api
 
 # Never push ./docs folder - the gh-pages branch is now used to publish to GH Pages
+docs/
 
 UnitTests/TestResults
 

+ 6 - 42
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -191,7 +191,7 @@ namespace Terminal.Gui {
 				background: MapCursesColor (background));
 		}
 
-		static Attribute MakeColor (Color fore, Color back)
+		public override Attribute MakeColor (Color fore, Color back)
 		{
 			return MakeColor ((short)MapColor (fore), (short)MapColor (back));
 		}
@@ -663,7 +663,7 @@ namespace Terminal.Gui {
 
 			// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
 			if (wch == 27) {
-				Curses.timeout (200);
+				Curses.timeout (10);
 
 				code = Curses.get_wch (out int wch2);
 
@@ -820,6 +820,7 @@ namespace Terminal.Gui {
 				//Console.Out.Flush ();
 
 				window = Curses.initscr ();
+				Curses.set_escdelay (10);
 			} catch (Exception e) {
 				throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}");
 			}
@@ -889,51 +890,14 @@ namespace Terminal.Gui {
 			//UpArrow = Curses.ACS_UARROW;
 			//DownArrow = Curses.ACS_DARROW;
 
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-
 			if (Curses.HasColors) {
 				Curses.StartColor ();
 				Curses.UseDefaultColors ();
 
-				Colors.TopLevel.Normal = MakeColor (Color.Green, Color.Black);
-				Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
-				Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);
-				Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan);
-				Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black);
-
-				Colors.Base.Normal = MakeColor (Color.White, Color.Blue);
-				Colors.Base.Focus = MakeColor (Color.Black, Color.Gray);
-				Colors.Base.HotNormal = MakeColor (Color.BrightCyan, Color.Blue);
-				Colors.Base.HotFocus = MakeColor (Color.BrightBlue, Color.Gray);
-				Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue);
-
-				// Focused,
-				//    Selected, Hot: Yellow on Black
-				//    Selected, text: white on black
-				//    Unselected, hot: yellow on cyan
-				//    unselected, text: same as unfocused
-				Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray);
-				Colors.Menu.Focus = MakeColor (Color.White, Color.Black);
-				Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray);
-				Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black);
-				Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray);
-
-				Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray);
-				Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray);
-				Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray);
-				Colors.Dialog.HotFocus = MakeColor (Color.Blue, Color.DarkGray);
-				Colors.Dialog.Disabled = MakeColor (Color.DarkGray, Color.Gray);
-
-				Colors.Error.Normal = MakeColor (Color.Red, Color.White);
-				Colors.Error.Focus = MakeColor (Color.White, Color.Red);
-				Colors.Error.HotNormal = MakeColor (Color.Black, Color.White);
-				Colors.Error.HotFocus = MakeColor (Color.Black, Color.Red);
-				Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
+				CreateColors ();
 			} else {
+				CreateColors (false);
+
 				Colors.TopLevel.Normal = Curses.COLOR_GREEN;
 				Colors.TopLevel.Focus = Curses.COLOR_WHITE;
 				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;

+ 4 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -330,6 +330,7 @@ namespace Unix.Terminal {
 		static public int reset_shell_mode () => methods.reset_shell_mode ();
 		static public int savetty () => methods.savetty ();
 		static public int resetty () => methods.resetty ();
+		static public int set_escdelay (int size) => methods.set_escdelay (size);
 	}
 
 #pragma warning disable RCS1102 // Make class static.
@@ -405,6 +406,7 @@ namespace Unix.Terminal {
 		public delegate int reset_shell_mode ();
 		public delegate int savetty ();
 		public delegate int resetty ();
+		public delegate int set_escdelay (int size);
 	}
 
 	internal class NativeMethods {
@@ -478,6 +480,7 @@ namespace Unix.Terminal {
 		public readonly Delegates.reset_shell_mode reset_shell_mode;
 		public readonly Delegates.savetty savetty;
 		public readonly Delegates.resetty resetty;
+		public readonly Delegates.set_escdelay set_escdelay;
 		public UnmanagedLibrary UnmanagedLibrary;
 
 		public NativeMethods (UnmanagedLibrary lib)
@@ -553,6 +556,7 @@ namespace Unix.Terminal {
 			reset_shell_mode = lib.GetNativeMethodDelegate<Delegates.reset_shell_mode> ("reset_shell_mode");
 			savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
 			resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
+			set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
 		}
 	}
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

+ 6 - 41
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -157,6 +157,11 @@ namespace Terminal.Gui {
 			FakeConsole.Clear ();
 		}
 
+		public override Attribute MakeColor (Color foreground, Color background)
+		{
+			return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background);
+		}
+
 		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		{
 			// Encode the colors into the int value.
@@ -177,47 +182,7 @@ namespace Terminal.Gui {
 			ResizeScreen ();
 			UpdateOffScreen ();
 
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-			Clip = new Rect (0, 0, Cols, Rows);
-
-			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
-			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
-			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
-			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
-			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
-
-			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
-			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
-			Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
-			Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
-			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
-
-			// Focused,
-			//    Selected, Hot: Yellow on Black
-			//    Selected, text: white on black
-			//    Unselected, hot: yellow on cyan
-			//    unselected, text: same as unfocused
-			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
-			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
-			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
-			Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan);
-
-			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
-			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
-			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
-			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
-
-			Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
-			Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
-			Colors.Error.HotFocus = Colors.Error.HotNormal;
-			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
+			CreateColors ();
 
 			//MockConsole.Clear ();
 		}

+ 6 - 40
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1309,6 +1309,11 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public override Attribute MakeColor (Color foreground, Color background)
+		{
+			return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background);
+		}
+
 		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		{
 			// Encode the colors into the int value.
@@ -1337,46 +1342,7 @@ namespace Terminal.Gui {
 
 			StartReportingMouseMoves ();
 
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-
-			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
-			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
-			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
-			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
-			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
-
-			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
-			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Base.HotNormal = MakeColor (ConsoleColor.Cyan, ConsoleColor.DarkBlue);
-			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
-			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
-
-			// Focused,
-			//    Selected, Hot: Yellow on Black
-			//    Selected, text: white on black
-			//    Unselected, hot: yellow on cyan
-			//    unselected, text: same as unfocused
-			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
-			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
-			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
-			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
-
-			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
-			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
-			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
-			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
-
-			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
-			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
-			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
-			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
-			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
+			CreateColors ();
 
 			Clear ();
 		}

+ 6 - 35
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1397,41 +1397,7 @@ namespace Terminal.Gui {
 			ResizeScreen ();
 			UpdateOffScreen ();
 
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-
-			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
-			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
-			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
-			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
-			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
-
-			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
-			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Base.HotNormal = MakeColor (ConsoleColor.Cyan, ConsoleColor.DarkBlue);
-			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
-			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
-
-			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
-			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
-			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
-			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
-
-			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
-			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
-			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
-			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
-
-			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
-			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
-			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
-			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
-			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
+			CreateColors ();
 		}
 
 		public override void ResizeScreen ()
@@ -1537,6 +1503,11 @@ namespace Terminal.Gui {
 			currentAttribute = c;
 		}
 
+		public override Attribute MakeColor (Color foreground, Color background)
+		{
+			return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background);
+		}
+
 		Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		{
 			// Encode the colors into the int value.

+ 19 - 2
Terminal.Gui/Core/Application.cs

@@ -279,9 +279,18 @@ namespace Terminal.Gui {
 
 			public override void Send (SendOrPostCallback d, object state)
 			{
-				mainLoop.Invoke (() => {
+				if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 					d (state);
-				});
+				} else {
+					var wasExecuted = false;
+					mainLoop.Invoke (() => {
+						d (state);
+						wasExecuted = true;
+					});
+					while (!wasExecuted) {
+						Thread.Sleep (15);
+					}
+				}
 			}
 		}
 
@@ -307,6 +316,7 @@ namespace Terminal.Gui {
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
 
 		internal static bool _initialized = false;
+		internal static int _mainThreadId = -1;
 
 		/// <summary>
 		/// Initializes the Terminal.Gui application
@@ -360,6 +370,7 @@ namespace Terminal.Gui {
 			Top = topLevelFactory ();
 			Current = Top;
 			supportedCultures = GetSupportedCultures ();
+			_mainThreadId = Thread.CurrentThread.ManagedThreadId;
 			_initialized = true;
 		}
 
@@ -627,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,
@@ -892,6 +908,7 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			RootKeyEvent = null;
 			Resized = null;
+			_mainThreadId = -1;
 			NotifyNewRunState = null;
 			NotifyStopRunState = null;
 			_initialized = false;

+ 55 - 0
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1338,5 +1338,60 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <returns>The current attribute.</returns>
 		public abstract Attribute GetAttribute ();
+
+		/// <summary>
+		/// Make the <see cref="Colors"/> for the <see cref="ColorScheme"/>.
+		/// </summary>
+		/// <param name="foreground">The foreground color.</param>
+		/// <param name="background">The background color.</param>
+		/// <returns>The attribute for the foreground and background colors.</returns>
+		public abstract Attribute MakeColor (Color foreground, Color background);
+
+		/// <summary>
+		/// Create all <see cref="Colors"/> with the <see cref="ColorScheme"/> for the console driver.
+		/// </summary>
+		/// <param name="hasColors">Flag indicating if colors are supported.</param>
+		public void CreateColors (bool hasColors = true)
+		{
+			Colors.TopLevel = new ColorScheme ();
+			Colors.Base = new ColorScheme ();
+			Colors.Dialog = new ColorScheme ();
+			Colors.Menu = new ColorScheme ();
+			Colors.Error = new ColorScheme ();
+
+			if (!hasColors) {
+				return;
+			}
+
+			Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
+			Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
+			Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);
+			Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan);
+			Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black);
+
+			Colors.Base.Normal = MakeColor (Color.White, Color.Blue);
+			Colors.Base.Focus = MakeColor (Color.Black, Color.Gray);
+			Colors.Base.HotNormal = MakeColor (Color.BrightCyan, Color.Blue);
+			Colors.Base.HotFocus = MakeColor (Color.BrightBlue, Color.Gray);
+			Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue);
+
+			Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray);
+			Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray);
+			Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray);
+			Colors.Dialog.HotFocus = MakeColor (Color.BrightYellow, Color.DarkGray);
+			Colors.Dialog.Disabled = MakeColor (Color.Gray, Color.DarkGray);
+
+			Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray);
+			Colors.Menu.Focus = MakeColor (Color.White, Color.Black);
+			Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray);
+			Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black);
+			Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray);
+
+			Colors.Error.Normal = MakeColor (Color.Red, Color.White);
+			Colors.Error.Focus = MakeColor (Color.Black, Color.BrightRed);
+			Colors.Error.HotNormal = MakeColor (Color.Black, Color.White);
+			Colors.Error.HotFocus = MakeColor (Color.BrightRed, Color.Gray);
+			Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
+		}
 	}
 }

+ 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 ();
 			}

+ 71 - 7
Terminal.Gui/Core/TextFormatter.cs

@@ -356,7 +356,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect)"/> is called.
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool)"/> is called.
 		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
 		/// </summary>
 		/// <remarks>
@@ -417,6 +417,50 @@ namespace Terminal.Gui {
 			return ustring.Make (runes);
 		}
 
+		/// <summary>
+		/// Splits all newlines in the <paramref name="text"/> into a list
+		/// and supports both CRLF and LF, preserving the ending newline.
+		/// </summary>
+		/// <param name="text">The text.</param>
+		/// <returns>A list of text without the newline characters.</returns>
+		public static List<ustring> SplitNewLine (ustring text)
+		{
+			var runes = text.ToRuneList ();
+			var lines = new List<ustring> ();
+			var start = 0;
+			var end = 0;
+
+			for (int i = 0; i < runes.Count; i++) {
+				end = i;
+				switch (runes [i]) {
+				case '\n':
+					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+					i++;
+					start = i;
+					break;
+
+				case '\r':
+					if ((i + 1) < runes.Count && runes [i + 1] == '\n') {
+						lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+						i += 2;
+						start = i;
+					} else {
+						lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+						i++;
+						start = i;
+					}
+					break;
+				}
+			}
+			if (runes.Count > 0 && lines.Count == 0) {
+				lines.Add (ustring.Make (runes));
+			} else if (runes.Count > 0 && start < runes.Count) {
+				lines.Add (ustring.Make (runes.GetRange (start, runes.Count - start)));
+			} else {
+				lines.Add (ustring.Make (""));
+			}
+			return lines;
+		}
 
 		/// <summary>
 		/// Adds trailing whitespace or truncates <paramref name="text"/>
@@ -649,7 +693,7 @@ namespace Terminal.Gui {
 				textCount = words.Sum (arg => arg.RuneCount);
 			}
 			var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
-			var extras = words.Length > 1 ? (width - textCount) % words.Length : 0;
+			var extras = words.Length > 1 ? (width - textCount) % (words.Length - 1) : 0;
 
 			var s = new System.Text.StringBuilder ();
 			for (int w = 0; w < words.Length; w++) {
@@ -659,8 +703,14 @@ namespace Terminal.Gui {
 					for (int i = 0; i < spaces; i++)
 						s.Append (spaceChar);
 				if (extras > 0) {
+					for (int i = 0; i < 1; i++)
+						s.Append (spaceChar);
 					extras--;
 				}
+				if (w + 1 == words.Length - 1) {
+					for (int i = 0; i < extras; i++)
+						s.Append (spaceChar);
+				}
 			}
 			return ustring.Make (s.ToString ());
 		}
@@ -791,6 +841,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>
@@ -1097,7 +1159,8 @@ namespace Terminal.Gui {
 		/// <param name="normalColor">The color to use for all text except the hotkey</param>
 		/// <param name="hotColor">The color to use to draw the hotkey</param>
 		/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
-		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default)
+		/// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
+		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true)
 		{
 			// With this check, we protect against subclasses with overrides of Text (like Button)
 			if (ustring.IsNullOrEmpty (text)) {
@@ -1200,7 +1263,7 @@ namespace Terminal.Gui {
 				var size = isVertical ? bounds.Height : bounds.Width;
 				var current = start;
 				var savedClip = Application.Driver?.Clip;
-				if (Application.Driver != null && containerBounds != default) {
+				if (Application.Driver != null) {
 					Application.Driver.Clip = containerBounds == default
 						? bounds
 						: new Rect (Math.Max (containerBounds.X, bounds.X),
@@ -1210,10 +1273,10 @@ namespace Terminal.Gui {
 				}
 
 				for (var idx = (isVertical ? start - y : start - x); current < start + size; idx++) {
-					if (idx < 0) {
+					if (!fillRemaining && idx < 0) {
 						current++;
 						continue;
-					} else if (idx > runes.Length - 1) {
+					} else if (!fillRemaining && idx > runes.Length - 1) {
 						break;
 					}
 					var rune = (Rune)' ';
@@ -1245,7 +1308,8 @@ namespace Terminal.Gui {
 					} else {
 						current += runeWidth;
 					}
-					if (!isVertical && idx + 1 < runes.Length && current + Rune.ColumnWidth (runes [idx + 1]) > start + size) {
+					var nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length ? Rune.ColumnWidth (runes [idx + 1]) : 0;
+					if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size) {
 						break;
 					}
 				}

+ 52 - 18
Terminal.Gui/Core/View.cs

@@ -271,7 +271,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Configurable keybindings supported by the control
 		/// </summary>
-		private Dictionary<Key, Command> KeyBindings { get; set; } = new Dictionary<Key, Command> ();
+		private Dictionary<Key, Command []> KeyBindings { get; set; } = new Dictionary<Key, Command []> ();
 		private Dictionary<Command, Func<bool?>> CommandImplementations { get; set; } = new Dictionary<Command, Func<bool?>> ();
 
 		/// <summary>
@@ -1716,17 +1716,32 @@ namespace Terminal.Gui {
 		/// <param name="keyEvent">The key event passed.</param>
 		protected bool? InvokeKeybindings (KeyEvent keyEvent)
 		{
+			bool? toReturn = null;
+
 			if (KeyBindings.ContainsKey (keyEvent.Key)) {
-				var command = KeyBindings [keyEvent.Key];
 
-				if (!CommandImplementations.ContainsKey (command)) {
-					throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})");
-				}
+				foreach (var command in KeyBindings [keyEvent.Key]) {
+
+					if (!CommandImplementations.ContainsKey (command)) {
+						throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})");
+					}
+
+					// each command has its own return value
+					var thisReturn = CommandImplementations [command] ();
 
-				return CommandImplementations [command] ();
+					// if we haven't got anything yet, the current command result should be used
+					if (toReturn == null) {
+						toReturn = thisReturn;
+					}
+
+					// if ever see a true then that's what we will return
+					if (thisReturn ?? false) {
+						toReturn = thisReturn.Value;
+					}
+				}
 			}
 
-			return null;
+			return toReturn;
 		}
 
 
@@ -1736,11 +1751,19 @@ namespace Terminal.Gui {
 		/// </para>
 		/// <para>If the key is already bound to a different <see cref="Command"/> it will be
 		/// rebound to this one</para>
+		/// <remarks>Commands are only ever applied to the current <see cref="View"/>(i.e. this feature
+		/// cannot be used to switch focus to another view and perform multiple commands there)</remarks>
 		/// </summary>
 		/// <param name="key"></param>
-		/// <param name="command"></param>
-		public void AddKeyBinding (Key key, Command command)
+		/// <param name="command">The command(s) to run on the <see cref="View"/> when <paramref name="key"/> is pressed.
+		/// When specifying multiple, all commands will be applied in sequence.  The bound <paramref name="key"/> strike
+		/// will be consumed if any took effect.</param>
+		public void AddKeyBinding (Key key, params Command [] command)
 		{
+			if (command.Length == 0) {
+				throw new ArgumentException ("At least one command must be specified", nameof (command));
+			}
+
 			if (KeyBindings.ContainsKey (key)) {
 				KeyBindings [key] = command;
 			} else {
@@ -1756,7 +1779,7 @@ namespace Terminal.Gui {
 		protected void ReplaceKeyBinding (Key fromKey, Key toKey)
 		{
 			if (KeyBindings.ContainsKey (fromKey)) {
-				Command value = KeyBindings [fromKey];
+				var value = KeyBindings [fromKey];
 				KeyBindings.Remove (fromKey);
 				KeyBindings [toKey] = value;
 			}
@@ -1795,9 +1818,9 @@ namespace Terminal.Gui {
 		/// keys bound to the same command and this method will clear all of them.
 		/// </summary>
 		/// <param name="command"></param>
-		public void ClearKeybinding (Command command)
+		public void ClearKeybinding (params Command [] command)
 		{
-			foreach (var kvp in KeyBindings.Where (kvp => kvp.Value == command).ToArray ()) {
+			foreach (var kvp in KeyBindings.Where (kvp => kvp.Value.SequenceEqual (command)).ToArray ()) {
 				KeyBindings.Remove (kvp.Key);
 			}
 		}
@@ -1837,9 +1860,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="command">The command to search.</param>
 		/// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-		public Key GetKeyFromCommand (Command command)
+		public Key GetKeyFromCommand (params Command [] command)
 		{
-			return KeyBindings.First (x => x.Value == command).Key;
+			return KeyBindings.First (x => x.Value.SequenceEqual (command)).Key;
 		}
 
 		/// <inheritdoc/>
@@ -2533,14 +2556,24 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Gets or sets whether a view is cleared if the <see cref="Visible"/> property is <see langword="false"/>.
+		/// </summary>
+		public bool ClearOnVisibleFalse { get; set; } = true;
+
 		/// <inheritdoc/>>
 		public override bool Visible {
 			get => base.Visible;
 			set {
 				if (base.Visible != value) {
 					base.Visible = value;
-					if (!value && HasFocus) {
-						SetHasFocus (false, this);
+					if (!value) {
+						if (HasFocus) {
+							SetHasFocus (false, this);
+						}
+						if (ClearOnVisibleFalse) {
+							Clear ();
+						}
 					}
 					OnVisibleChanged ();
 					SetNeedsDisplay ();
@@ -2971,8 +3004,9 @@ namespace Terminal.Gui {
 		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 		/// </summary>
 		/// <returns><see cref="ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
-		/// or <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/></returns>
-		public Attribute GetNormalColor ()
+		/// or <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
+		/// If it's overridden can return other values.</returns>
+		public virtual Attribute GetNormalColor ()
 		{
 			return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
 		}

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -16,7 +16,7 @@
     <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
     <PackageReference Include="NStack.Core" Version="0.17.1" />
     <InternalsVisibleTo Include="UnitTests" />
   </ItemGroup>

+ 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>

+ 12 - 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);
@@ -521,12 +521,17 @@ namespace Terminal.Gui {
 					top++;
 				} else if (selected < top) {
 					top = selected;
+				} else if (selected < top) {
+					top = selected;
 				}
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 			} else if (selected == 0) {
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
+			} else if (selected >= top + Frame.Height) {
+				top = source.Count - Frame.Height;
+				SetNeedsDisplay ();
 			}
 
 			return true;
@@ -561,6 +566,9 @@ namespace Terminal.Gui {
 				}
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
+			} else if (selected < top) {
+				top = selected;
+				SetNeedsDisplay ();
 			}
 			return true;
 		}
@@ -819,7 +827,7 @@ namespace Terminal.Gui {
 
 		int GetMaxLengthItem ()
 		{
-			if (src?.Count == 0) {
+			if (src == null || src?.Count == 0) {
 				return 0;
 			}
 
@@ -875,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 {

+ 15 - 6
Terminal.Gui/Views/Menu.cs

@@ -384,17 +384,26 @@ namespace Terminal.Gui {
 		internal int current;
 		internal View previousSubFocused;
 
-		internal static Rect MakeFrame (int x, int y, MenuItem [] items)
+		internal static Rect MakeFrame (int x, int y, MenuItem [] items, Menu parent = null)
 		{
 			if (items == null || items.Length == 0) {
 				return new Rect ();
 			}
-			int maxW = items.Max (z => z?.Width) ?? 0;
-
-			return new Rect (x, y, maxW + 2, items.Length + 2);
+			int minX = x;
+			int minY = y;
+			int maxW = (items.Max (z => z?.Width) ?? 0) + 2;
+			int maxH = items.Length + 2;
+			if (parent != null && x + maxW > Driver.Cols) {
+				minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
+			}
+			if (y + maxH > Driver.Rows) {
+				minY = Math.Max (Driver.Rows - maxH, 0);
+			}
+			return new Rect (minX, minY, maxW, maxH);
 		}
 
-		public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
+		public Menu (MenuBar host, int x, int y, MenuBarItem barItems, Menu parent = null)
+			: base (MakeFrame (x, y, barItems.Children, parent))
 		{
 			this.barItems = barItems;
 			this.host = host;
@@ -1232,7 +1241,7 @@ namespace Terminal.Gui {
 				} else {
 					var last = openSubMenu.Count > 0 ? openSubMenu.Last () : openMenu;
 					if (!UseSubMenusSingleFrame) {
-						openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width, last.Frame.Top + 1 + last.current, subMenu);
+						openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width, last.Frame.Top + 1 + last.current, subMenu, last);
 					} else {
 						var first = openSubMenu.Count > 0 ? openSubMenu.First () : openMenu;
 						var mbi = new MenuItem [2 + subMenu.Children.Length];

+ 43 - 15
Terminal.Gui/Views/ScrollBarView.cs

@@ -106,7 +106,7 @@ namespace Terminal.Gui {
 				OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
 				OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
 				OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
-				OtherScrollBarView.showScrollIndicator = true;
+				OtherScrollBarView.ShowScrollIndicator = true;
 			}
 			ShowScrollIndicator = true;
 			contentBottomRightCorner = new View (" ") { Visible = host.Visible };
@@ -116,6 +116,7 @@ namespace Terminal.Gui {
 			contentBottomRightCorner.Width = 1;
 			contentBottomRightCorner.Height = 1;
 			contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
+			ClearOnVisibleFalse = false;
 		}
 
 		private void Host_VisibleChanged ()
@@ -188,11 +189,9 @@ namespace Terminal.Gui {
 		public int Size {
 			get => size;
 			set {
-				if (hosted || (otherScrollBarView != null && otherScrollBarView.hosted)) {
-					size = value + 1;
-				} else {
-					size = value;
-				}
+				size = value;
+				SetRelativeLayout (Bounds);
+				ShowHideScrollBars (false);
 				SetNeedsDisplay ();
 			}
 		}
@@ -220,9 +219,6 @@ namespace Terminal.Gui {
 						position = Math.Max (position + max, 0);
 					}
 					var s = GetBarsize (vertical);
-					if (position + s == size && (hosted || (otherScrollBarView != null && otherScrollBarView.hosted))) {
-						position++;
-					}
 					OnChangedPosition ();
 					SetNeedsDisplay ();
 				}
@@ -327,11 +323,13 @@ namespace Terminal.Gui {
 			ShowHideScrollBars ();
 		}
 
-		void ShowHideScrollBars ()
+		void ShowHideScrollBars (bool redraw = true)
 		{
 			if (!hosted || (hosted && !autoHideScrollBars)) {
 				if (contentBottomRightCorner != null && contentBottomRightCorner.Visible) {
 					contentBottomRightCorner.Visible = false;
+				} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null && otherScrollBarView.contentBottomRightCorner.Visible) {
+					otherScrollBarView.contentBottomRightCorner.Visible = false;
 				}
 				return;
 			}
@@ -350,24 +348,34 @@ namespace Terminal.Gui {
 			if (showBothScrollIndicator) {
 				if (contentBottomRightCorner != null) {
 					contentBottomRightCorner.Visible = true;
+				} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null) {
+					otherScrollBarView.contentBottomRightCorner.Visible = true;
 				}
 			} else if (!showScrollIndicator) {
 				if (contentBottomRightCorner != null) {
 					contentBottomRightCorner.Visible = false;
+				} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null) {
+					otherScrollBarView.contentBottomRightCorner.Visible = false;
 				}
 				if (Application.mouseGrabView != null && Application.mouseGrabView == this) {
 					Application.UngrabMouse ();
 				}
-			} else {
+			} else if (contentBottomRightCorner != null) {
 				contentBottomRightCorner.Visible = false;
+			} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null) {
+				otherScrollBarView.contentBottomRightCorner.Visible = false;
 			}
 			if (Host?.Visible == true && showScrollIndicator && !Visible) {
 				Visible = true;
 			}
-			if (Host?.Visible == true && otherScrollBarView != null && otherScrollBarView.showScrollIndicator
-				&& !otherScrollBarView.Visible) {
+			if (Host?.Visible == true && otherScrollBarView?.showScrollIndicator == true && !otherScrollBarView.Visible) {
 				otherScrollBarView.Visible = true;
 			}
+
+			if (!redraw) {
+				return;
+			}
+
 			if (showScrollIndicator) {
 				Redraw (Bounds);
 			}
@@ -384,13 +392,22 @@ namespace Terminal.Gui {
 				if (scrollBarView.showScrollIndicator) {
 					scrollBarView.ShowScrollIndicator = false;
 				}
+				if (scrollBarView.Visible) {
+					scrollBarView.Visible = false;
+				}
 			} else if (barsize > 0 && barsize == scrollBarView.size && scrollBarView.OtherScrollBarView != null && pending) {
 				if (scrollBarView.showScrollIndicator) {
 					scrollBarView.ShowScrollIndicator = false;
 				}
+				if (scrollBarView.Visible) {
+					scrollBarView.Visible = false;
+				}
 				if (scrollBarView.OtherScrollBarView != null && scrollBarView.showBothScrollIndicator) {
 					scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
 				}
+				if (scrollBarView.OtherScrollBarView.Visible) {
+					scrollBarView.OtherScrollBarView.Visible = false;
+				}
 			} else if (barsize > 0 && barsize == size && scrollBarView.OtherScrollBarView != null && !pending) {
 				pending = true;
 			} else {
@@ -398,10 +415,16 @@ namespace Terminal.Gui {
 					if (!scrollBarView.showBothScrollIndicator) {
 						scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
 					}
+					if (!scrollBarView.OtherScrollBarView.Visible) {
+						scrollBarView.OtherScrollBarView.Visible = true;
+					}
 				}
 				if (!scrollBarView.showScrollIndicator) {
 					scrollBarView.ShowScrollIndicator = true;
 				}
+				if (!scrollBarView.Visible) {
+					scrollBarView.Visible = true;
+				}
 			}
 
 			return pending;
@@ -418,7 +441,7 @@ namespace Terminal.Gui {
 			} else if (showScrollIndicator) {
 				Width = vertical ? 1 : Dim.Width (Host) - 0;
 				Height = vertical ? Dim.Height (Host) - 0 : 1;
-			} else if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) {
+			} else if (otherScrollBarView?.showScrollIndicator == true) {
 				otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 0;
 				otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 0 : 1;
 			}
@@ -432,7 +455,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect region)
 		{
-			if (ColorScheme == null || Size == 0) {
+			if (ColorScheme == null || ((!showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) {
+				if ((!showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
+					ShowHideScrollBars (false);
+				}
 				return;
 			}
 
@@ -578,6 +604,8 @@ namespace Terminal.Gui {
 
 			if (contentBottomRightCorner != null && hosted && showBothScrollIndicator) {
 				contentBottomRightCorner.Redraw (contentBottomRightCorner.Bounds);
+			} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null && otherScrollBarView.hosted && otherScrollBarView.showBothScrollIndicator) {
+				otherScrollBarView.contentBottomRightCorner.Redraw (otherScrollBarView.contentBottomRightCorner.Bounds);
 			}
 		}
 

+ 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;
 		}

+ 9 - 1
Terminal.Gui/Views/TextField.cs

@@ -203,6 +203,8 @@ namespace Terminal.Gui {
 			AddKeyBinding (Key.X | Key.CtrlMask, Command.Cut);
 			AddKeyBinding (Key.V | Key.CtrlMask, Command.Paste);
 			AddKeyBinding (Key.T | Key.CtrlMask, Command.SelectAll);
+
+			AddKeyBinding (Key.R | Key.CtrlMask, Command.DeleteAll);
 			AddKeyBinding (Key.D | Key.CtrlMask | Key.ShiftMask, Command.DeleteAll);
 
 			currentCulture = Thread.CurrentThread.CurrentUICulture;
@@ -412,7 +414,7 @@ namespace Terminal.Gui {
 			var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
 			SetSelectedStartSelectedLength ();
 
-			Driver.SetAttribute (ColorScheme.Focus);
+			Driver.SetAttribute (GetNormalColor ());
 			Move (0, 0);
 
 			int p = first;
@@ -464,6 +466,12 @@ namespace Terminal.Gui {
 			Autocomplete.RenderOverlay (renderAt);
 		}
 
+		/// <inheritdoc/>
+		public override Attribute GetNormalColor ()
+		{
+			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+		}
+
 		Attribute GetReadOnlyColor ()
 		{
 			if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {

+ 123 - 50
Terminal.Gui/Views/TextView.cs

@@ -1176,6 +1176,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action TextChanged;
 
+		/// <summary>
+		/// Invoked with the unwrapped <see cref="CursorPosition"/>.
+		/// </summary>
+		public event Action<Point> UnwrappedCursorPosition;
+
 		/// <summary>
 		/// Provides autocomplete context menu based on suggestions at the current cursor
 		/// position.  Populate <see cref="Autocomplete.AllSuggestions"/> to enable this feature
@@ -1367,6 +1372,8 @@ namespace Terminal.Gui {
 
 			AddKeyBinding (Key.Z | Key.CtrlMask, Command.Undo);
 			AddKeyBinding (Key.R | Key.CtrlMask, Command.Redo);
+
+			AddKeyBinding (Key.G | Key.CtrlMask, Command.DeleteAll);
 			AddKeyBinding (Key.D | Key.CtrlMask | Key.ShiftMask, Command.DeleteAll);
 
 			currentCulture = Thread.CurrentThread.CurrentUICulture;
@@ -1608,12 +1615,7 @@ namespace Terminal.Gui {
 					return ustring.Empty;
 				}
 
-				SetWrapModel ();
-				var sel = GetRegion ();
-				UpdateWrapModel ();
-				Adjust ();
-
-				return sel;
+				return GetSelectedRegion ();
 			}
 		}
 
@@ -1936,7 +1938,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Sets the driver to the default color for the control where no text is being rendered.  Defaults to <see cref="ColorScheme.Normal"/>.
 		/// </summary>
-		protected virtual void ColorNormal ()
+		protected virtual void SetNormalColor ()
 		{
 			Driver.SetAttribute (GetNormalColor ());
 		}
@@ -1948,7 +1950,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="line"></param>
 		/// <param name="idx"></param>
-		protected virtual void ColorNormal (List<Rune> line, int idx)
+		protected virtual void SetNormalColor (List<Rune> line, int idx)
 		{
 			Driver.SetAttribute (GetNormalColor ());
 		}
@@ -1960,9 +1962,27 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="line"></param>
 		/// <param name="idx"></param>
-		protected virtual void ColorSelection (List<Rune> line, int idx)
+		protected virtual void SetSelectionColor (List<Rune> line, int idx)
+		{
+			Driver.SetAttribute (new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground));
+		}
+
+		/// <summary>
+		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
+		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// Defaults to <see cref="ColorScheme.Focus"/>.
+		/// </summary>
+		/// <param name="line"></param>
+		/// <param name="idx"></param>
+		protected virtual void SetReadOnlyColor (List<Rune> line, int idx)
 		{
-			Driver.SetAttribute (ColorScheme.Focus);
+			Attribute attribute;
+			if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
+				attribute = new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
+			} else {
+				attribute = new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
+			}
+			Driver.SetAttribute (attribute);
 		}
 
 		/// <summary>
@@ -1972,7 +1992,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="line"></param>
 		/// <param name="idx"></param>
-		protected virtual void ColorUsed (List<Rune> line, int idx)
+		protected virtual void SetUsedColor (List<Rune> line, int idx)
 		{
 			Driver.SetAttribute (ColorScheme.HotFocus);
 		}
@@ -2022,10 +2042,18 @@ namespace Terminal.Gui {
 		}
 
 		// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
-		void GetEncodedRegionBounds (out long start, out long end)
-		{
-			long selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
-			long point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
+		void GetEncodedRegionBounds (out long start, out long end,
+			int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null)
+		{
+			long selection;
+			long point;
+			if (startRow == null || startCol == null || cRow == null || cCol == null) {
+				selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
+				point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
+			} else {
+				selection = ((long)(uint)startRow << 32) | (uint)startCol;
+				point = ((long)(uint)cRow << 32) | (uint)cCol;
+			}
 			if (selection > point) {
 				start = point;
 				end = selection;
@@ -2047,10 +2075,10 @@ namespace Terminal.Gui {
 		// Returns a ustring with the text in the selected 
 		// region.
 		//
-		ustring GetRegion ()
+		ustring GetRegion (int? sRow = null, int? sCol = null, int? cRow = null, int? cCol = null, TextModel model = null)
 		{
 			long start, end;
-			GetEncodedRegionBounds (out start, out end);
+			GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol);
 			if (start == end) {
 				return ustring.Empty;
 			}
@@ -2058,7 +2086,7 @@ namespace Terminal.Gui {
 			var maxrow = ((int)(end >> 32));
 			int startCol = (int)(start & 0xffffffff);
 			var endCol = (int)(end & 0xffffffff);
-			var line = model.GetLine (startRow);
+			var line = model == null ? this.model.GetLine (startRow) : model.GetLine (startRow);
 
 			if (startRow == maxrow)
 				return StringFromRunes (line.GetRange (startCol, endCol - startCol));
@@ -2066,9 +2094,10 @@ namespace Terminal.Gui {
 			ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
 
 			for (int row = startRow + 1; row < maxrow; row++) {
-				res = res + ustring.Make (Environment.NewLine) + StringFromRunes (model.GetLine (row));
+				res = res + ustring.Make (Environment.NewLine) + StringFromRunes (model == null
+					? this.model.GetLine (row) : model.GetLine (row));
 			}
-			line = model.GetLine (maxrow);
+			line = model == null ? this.model.GetLine (maxrow) : model.GetLine (maxrow);
 			res = res + ustring.Make (Environment.NewLine) + StringFromRunes (line.GetRange (0, endCol));
 			return res;
 		}
@@ -2320,10 +2349,42 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
 		}
 
+		/// <summary>
+		/// Invoke the <see cref="UnwrappedCursorPosition"/> event with the unwrapped <see cref="CursorPosition"/>.
+		/// </summary>
+		public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null)
+		{
+			var row = cRow == null ? currentRow : cRow;
+			var col = cCol == null ? currentColumn : cCol;
+			if (cRow == null && cCol == null && wordWrap) {
+				row = wrapManager.GetModelLineFromWrappedLines (currentRow);
+				col = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn);
+			}
+			UnwrappedCursorPosition?.Invoke (new Point ((int)col, (int)row));
+		}
+
+		ustring GetSelectedRegion ()
+		{
+			var cRow = currentRow;
+			var cCol = currentColumn;
+			var startRow = selectionStartRow;
+			var startCol = selectionStartColumn;
+			var model = this.model;
+			if (wordWrap) {
+				cRow = wrapManager.GetModelLineFromWrappedLines (currentRow);
+				cCol = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn);
+				startRow = wrapManager.GetModelLineFromWrappedLines (selectionStartRow);
+				startCol = wrapManager.GetModelColFromWrappedLines (selectionStartRow, selectionStartColumn);
+				model = wrapManager.Model;
+			}
+			OnUnwrappedCursorPosition (cRow, cCol);
+			return GetRegion (startRow, startCol, cRow, cCol, model);
+		}
+
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			ColorNormal ();
+			SetNormalColor ();
 
 			var offB = OffSetBackground ();
 			int right = Frame.Width + offB.width + RightOffset;
@@ -2339,12 +2400,14 @@ namespace Terminal.Gui {
 					var rune = idxCol >= lineRuneCount ? ' ' : line [idxCol];
 					var cols = Rune.ColumnWidth (rune);
 					if (idxCol < line.Count && selecting && PointInSelection (idxCol, idxRow)) {
-						ColorSelection (line, idxCol);
+						SetSelectionColor (line, idxCol);
 					} else if (idxCol == currentColumn && idxRow == currentRow && !selecting && !Used
 						&& HasFocus && idxCol < lineRuneCount) {
-						ColorUsed (line, idxCol);
+						SetSelectionColor (line, idxCol);
+					} else if (ReadOnly) {
+						SetReadOnlyColor (line, idxCol);
 					} else {
-						ColorNormal (line, idxCol);
+						SetNormalColor (line, idxCol);
 					}
 
 					if (rune == '\t') {
@@ -2368,13 +2431,13 @@ namespace Terminal.Gui {
 					}
 				}
 				if (col < right) {
-					ColorNormal ();
+					SetNormalColor ();
 					ClearRegion (col, row, right, row + 1);
 				}
 				row++;
 			}
 			if (row < bottom) {
-				ColorNormal ();
+				SetNormalColor ();
 				ClearRegion (bounds.Left, row, right, bottom);
 			}
 
@@ -2395,6 +2458,12 @@ namespace Terminal.Gui {
 			Autocomplete.RenderOverlay (renderAt);
 		}
 
+		/// <inheritdoc/>
+		public override Attribute GetNormalColor ()
+		{
+			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+		}
+
 		///<inheritdoc/>
 		public override bool CanFocus {
 			get => base.CanFocus;
@@ -2617,6 +2686,8 @@ namespace Terminal.Gui {
 			} else {
 				PositionCursor ();
 			}
+
+			OnUnwrappedCursorPosition ();
 		}
 
 		(int width, int height) OffSetBackground ()
@@ -3135,14 +3206,14 @@ namespace Terminal.Gui {
 			if (newPos.HasValue && currentRow == newPos.Value.row) {
 				var restCount = currentColumn - newPos.Value.col;
 				currentLine.RemoveRange (newPos.Value.col, restCount);
-				if (wordWrap && wrapManager.RemoveRange (currentRow, newPos.Value.col, restCount)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 				currentColumn = newPos.Value.col;
 			} else if (newPos.HasValue) {
 				var restCount = currentLine.Count - currentColumn;
 				currentLine.RemoveRange (currentColumn, restCount);
-				if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 				currentColumn = newPos.Value.col;
@@ -3192,7 +3263,7 @@ namespace Terminal.Gui {
 				restCount = currentLine.Count - currentColumn;
 				currentLine.RemoveRange (currentColumn, restCount);
 			}
-			if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
+			if (wordWrap) {
 				wrapNeeded = true;
 			}
 
@@ -3579,16 +3650,24 @@ namespace Terminal.Gui {
 			if (selecting) {
 				ClearSelectedRegion ();
 			}
-			if (Used) {
-				Insert ((uint)kb.Key);
-				currentColumn++;
-				if (currentColumn >= leftColumn + Frame.Width) {
-					leftColumn++;
-					SetNeedsDisplay ();
-				}
+			if (kb.Key == Key.Enter) {
+				model.AddLine (currentRow + 1, new List<Rune> ());
+				currentRow++;
+				currentColumn = 0;
+			} else if ((uint)kb.Key == 13) {
+				currentColumn = 0;
 			} else {
-				Insert ((uint)kb.Key);
-				currentColumn++;
+				if (Used) {
+					Insert ((uint)kb.Key);
+					currentColumn++;
+					if (currentColumn >= leftColumn + Frame.Width) {
+						leftColumn++;
+						SetNeedsDisplay ();
+					}
+				} else {
+					Insert ((uint)kb.Key);
+					currentColumn++;
+				}
 			}
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
@@ -3674,7 +3753,7 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
-				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 				if (wrapNeeded) {
@@ -3691,7 +3770,7 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
-				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 
@@ -3719,7 +3798,7 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
 
 				currentLine.RemoveAt (currentColumn - 1);
-				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 				currentColumn--;
@@ -3751,8 +3830,7 @@ namespace Terminal.Gui {
 				var prevCount = prevRow.Count;
 				model.GetLine (prowIdx).AddRange (GetCurrentLine ());
 				model.RemoveLine (currentRow);
-				bool lineRemoved = false;
-				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out lineRemoved, false)) {
+				if (wordWrap) {
 					wrapNeeded = true;
 				}
 				currentRow--;
@@ -3760,11 +3838,7 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { GetCurrentLine () }, new Point (currentColumn, prowIdx),
 					HistoryText.LineStatus.Replaced);
 
-				if (wrapNeeded && !lineRemoved) {
-					currentColumn = Math.Max (prevCount - 1, 0);
-				} else {
-					currentColumn = prevCount;
-				}
+				currentColumn = prevCount;
 				SetNeedsDisplay ();
 			}
 
@@ -3871,6 +3945,7 @@ namespace Terminal.Gui {
 		{
 			shiftSelecting = false;
 			selecting = false;
+			isButtonShift = false;
 		}
 
 		void ClearSelectedRegion ()
@@ -4185,8 +4260,6 @@ namespace Terminal.Gui {
 			if (ev.Flags == MouseFlags.Button1Clicked) {
 				if (shiftSelecting && !isButtonShift) {
 					StopSelecting ();
-				} else if (!shiftSelecting && isButtonShift) {
-					isButtonShift = false;
 				}
 				ProcessMouseClick (ev, out _);
 				PositionCursor ();

+ 1 - 1
Terminal.Gui/Views/TreeView.cs

@@ -907,7 +907,7 @@ namespace Terminal.Gui {
 		{
 			var map = BuildLineMap ();
 			ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
-			SelectedObject = map.Last ().Model;
+			SelectedObject = map.LastOrDefault ()?.Model;
 
 			SetNeedsDisplay ();
 		}

+ 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;

+ 0 - 5
Terminal.Gui/Windows/Wizard.cs

@@ -203,11 +203,6 @@ namespace Terminal.Gui {
 
 				base.Add (contentView);
 
-				helpTextView.ColorScheme = new ColorScheme () {  
-					Normal = new Attribute(Color.Gray, Color.DarkGray),
-					Focus = new Attribute(Color.DarkGray, Color.Gray),
-					HotFocus = new Attribute(Color.White, Color.DarkGray)
-				};
 				helpTextView.ReadOnly = true;
 				helpTextView.WordWrap = true;
 				base.Add (helpTextView);

+ 9 - 2
UICatalog/KeyBindingsDialog.cs

@@ -132,7 +132,7 @@ namespace UICatalog {
 				Width = Dim.Percent (50),
 				Height = Dim.Percent (100) - 1,
 			};
-			commandsListView.SelectedItemChanged += CommandsListView_SelectedItemChanged;
+
 			Add (commandsListView);
 
 			keyLabel = new Label () {
@@ -143,7 +143,7 @@ namespace UICatalog {
 			};
 			Add (keyLabel);
 
-			var btnChange = new Button ("Change") {
+			var btnChange = new Button ("Ch_ange") {
 				X = Pos.Percent (50),
 				Y = 1,
 			};
@@ -160,6 +160,13 @@ namespace UICatalog {
 			var cancel = new Button ("Cancel");
 			cancel.Clicked += ()=>Application.RequestStop();
 			AddButton (cancel);
+
+			// Register event handler as the last thing in constructor to prevent early calls
+			// before it is even shown (e.g. OnEnter)
+			commandsListView.SelectedItemChanged += CommandsListView_SelectedItemChanged;
+
+			// Setup to show first ListView entry
+			SetTextBoxToShowBinding (commands.First());
 		}
 
 		private void RemapKey ()

+ 0 - 1
UICatalog/Scenarios/AutoSizeAndDirectionText.cs

@@ -33,7 +33,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Center (),
 				Width = 20,
 				Height = 5,
-				ColorScheme = color,
 				Text = text
 			};
 

+ 0 - 3
UICatalog/Scenarios/BordersComparisons.cs

@@ -46,7 +46,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			var tf2 = new TextField ("1234567890") {
@@ -86,7 +85,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			var tf4 = new TextField ("1234567890") {
@@ -123,7 +121,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			var tf6 = new TextField ("1234567890") {

+ 0 - 1
UICatalog/Scenarios/BordersOnFrameView.cs

@@ -56,7 +56,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			smartView.Add (tf1, button, label, tf2, tv);

+ 0 - 1
UICatalog/Scenarios/BordersOnToplevel.cs

@@ -56,7 +56,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			smartView.Add (tf1, button, label, tf2, tv);

+ 0 - 1
UICatalog/Scenarios/BordersOnWindow.cs

@@ -56,7 +56,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (2),
 				Width = 10,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Dialog,
 				Text = "1234567890"
 			};
 			smartView.Add (tf1, button, label, tf2, tv);

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

+ 0 - 1
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -681,7 +681,6 @@ namespace UICatalog.Scenarios {
 				Add (_lblAction);
 
 				_txtAction = new TextView () {
-					ColorScheme = Colors.Dialog,
 					X = Pos.Left (_txtTitle),
 					Y = Pos.Top (_lblAction),
 					Width = Dim.Fill (),

+ 0 - 1
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -383,7 +383,6 @@ namespace UICatalog.Scenarios {
 				Add (_lblAction);
 
 				_txtAction = new TextView () {
-					ColorScheme = Colors.Dialog,
 					X = Pos.Left (_txtTitle),
 					Y = Pos.Top (_lblAction),
 					Width = Dim.Fill (),

+ 34 - 27
UICatalog/Scenarios/Editor.cs

@@ -26,11 +26,11 @@ namespace UICatalog.Scenarios {
 		private string _textToReplace;
 		private bool _matchCase;
 		private bool _matchWholeWord;
-		private Window winDialog;
+		private Window _winDialog;
 		private TabView _tabView;
-		private MenuItem miForceMinimumPosToZero;
-		private bool forceMinimumPosToZero = true;
-		private readonly List<CultureInfo> cultureInfos = Application.SupportedCultures;
+		private MenuItem _miForceMinimumPosToZero;
+		private bool _forceMinimumPosToZero = true;
+		private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
@@ -60,6 +60,12 @@ namespace UICatalog.Scenarios {
 
 			CreateDemoFile (_fileName);
 
+			var siCursorPosition = new StatusItem (Key.Null, "", null);
+
+			_textView.UnwrappedCursorPosition += (e) => {
+				siCursorPosition.Title = $"Ln {e.Y + 1}, Col {e.X + 1}";
+			};
+
 			LoadFile ();
 
 			Win.Add (_textView);
@@ -103,16 +109,17 @@ namespace UICatalog.Scenarios {
 					CreateVisibleChecked ()
 				}),
 				new MenuBarItem ("Conte_xtMenu", new MenuItem [] {
-					miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => {
-						miForceMinimumPosToZero.Checked = forceMinimumPosToZero = !forceMinimumPosToZero;
-						_textView.ContextMenu.ForceMinimumPosToZero = forceMinimumPosToZero;
-					}) { CheckType = MenuItemCheckStyle.Checked, Checked = forceMinimumPosToZero },
+					_miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => {
+						_miForceMinimumPosToZero.Checked = _forceMinimumPosToZero = !_forceMinimumPosToZero;
+						_textView.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+					}) { CheckType = MenuItemCheckStyle.Checked, Checked = _forceMinimumPosToZero },
 					new MenuBarItem ("_Languages", GetSupportedCultures ())
 				})
 			});
 			Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
+				siCursorPosition,
 				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
 				new StatusItem(Key.F3, "~F3~ Save", () => Save()),
 				new StatusItem(Key.F4, "~F4~ Save As", () => SaveAs()),
@@ -168,20 +175,20 @@ namespace UICatalog.Scenarios {
 
 			Win.KeyPress += (e) => {
 				var keys = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-				if (winDialog != null && (e.KeyEvent.Key == Key.Esc
+				if (_winDialog != null && (e.KeyEvent.Key == Key.Esc
 					|| e.KeyEvent.Key == (Key.Q | Key.CtrlMask))) {
 					DisposeWinDialog ();
 				} else if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
 					Quit ();
 					e.Handled = true;
-				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask)) {
+				} else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (0);
 					} else {
 						_tabView.SwitchTabBy (1);
 					}
 					e.Handled = true;
-				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
+				} else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (0)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1);
 					} else {
@@ -196,9 +203,9 @@ namespace UICatalog.Scenarios {
 
 		private void DisposeWinDialog ()
 		{
-			winDialog.Dispose ();
-			Win.Remove (winDialog);
-			winDialog = null;
+			_winDialog.Dispose ();
+			Win.Remove (_winDialog);
+			_winDialog = null;
 		}
 
 		public override void Setup ()
@@ -276,7 +283,7 @@ namespace UICatalog.Scenarios {
 				Find ();
 				return;
 			} else if (replace && (string.IsNullOrEmpty (_textToFind)
-				|| (winDialog == null && string.IsNullOrEmpty (_textToReplace)))) {
+				|| (_winDialog == null && string.IsNullOrEmpty (_textToReplace)))) {
 				Replace ();
 				return;
 			}
@@ -323,7 +330,7 @@ namespace UICatalog.Scenarios {
 
 		private void ReplaceAll ()
 		{
-			if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && winDialog == null)) {
+			if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _winDialog == null)) {
 				Replace ();
 				return;
 			}
@@ -468,7 +475,7 @@ namespace UICatalog.Scenarios {
 			List<MenuItem> supportedCultures = new List<MenuItem> ();
 			var index = -1;
 
-			foreach (var c in cultureInfos) {
+			foreach (var c in _cultureInfos) {
 				var culture = new MenuItem {
 					CheckType = MenuItemCheckStyle.Checked
 				};
@@ -714,17 +721,17 @@ namespace UICatalog.Scenarios {
 
 		private void CreateFindReplace (bool isFind = true)
 		{
-			if (winDialog != null) {
-				winDialog.SetFocus ();
+			if (_winDialog != null) {
+				_winDialog.SetFocus ();
 				return;
 			}
 
-			winDialog = new Window (isFind ? "Find" : "Replace") {
+			_winDialog = new Window (isFind ? "Find" : "Replace") {
 				X = Win.Bounds.Width / 2 - 30,
 				Y = Win.Bounds.Height / 2 - 10,
 				ColorScheme = Colors.TopLevel
 			};
-			winDialog.Border.Effect3D = true;
+			_winDialog.Border.Effect3D = true;
 
 			_tabView = new TabView () {
 				X = 0,
@@ -737,15 +744,15 @@ namespace UICatalog.Scenarios {
 			var replace = ReplaceTab ();
 			_tabView.AddTab (new TabView.Tab ("Replace", replace), !isFind);
 			_tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
-			winDialog.Add (_tabView);
+			_winDialog.Add (_tabView);
 
-			Win.Add (winDialog);
+			Win.Add (_winDialog);
 
-			winDialog.Width = replace.Width + 4;
-			winDialog.Height = replace.Height + 4;
+			_winDialog.Width = replace.Width + 4;
+			_winDialog.Height = replace.Height + 4;
 
-			winDialog.SuperView.BringSubviewToFront (winDialog);
-			winDialog.SetFocus ();
+			_winDialog.SuperView.BringSubviewToFront (_winDialog);
+			_winDialog.SetFocus ();
 		}
 
 		private void SetFindText ()

+ 11 - 18
UICatalog/Scenarios/GraphViewExample.cs

@@ -101,7 +101,8 @@ namespace UICatalog.Scenarios {
 
 			about.Text = "Housing Expenditures by income thirds 1996-2003";
 
-			var black = Application.Driver.MakeAttribute (graphView.ColorScheme.Normal.Foreground, Color.Black);
+			var fore = graphView.ColorScheme.Normal.Foreground == Color.Black ? Color.White : graphView.ColorScheme.Normal.Foreground;
+			var black = Application.Driver.MakeAttribute (fore, Color.Black);
 			var cyan = Application.Driver.MakeAttribute (Color.BrightCyan, Color.Black);
 			var magenta = Application.Driver.MakeAttribute (Color.BrightMagenta, Color.Black);
 			var red = Application.Driver.MakeAttribute (Color.BrightRed, Color.Black);
@@ -138,7 +139,7 @@ namespace UICatalog.Scenarios {
 
 			graphView.AxisY.Minimum = 0;
 
-			var legend = new LegendAnnotation (new Rect (graphView.Bounds.Width - 20,0, 20, 5));
+			var legend = new LegendAnnotation (new Rect (graphView.Bounds.Width - 20, 0, 20, 5));
 			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), "Lower Third");
 			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), "Middle Third");
 			legend.AddEntry (new GraphCellToRender (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), "Upper Third");
@@ -540,26 +541,19 @@ namespace UICatalog.Scenarios {
 				var driver = Application.Driver;
 
 				int x = start.X;
-				for(int y = end.Y; y <= start.Y; y++) {
+				for (int y = end.Y; y <= start.Y; y++) {
 
 					var height = graph.ScreenToGraphSpace (x, y).Y;
 
 					if (height >= 85) {
-						driver.SetAttribute(red);
-					}
-					else
-					if (height >= 66) {
+						driver.SetAttribute (red);
+					} else if (height >= 66) {
 						driver.SetAttribute (brightred);
-					} 
-					else
-					if (height >= 45) {
+					} else if (height >= 45) {
 						driver.SetAttribute (brightyellow);
-					} 
-					else
-					if (height >= 25) {
+					} else if (height >= 25) {
 						driver.SetAttribute (brightgreen);
-					}
-					else{
+					} else {
 						driver.SetAttribute (green);
 					}
 
@@ -683,9 +677,8 @@ namespace UICatalog.Scenarios {
 		private void Margin (bool left, bool increase)
 		{
 			if (left) {
-				graphView.MarginLeft = (uint)Math.Max(0,graphView.MarginLeft + (increase ? 1 : -1));
-			}
-			else {
+				graphView.MarginLeft = (uint)Math.Max (0, graphView.MarginLeft + (increase ? 1 : -1));
+			} else {
 				graphView.MarginBottom = (uint)Math.Max (0, graphView.MarginBottom + (increase ? 1 : -1));
 			}
 

+ 0 - 1
UICatalog/Scenarios/MessageBoxes.cs

@@ -90,7 +90,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Top (label),
 				Width = Dim.Fill (),
 				Height = 5,
-				ColorScheme = Colors.Dialog,
 			};
 			frame.Add (messageEdit);
 

+ 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;
 			}
 		}
 	}

+ 59 - 63
UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -38,7 +38,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (1),
 			};
 
-			textView.Init();
+			textView.Init ();
 
 			textView.Text = "SELECT TOP 100 * \nfrom\n MyDb.dbo.Biochemistry;";
 
@@ -63,49 +63,49 @@ namespace UICatalog.Scenarios {
 			Application.RequestStop ();
 		}
 
-		private class SqlTextView : TextView{
+		private class SqlTextView : TextView {
 
-			private HashSet<string> keywords = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
+			private HashSet<string> keywords = new HashSet<string> (StringComparer.CurrentCultureIgnoreCase);
 			private Attribute blue;
 			private Attribute white;
 			private Attribute magenta;
 
 
-		public void Init()
+			public void Init ()
 			{
-				keywords.Add("select");
-				keywords.Add("distinct");
-				keywords.Add("top");
-				keywords.Add("from");
-				keywords.Add("create");
-				keywords.Add("CIPHER");
-				keywords.Add("CLASS_ORIGIN");
-				keywords.Add("CLIENT");
-				keywords.Add("CLOSE");
-				keywords.Add("COALESCE");
-				keywords.Add("CODE");
-				keywords.Add("COLUMNS");
-				keywords.Add("COLUMN_FORMAT");
-				keywords.Add("COLUMN_NAME");
-				keywords.Add("COMMENT");
-				keywords.Add("COMMIT");
-				keywords.Add("COMPACT");
-				keywords.Add("COMPLETION");
-				keywords.Add("COMPRESSED");
-				keywords.Add("COMPRESSION");
-				keywords.Add("CONCURRENT");
-				keywords.Add("CONNECT");
-				keywords.Add("CONNECTION");
-				keywords.Add("CONSISTENT");
-				keywords.Add("CONSTRAINT_CATALOG");
-				keywords.Add("CONSTRAINT_SCHEMA");
-				keywords.Add("CONSTRAINT_NAME");
-				keywords.Add("CONTAINS");
-				keywords.Add("CONTEXT");
-				keywords.Add("CONTRIBUTORS");
-				keywords.Add("COPY");
-				keywords.Add("CPU");
-				keywords.Add("CURSOR_NAME");
+				keywords.Add ("select");
+				keywords.Add ("distinct");
+				keywords.Add ("top");
+				keywords.Add ("from");
+				keywords.Add ("create");
+				keywords.Add ("CIPHER");
+				keywords.Add ("CLASS_ORIGIN");
+				keywords.Add ("CLIENT");
+				keywords.Add ("CLOSE");
+				keywords.Add ("COALESCE");
+				keywords.Add ("CODE");
+				keywords.Add ("COLUMNS");
+				keywords.Add ("COLUMN_FORMAT");
+				keywords.Add ("COLUMN_NAME");
+				keywords.Add ("COMMENT");
+				keywords.Add ("COMMIT");
+				keywords.Add ("COMPACT");
+				keywords.Add ("COMPLETION");
+				keywords.Add ("COMPRESSED");
+				keywords.Add ("COMPRESSION");
+				keywords.Add ("CONCURRENT");
+				keywords.Add ("CONNECT");
+				keywords.Add ("CONNECTION");
+				keywords.Add ("CONSISTENT");
+				keywords.Add ("CONSTRAINT_CATALOG");
+				keywords.Add ("CONSTRAINT_SCHEMA");
+				keywords.Add ("CONSTRAINT_NAME");
+				keywords.Add ("CONTAINS");
+				keywords.Add ("CONTEXT");
+				keywords.Add ("CONTRIBUTORS");
+				keywords.Add ("COPY");
+				keywords.Add ("CPU");
+				keywords.Add ("CURSOR_NAME");
 				keywords.Add ("primary");
 				keywords.Add ("key");
 				keywords.Add ("insert");
@@ -138,29 +138,26 @@ namespace UICatalog.Scenarios {
 				keywords.Add ("union");
 				keywords.Add ("exists");
 
-				Autocomplete.AllSuggestions = keywords.ToList();
+				Autocomplete.AllSuggestions = keywords.ToList ();
 
 				magenta = Driver.MakeAttribute (Color.Magenta, Color.Black);
 				blue = Driver.MakeAttribute (Color.Cyan, Color.Black);
 				white = Driver.MakeAttribute (Color.White, Color.Black);
 			}
 
-			protected override void ColorNormal ()
+			protected override void SetNormalColor ()
 			{
 				Driver.SetAttribute (white);
 			}
 
-			protected override void ColorNormal (List<System.Rune> line, int idx)
+			protected override void SetNormalColor (List<System.Rune> line, int idx)
 			{
-				if(IsInStringLiteral(line,idx)) {
+				if (IsInStringLiteral (line, idx)) {
 					Driver.SetAttribute (magenta);
-				}
-				else
-				if(IsKeyword(line,idx))
-				{
+				} else
+				if (IsKeyword (line, idx)) {
 					Driver.SetAttribute (blue);
-				}
-				else{
+				} else {
 					Driver.SetAttribute (white);
 				}
 			}
@@ -168,9 +165,9 @@ namespace UICatalog.Scenarios {
 			private bool IsInStringLiteral (List<System.Rune> line, int idx)
 			{
 				string strLine = new string (line.Select (r => (char)r).ToArray ());
-				
-				foreach(Match m in Regex.Matches(strLine, "'[^']*'")) {
-					if(idx >= m.Index && idx < m.Index+m.Length) {
+
+				foreach (Match m in Regex.Matches (strLine, "'[^']*'")) {
+					if (idx >= m.Index && idx < m.Index + m.Length) {
 						return true;
 					}
 				}
@@ -178,37 +175,36 @@ namespace UICatalog.Scenarios {
 				return false;
 			}
 
-			private bool IsKeyword(List<System.Rune> line, int idx)
+			private bool IsKeyword (List<System.Rune> line, int idx)
 			{
-				var word = IdxToWord(line,idx);
-				
-				if(string.IsNullOrWhiteSpace(word)){
+				var word = IdxToWord (line, idx);
+
+				if (string.IsNullOrWhiteSpace (word)) {
 					return false;
 				}
 
-				return keywords.Contains(word,StringComparer.CurrentCultureIgnoreCase);
+				return keywords.Contains (word, StringComparer.CurrentCultureIgnoreCase);
 			}
 
-			private string IdxToWord(List<System.Rune> line, int idx)
+			private string IdxToWord (List<System.Rune> line, int idx)
 			{
-				var words = Regex.Split(
-					new string(line.Select(r=>(char)r).ToArray()),
+				var words = Regex.Split (
+					new string (line.Select (r => (char)r).ToArray ()),
 					"\\b");
 
 
 				int count = 0;
 				string current = null;
 
-				foreach(var word in words)
-				{
+				foreach (var word in words) {
 					current = word;
-					count+= word.Length;
-					if(count > idx){
+					count += word.Length;
+					if (count > idx) {
 						break;
 					}
 				}
 
-				return current?.Trim();
+				return current?.Trim ();
 			}
 		}
 	}

+ 0 - 1
UICatalog/Scenarios/Text.cs

@@ -49,7 +49,6 @@ namespace UICatalog.Scenarios {
 				Y = 3,
 				Width = Dim.Percent (50),
 				Height = Dim.Percent (30),
-				ColorScheme = Colors.Dialog
 			};
 			textView.Text = s;
 			textView.DrawContent += TextView_DrawContent;

+ 1 - 1
UICatalog/Scenarios/TextAlignmentsAndDirection.cs

@@ -105,7 +105,7 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Bottom (container) + 1,
 				Width = Dim.Fill (10),
 				Height = Dim.Fill (1),
-				ColorScheme = color2,
+				ColorScheme = Colors.TopLevel,
 				Text = txt
 			};
 

+ 0 - 6
UICatalog/Scenarios/TextViewAutocompletePopup.cs

@@ -24,7 +24,6 @@ namespace UICatalog.Scenarios {
 		{
 			Win.Title = GetName ();
 			var width = 20;
-			var colorScheme = Colors.Dialog;
 			var text = " jamp jemp jimp jomp jump";
 
 			var menu = new MenuBar (new MenuBarItem [] {
@@ -39,7 +38,6 @@ namespace UICatalog.Scenarios {
 			textViewTopLeft = new TextView () {
 				Width = width,
 				Height = height,
-				ColorScheme = colorScheme,
 				Text = text
 			};
 			textViewTopLeft.DrawContent += TextViewTopLeft_DrawContent;
@@ -49,7 +47,6 @@ namespace UICatalog.Scenarios {
 				X = Pos.AnchorEnd (width),
 				Width = width,
 				Height = height,
-				ColorScheme = colorScheme,
 				Text = text
 			};
 			textViewTopRight.DrawContent += TextViewTopRight_DrawContent;
@@ -59,7 +56,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (height),
 				Width = width,
 				Height = height,
-				ColorScheme = colorScheme,
 				Text = text
 			};
 			textViewBottomLeft.DrawContent += TextViewBottomLeft_DrawContent;
@@ -70,7 +66,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (height),
 				Width = width,
 				Height = height,
-				ColorScheme = colorScheme,
 				Text = text
 			};
 			textViewBottomRight.DrawContent += TextViewBottomRight_DrawContent;
@@ -81,7 +76,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Center (),
 				Width = width,
 				Height = height,
-				ColorScheme = colorScheme,
 				Text = text
 			};
 			textViewCentered.DrawContent += TextViewCentered_DrawContent;

+ 1 - 0
UICatalog/Scenarios/Wizards.cs

@@ -217,6 +217,7 @@ namespace UICatalog.Scenarios {
 						Height = Dim.Fill (1),
 						WordWrap = true,
 						AllowsTab = false,
+						ColorScheme = Colors.Base
 					};
 					var help = "This is helpful.";
 					fourthStep.Add (someText);

+ 157 - 2
UnitTests/ApplicationTests.cs

@@ -84,6 +84,7 @@ namespace Terminal.Gui.Core {
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (SynchronizationContext.Current);
 		}
 
 		void Shutdown ()
@@ -1300,7 +1301,7 @@ namespace Terminal.Gui.Core {
 					var myi = i;
 
 					Task.Run (() => {
-						Task.Delay (100).Wait ();
+						Thread.Sleep (100);
 
 						// each thread registers lots of 1s timeouts
 						for (int j = 0; j < numberOfTimeoutsPerThread; j++) {
@@ -1317,7 +1318,7 @@ namespace Terminal.Gui.Core {
 						if (myi == 0) {
 
 							// let the timeouts run for a bit
-							Task.Delay (5000).Wait ();
+							Thread.Sleep (5000);
 
 							// then tell the application to quit
 							Application.MainLoop.Invoke (() => Application.RequestStop ());
@@ -1335,5 +1336,159 @@ namespace Terminal.Gui.Core {
 				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
 			}
 		}
+
+		[Fact]
+		public void SynchronizationContext_Post ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// non blocking
+				context.Post (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.False (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_Send ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// blocking
+				context.Send (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.True (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_CreateCopy ()
+		{
+			Init ();
+
+			var context = SynchronizationContext.Current;
+			Assert.NotNull (context);
+
+			var contextCopy = context.CreateCopy ();
+			Assert.NotNull (contextCopy);
+
+			Assert.NotEqual (context, contextCopy);
+
+			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 ();
+		}
 	}
 }

+ 38 - 18
UnitTests/CheckboxTests.cs

@@ -314,10 +314,18 @@ namespace Terminal.Gui.Views {
 		[Fact, AutoInitShutdown]
 		public void TextAlignment_Justified ()
 		{
-			var checkBox = new CheckBox () {
+			var checkBox1 = new CheckBox () {
 				X = 1,
 				Y = Pos.Center (),
-				Text = "Check this out 你",
+				Text = "Check first out 你",
+				TextAlignment = TextAlignment.Justified,
+				AutoSize = false,
+				Width = 25
+			};
+			var checkBox2 = new CheckBox () {
+				X = 1,
+				Y = Pos.Bottom (checkBox1),
+				Text = "Check second out 你",
 				TextAlignment = TextAlignment.Justified,
 				AutoSize = false,
 				Width = 25
@@ -327,44 +335,56 @@ namespace Terminal.Gui.Views {
 				Height = Dim.Fill (),
 				Title = "Test Demo 你"
 			};
-			win.Add (checkBox);
+			win.Add (checkBox1, checkBox2);
 			Application.Top.Add (win);
 
 			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 6);
+
+			Assert.Equal (TextAlignment.Justified, checkBox1.TextAlignment);
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame);
+			Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size);
+			Assert.Equal ("Check first out 你", checkBox1.Text);
+			Assert.Equal ("╴ Check first out 你", checkBox1.TextFormatter.Text);
+			Assert.False (checkBox1.AutoSize);
+			Assert.Equal (TextAlignment.Justified, checkBox2.TextAlignment);
+			Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame);
+			Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size);
+			Assert.Equal ("Check second out 你", checkBox2.Text);
+			Assert.Equal ("╴ Check second out 你", checkBox2.TextFormatter.Text);
+			Assert.False (checkBox2.AutoSize);
 
-			Assert.Equal (TextAlignment.Justified, checkBox.TextAlignment);
-			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
-			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
-			Assert.Equal ("Check this out 你", checkBox.Text);
-			Assert.Equal ("╴ Check this out 你", checkBox.TextFormatter.Text);
-			Assert.False (checkBox.AutoSize);
 
 			var expected = @"
 ┌ Test Demo 你 ──────────────┐
 │                            │
-│ ╴  Check  this  out  你    │
+│ ╴   Check  first  out  你  │
+│ ╴  Check  second  out  你  │
 │                            │
 └────────────────────────────┘
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 30, 5), pos);
-
-			checkBox.Checked = true;
-			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
-			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Assert.Equal (new Rect (0, 0, 30, 6), pos);
+
+			checkBox1.Checked = true;
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame);
+			Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size);
+			checkBox2.Checked = true;
+			Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame);
+			Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size);
 			Application.Refresh ();
 			expected = @"
 ┌ Test Demo 你 ──────────────┐
 │                            │
-│ √  Check  this  out  你    │
+│ √   Check  first  out  你  │
+│ √  Check  second  out  你  │
 │                            │
 └────────────────────────────┘
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+			Assert.Equal (new Rect (0, 0, 30, 6), pos);
 		}
 
 		[Fact, AutoInitShutdown]

+ 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);
+		}
 	}
 }

+ 239 - 30
UnitTests/ContextMenuTests.cs

@@ -423,7 +423,7 @@ namespace Terminal.Gui.Core {
 			cm.Show ();
 			Assert.Equal (new Point (0, 0), cm.Position);
 			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (80, 4);
+			((FakeDriver)Application.Driver).SetBufferSize (80, 3);
 
 			var expected = @"
 ┌──────┐
@@ -432,7 +432,7 @@ namespace Terminal.Gui.Core {
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 1, 8, 3), pos);
+			Assert.Equal (new Rect (0, 0, 8, 3), pos);
 
 			cm.Hide ();
 			Assert.Equal (new Point (0, 0), cm.Position);
@@ -592,27 +592,27 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit                         
-                                      
-                                      
-  Label: TextField                    
-         ┌───────────────────────────
-         │ Select All         Ctrl+T │
-         │ Delete All   Ctrl+Shift+D
-         │ Copy               Ctrl+C │
-         │ Cut                Ctrl+X │
-         │ Paste              Ctrl+V │
-         │ Undo               Ctrl+Z │
-         │ Redo               Ctrl+Y │
-         └───────────────────────────
-                                      
-                                      
-                                      
- F1 Help │ ^Q Quit                    
+  File   Edit                   
+                                
+                                
+  Label: TextField              
+         ┌─────────────────────┐
+         │ Select All   Ctrl+T │
+         │ Delete All   Ctrl+R
+         │ Copy         Ctrl+C │
+         │ Cut          Ctrl+X │
+         │ Paste        Ctrl+V │
+         │ Undo         Ctrl+Z │
+         │ Redo         Ctrl+Y │
+         └─────────────────────┘
+                                
+                                
+                                
+ F1 Help │ ^Q Quit              
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 38, 17), pos);
+			Assert.Equal (new Rect (2, 0, 32, 17), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -648,7 +648,6 @@ namespace Terminal.Gui.Core {
 			Application.Begin (Application.Top);
 			((FakeDriver)Application.Driver).SetBufferSize (44, 17);
 
-
 			Assert.Equal (new Rect (9, 3, 20, 1), tf.Frame);
 			Assert.True (tf.HasFocus);
 
@@ -663,15 +662,15 @@ namespace Terminal.Gui.Core {
 │                                          │
 │                                          │
 │  Label: TextField                        │
-│         ┌───────────────────────────┐    │
-│         │ Select All         Ctrl+T │    │
-│         │ Delete All   Ctrl+Shift+D │
-│         │ Copy               Ctrl+C │    │
-│         │ Cut                Ctrl+X │    │
-│         │ Paste              Ctrl+V │    │
-│         │ Undo               Ctrl+Z │    │
-│         │ Redo               Ctrl+Y │    │
-│         └───────────────────────────┘    │
+│         ┌─────────────────────┐      
+│         │ Select All   Ctrl+T │      
+│         │ Delete All   Ctrl+R │      
+│         │ Copy         Ctrl+C │      
+│         │ Cut          Ctrl+X │      
+│         │ Paste        Ctrl+V │      
+│         │ Undo         Ctrl+Z │      
+│         │ Redo         Ctrl+Y │      
+│         └─────────────────────┘      
 └──────────────────────────────────────────┘
  F1 Help │ ^Q Quit                          
 ";
@@ -679,5 +678,215 @@ namespace Terminal.Gui.Core {
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (2, 0, 44, 17), pos);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Menus_And_SubMenus_Always_Try_To_Be_On_Screen ()
+		{
+			var cm = new ContextMenu (-1, -2,
+				new MenuBarItem (new MenuItem [] {
+					new MenuItem ("One", "", null),
+					new MenuItem ("Two", "", null),
+					new MenuItem ("Three", "", null),
+					new MenuBarItem ("Four", new MenuItem [] {
+						new MenuItem ("SubMenu1", "", null),
+						new MenuItem ("SubMenu2", "", null),
+						new MenuItem ("SubMenu3", "", null),
+						new MenuItem ("SubMenu4", "", null),
+						new MenuItem ("SubMenu5", "", null),
+						new MenuItem ("SubMenu6", "", null),
+						new MenuItem ("SubMenu7", "", null)
+					}),
+					new MenuItem ("Five", "", null),
+					new MenuItem ("Six", "", null)
+				})
+			);
+
+			Assert.Equal (new Point (-1, -2), cm.Position);
+
+			cm.Show ();
+			Assert.Equal (new Point (-1, -2), cm.Position);
+			var top = Application.Top;
+			Application.Begin (top);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│ One    │
+│ Two    │
+│ Three  │
+│ Four  ►│
+│ Five   │
+│ Six    │
+└────────┘
+", output);
+
+			Assert.True (top.Subviews [0].MouseEvent (new MouseEvent {
+				X = 0,
+				Y = 4,
+				Flags = MouseFlags.ReportMousePosition,
+				View = top.Subviews [0]
+			}));
+			Application.Refresh ();
+			Assert.Equal (new Point (-1, -2), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐             
+│ One    │             
+│ Two    │             
+│ Three  │             
+│ Four  ►│┌───────────┐
+│ Five   ││ SubMenu1  │
+│ Six    ││ SubMenu2  │
+└────────┘│ SubMenu3  │
+          │ SubMenu4  │
+          │ SubMenu5  │
+          │ SubMenu6  │
+          │ SubMenu7  │
+          └───────────┘
+", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (40, 20);
+			cm.Position = new Point (41, -2);
+			cm.Show ();
+			Application.Refresh ();
+			Assert.Equal (new Point (41, -2), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                              ┌────────┐
+                              │ One    │
+                              │ Two    │
+                              │ Three  │
+                              │ Four  ►│
+                              │ Five   │
+                              │ Six    │
+                              └────────┘
+", output);
+
+			Assert.True (top.Subviews [0].MouseEvent (new MouseEvent {
+				X = 30,
+				Y = 4,
+				Flags = MouseFlags.ReportMousePosition,
+				View = top.Subviews [0]
+			}));
+			Application.Refresh ();
+			Assert.Equal (new Point (41, -2), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                              ┌────────┐
+                              │ One    │
+                              │ Two    │
+                              │ Three  │
+                 ┌───────────┐│ Four  ►│
+                 │ SubMenu1  ││ Five   │
+                 │ SubMenu2  ││ Six    │
+                 │ SubMenu3  │└────────┘
+                 │ SubMenu4  │          
+                 │ SubMenu5  │          
+                 │ SubMenu6  │          
+                 │ SubMenu7  │          
+                 └───────────┘          
+", output);
+
+			cm.Position = new Point (41, 9);
+			cm.Show ();
+			Application.Refresh ();
+			Assert.Equal (new Point (41, 9), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                              ┌────────┐
+                              │ One    │
+                              │ Two    │
+                              │ Three  │
+                              │ Four  ►│
+                              │ Five   │
+                              │ Six    │
+                              └────────┘
+", output);
+
+			Assert.True (top.Subviews [0].MouseEvent (new MouseEvent {
+				X = 30,
+				Y = 4,
+				Flags = MouseFlags.ReportMousePosition,
+				View = top.Subviews [0]
+			}));
+			Application.Refresh ();
+			Assert.Equal (new Point (41, 9), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                              ┌────────┐
+                 ┌───────────┐│ One    │
+                 │ SubMenu1  ││ Two    │
+                 │ SubMenu2  ││ Three  │
+                 │ SubMenu3  ││ Four  ►│
+                 │ SubMenu4  ││ Five   │
+                 │ SubMenu5  ││ Six    │
+                 │ SubMenu6  │└────────┘
+                 │ SubMenu7  │          
+                 └───────────┘          
+", output);
+
+			cm.Position = new Point (41, 22);
+			cm.Show ();
+			Application.Refresh ();
+			Assert.Equal (new Point (41, 22), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                              ┌────────┐
+                              │ One    │
+                              │ Two    │
+                              │ Three  │
+                              │ Four  ►│
+                              │ Five   │
+                              │ Six    │
+                              └────────┘
+", output);
+
+			Assert.True (top.Subviews [0].MouseEvent (new MouseEvent {
+				X = 30,
+				Y = 4,
+				Flags = MouseFlags.ReportMousePosition,
+				View = top.Subviews [0]
+			}));
+			Application.Refresh ();
+			Assert.Equal (new Point (41, 22), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+                 ┌───────────┐          
+                 │ SubMenu1  │┌────────┐
+                 │ SubMenu2  ││ One    │
+                 │ SubMenu3  ││ Two    │
+                 │ SubMenu4  ││ Three  │
+                 │ SubMenu5  ││ Four  ►│
+                 │ SubMenu6  ││ Five   │
+                 │ SubMenu7  ││ Six    │
+                 └───────────┘└────────┘
+", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (18, 8);
+			cm.Position = new Point (19, 10);
+			cm.Show ();
+			Application.Refresh ();
+			Assert.Equal (new Point (19, 10), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+        ┌────────┐
+        │ One    │
+        │ Two    │
+        │ Three  │
+        │ Four  ►│
+        │ Five   │
+        │ Six    │
+        └────────┘
+", output);
+
+			Assert.True (top.Subviews [0].MouseEvent (new MouseEvent {
+				X = 30,
+				Y = 4,
+				Flags = MouseFlags.ReportMousePosition,
+				View = top.Subviews [0]
+			}));
+			Application.Refresh ();
+			Assert.Equal (new Point (19, 10), cm.Position);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌───────────┐────┐
+│ SubMenu1  │    │
+│ SubMenu2  │    │
+│ SubMenu3  │ee  │
+│ SubMenu4  │r  ►│
+│ SubMenu5  │e   │
+│ SubMenu6  │    │
+│ SubMenu7  │────┘
+", output);
+		}
 	}
 }

+ 321 - 0
UnitTests/ListViewTests.cs

@@ -5,9 +5,17 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class ListViewTests {
+		readonly ITestOutputHelper output;
+
+		public ListViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Constructors_Defaults ()
 		{
@@ -30,6 +38,97 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (new Rect (0, 1, 10, 20), lv.Frame);
 		}
 
+		[Fact]
+		public void ListViewSelectThenDown ()
+		{
+			var lv = new ListView (new List<string> () { "One", "Two", "Three" });
+			lv.AllowsMarking = true;
+
+			Assert.NotNull (lv.Source);
+
+			// first item should be selected by default
+			Assert.Equal (0, lv.SelectedItem);
+
+			// nothing is ticked
+			Assert.False (lv.Source.IsMarked (0));
+			Assert.False (lv.Source.IsMarked (1));
+			Assert.False (lv.Source.IsMarked (2));
+
+			lv.AddKeyBinding (Key.Space | Key.ShiftMask, Command.ToggleChecked, Command.LineDown);
+
+			var ev = new KeyEvent (Key.Space | Key.ShiftMask, new KeyModifiers () { Shift = true });
+
+			// view should indicate that it has accepted and consumed the event
+			Assert.True (lv.ProcessKey (ev));
+
+			// second item should now be selected
+			Assert.Equal (1, lv.SelectedItem);
+
+			// first item only should be ticked
+			Assert.True (lv.Source.IsMarked (0));
+			Assert.False (lv.Source.IsMarked (1));
+			Assert.False (lv.Source.IsMarked (2));
+
+			// Press key combo again
+			Assert.True (lv.ProcessKey (ev));
+			Assert.Equal (2, lv.SelectedItem);
+			Assert.True (lv.Source.IsMarked (0));
+			Assert.True (lv.Source.IsMarked (1));
+			Assert.False (lv.Source.IsMarked (2));
+
+			// Press key combo again
+			Assert.True (lv.ProcessKey (ev));
+			Assert.Equal (2, lv.SelectedItem); // cannot move down any further
+			Assert.True (lv.Source.IsMarked (0));
+			Assert.True (lv.Source.IsMarked (1));
+			Assert.True (lv.Source.IsMarked (2)); // but can toggle marked
+
+			// Press key combo again 
+			Assert.True (lv.ProcessKey (ev));
+			Assert.Equal (2, lv.SelectedItem); // cannot move down any further
+			Assert.True (lv.Source.IsMarked (0));
+			Assert.True (lv.Source.IsMarked (1));
+			Assert.False (lv.Source.IsMarked (2)); // untoggle toggle marked
+		}
+		[Fact]
+		public void SettingEmptyKeybindingThrows ()
+		{
+			var lv = new ListView (new List<string> () { "One", "Two", "Three" });
+			Assert.Throws<ArgumentException> (() => lv.AddKeyBinding (Key.Space));
+		}
+
+
+		/// <summary>
+		/// Tests that when none of the Commands in a chained keybinding are possible
+		/// the <see cref="View.ProcessKey(KeyEvent)"/> returns the appropriate result
+		/// </summary>
+		[Fact]
+		public void ListViewProcessKeyReturnValue_WithMultipleCommands ()
+		{
+			var lv = new ListView (new List<string> () { "One", "Two", "Three", "Four" });
+
+			Assert.NotNull (lv.Source);
+
+			// first item should be selected by default
+			Assert.Equal (0, lv.SelectedItem);
+
+			// bind shift down to move down twice in control
+			lv.AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDown, Command.LineDown);
+
+			var ev = new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers () { Shift = true });
+
+			Assert.True (lv.ProcessKey (ev), "The first time we move down 2 it should be possible");
+
+			// After moving down twice from One we should be at 'Three'
+			Assert.Equal (2, lv.SelectedItem);
+
+			// clear the items
+			lv.SetSource (null);
+
+			// Press key combo again - return should be false this time as none of the Commands are allowable
+			Assert.False (lv.ProcessKey (ev), "We cannot move down so will not respond to this");
+		}
+
 		private class NewListDataSource : IListDataSource {
 			public int Count => throw new NotImplementedException ();
 
@@ -130,5 +229,227 @@ namespace Terminal.Gui.Views {
 				return item;
 			}
 		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp ()
+		{
+			var source = new List<string> ();
+			for (int i = 0; i < 20; i++) {
+				source.Add ($"Line{i}");
+			}
+			var lv = new ListView (source) { Width = Dim.Fill (), Height = Dim.Fill () };
+			var win = new Window ();
+			win.Add (lv);
+			Application.Top.Add (win);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (12, 12);
+			Application.Refresh ();
+
+			Assert.Equal (0, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line0     │
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│Line9     │
+└──────────┘", output);
+
+			Assert.True (lv.ScrollDown (10));
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (0, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
+│Line19    │
+└──────────┘", output);
+
+			Assert.True (lv.MoveDown ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (1, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│Line9     │
+│Line10    │
+└──────────┘", output);
+
+			Assert.True (lv.MoveEnd ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (19, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line19    │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+└──────────┘", output);
+
+			Assert.True (lv.ScrollUp (20));
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (19, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line0     │
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│Line9     │
+└──────────┘", output);
+
+			Assert.True (lv.MoveDown ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (19, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
+│Line19    │
+└──────────┘", output);
+
+			Assert.True (lv.ScrollUp (20));
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (19, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line0     │
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│Line9     │
+└──────────┘", output);
+
+			Assert.True (lv.MoveDown ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (19, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
+│Line19    │
+└──────────┘", output);
+
+			Assert.True (lv.MoveHome ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (0, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line0     │
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│Line9     │
+└──────────┘", output);
+
+			Assert.True (lv.ScrollDown (20));
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (0, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line19    │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+│          │
+└──────────┘", output);
+
+			Assert.True (lv.MoveUp ());
+			lv.Redraw (lv.Bounds);
+			Assert.Equal (0, lv.SelectedItem);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────┐
+│Line0     │
+│Line1     │
+│Line2     │
+│Line3     │
+│Line4     │
+│Line5     │
+│Line6     │
+│Line7     │
+│Line8     │
+│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);
+		}
 	}
 }

+ 88 - 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,71 @@ namespace Terminal.Gui.Core {
 		// - wait = false
 
 		// TODO: Add IMainLoop tests
+
+		volatile static int tbCounter = 0;
+		static ManualResetEventSlim _wakeUp = new ManualResetEventSlim (false);
+
+		private static void Launch (Random r, TextField tf, int target)
+		{
+			Task.Run (() => {
+				Thread.Sleep (r.Next (2, 4));
+				Application.MainLoop.Invoke (() => {
+					tf.Text = $"index{r.Next ()}";
+					Interlocked.Increment (ref tbCounter);
+					if (target == tbCounter) {
+						// On last increment wake up the check
+						_wakeUp.Set ();
+					}
+				});
+			});
+		}
+
+		private static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs)
+		{
+			for (int j = 0; j < numPasses; j++) {
+
+				_wakeUp.Reset ();
+				for (var i = 0; i < numIncrements; i++) {
+					Launch (r, tf, (j + 1) * numIncrements);
+				}
+
+
+				while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
+				{
+					var tbNow = tbCounter;
+					_wakeUp.Wait (pollMs);
+					if (tbCounter == tbNow) {
+						// No change after wait: 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 = 20000;
+
+			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);
+		}
 	}
 }

+ 2 - 2
UnitTests/MenuTests.cs

@@ -670,7 +670,7 @@ Edit
 
 			menu.CloseAllMenus ();
 			menu.Frame = new Rect (0, 0, menu.Frame.Width, menu.Frame.Height);
-			((FakeDriver)Application.Driver).SetBufferSize (7, 4);
+			((FakeDriver)Application.Driver).SetBufferSize (7, 3);
 			menu.OpenMenu ();
 			Application.Refresh ();
 
@@ -681,7 +681,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 1, 7, 3), pos);
+			Assert.Equal (new Rect (0, 0, 7, 3), pos);
 		}
 
 		[Fact, AutoInitShutdown]

+ 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;
+	}
+}

+ 202 - 23
UnitTests/ScrollBarViewTests.cs

@@ -192,9 +192,9 @@ namespace Terminal.Gui.Views {
 			_hostView.Redraw (_hostView.Bounds);
 
 			Assert.Equal (_scrollBar.Position, _hostView.Top);
-			Assert.Equal (_scrollBar.Size, _hostView.Lines + 1);
+			Assert.Equal (_scrollBar.Size, _hostView.Lines);
 			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
-			Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols + 1);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols);
 		}
 
 		[Fact]
@@ -310,8 +310,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (25, _hostView.Bounds.Height);
 			Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width);
 			Assert.Equal (24, _scrollBar.Bounds.Height);
-			Assert.Equal (31, _scrollBar.Size);
-			Assert.Equal (101, _scrollBar.OtherScrollBarView.Size);
+			Assert.Equal (30, _scrollBar.Size);
+			Assert.Equal (100, _scrollBar.OtherScrollBarView.Size);
 			Assert.True (_scrollBar.ShowScrollIndicator);
 			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
 			Assert.True (_scrollBar.Visible);
@@ -320,8 +320,8 @@ namespace Terminal.Gui.Views {
 			_scrollBar.Position = 50;
 			Assert.Equal (_scrollBar.Position, _scrollBar.Size - _scrollBar.Bounds.Height);
 			Assert.Equal (_scrollBar.Position, _hostView.Top);
-			Assert.Equal (7, _scrollBar.Position);
-			Assert.Equal (7, _hostView.Top);
+			Assert.Equal (6, _scrollBar.Position);
+			Assert.Equal (6, _hostView.Top);
 			Assert.True (_scrollBar.ShowScrollIndicator);
 			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
 			Assert.True (_scrollBar.Visible);
@@ -330,8 +330,8 @@ namespace Terminal.Gui.Views {
 			_scrollBar.OtherScrollBarView.Position = 150;
 			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - _scrollBar.OtherScrollBarView.Bounds.Width);
 			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
-			Assert.Equal (22, _scrollBar.OtherScrollBarView.Position);
-			Assert.Equal (22, _hostView.Left);
+			Assert.Equal (21, _scrollBar.OtherScrollBarView.Position);
+			Assert.Equal (21, _hostView.Left);
 			Assert.True (_scrollBar.ShowScrollIndicator);
 			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
 			Assert.True (_scrollBar.Visible);
@@ -350,14 +350,14 @@ namespace Terminal.Gui.Views {
 			_scrollBar.Position = 50;
 			Assert.Equal (_scrollBar.Position, _scrollBar.Size - 1);
 			Assert.Equal (_scrollBar.Position, _hostView.Top);
-			Assert.Equal (30, _scrollBar.Position);
-			Assert.Equal (30, _hostView.Top);
+			Assert.Equal (29, _scrollBar.Position);
+			Assert.Equal (29, _hostView.Top);
 
 			_scrollBar.OtherScrollBarView.Position = 150;
 			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - 1);
 			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
-			Assert.Equal (100, _scrollBar.OtherScrollBarView.Position);
-			Assert.Equal (100, _hostView.Left);
+			Assert.Equal (99, _scrollBar.OtherScrollBarView.Position);
+			Assert.Equal (99, _hostView.Left);
 		}
 
 		[Fact]
@@ -497,7 +497,7 @@ namespace Terminal.Gui.Views {
 				};
 
 				listView.DrawContent += (e) => {
-					newScrollBarView.Size = listView.Source.Count - 1;
+					newScrollBarView.Size = listView.Source.Count;
 					Assert.Equal (newScrollBarView.Size, listView.Source.Count);
 					newScrollBarView.Position = listView.TopItem;
 					Assert.Equal (newScrollBarView.Position, listView.TopItem);
@@ -572,7 +572,7 @@ namespace Terminal.Gui.Views {
 				};
 
 				listView.DrawContent += (e) => {
-					newScrollBarView.Size = listView.Maxlength - 1;
+					newScrollBarView.Size = listView.Maxlength;
 					Assert.Equal (newScrollBarView.Size, listView.Maxlength);
 					newScrollBarView.Position = listView.LeftItem;
 					Assert.Equal (newScrollBarView.Position, listView.LeftItem);
@@ -618,9 +618,9 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (0, max);
 			Assert.False (sbv.OtherScrollBarView.CanScroll (10, out max, sbv.OtherScrollBarView.IsVertical));
 			Assert.Equal (0, max);
-			// They are visible but are not drawn.
-			Assert.True (sbv.Visible);
-			Assert.True (sbv.OtherScrollBarView.Visible);
+			// They aren't visible so they aren't drawn.
+			Assert.False (sbv.Visible);
+			Assert.False (sbv.OtherScrollBarView.Visible);
 			top.LayoutSubviews ();
 			// Now the host bounds is not empty.
 			Assert.True (sbv.CanScroll (10, out max, sbv.IsVertical));
@@ -628,17 +628,19 @@ namespace Terminal.Gui.Views {
 			Assert.True (sbv.OtherScrollBarView.CanScroll (10, out max, sbv.OtherScrollBarView.IsVertical));
 			Assert.Equal (10, max);
 			Assert.True (sbv.CanScroll (50, out max, sbv.IsVertical));
-			Assert.Equal (17, max); // 17+23=40
+			Assert.Equal (40, sbv.Size);
+			Assert.Equal (15, max); // 15+25=40
 			Assert.True (sbv.OtherScrollBarView.CanScroll (150, out max, sbv.OtherScrollBarView.IsVertical));
-			Assert.Equal (22, max); // 22+78=100
-			Assert.True (sbv.Visible);
-			Assert.True (sbv.OtherScrollBarView.Visible);
+			Assert.Equal (100, sbv.OtherScrollBarView.Size);
+			Assert.Equal (20, max); // 20+80=100
+			Assert.False (sbv.Visible);
+			Assert.False (sbv.OtherScrollBarView.Visible);
 			sbv.KeepContentAlwaysInViewport = false;
 			sbv.OtherScrollBarView.KeepContentAlwaysInViewport = false;
 			Assert.True (sbv.CanScroll (50, out max, sbv.IsVertical));
-			Assert.Equal (40, max);
+			Assert.Equal (39, max);
 			Assert.True (sbv.OtherScrollBarView.CanScroll (150, out max, sbv.OtherScrollBarView.IsVertical));
-			Assert.Equal (100, max);
+			Assert.Equal (99, max);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
 		}
@@ -801,5 +803,182 @@ namespace Terminal.Gui.Views {
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 10), pos);
 		}
+
+
+		[Fact, AutoInitShutdown]
+		public void ContentBottomRightCorner_Not_Redraw_If_Both_Size_Equal_To_Zero ()
+		{
+			var text = "This is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test";
+			var label = new Label (text);
+			Application.Top.Add (label);
+
+			var sbv = new ScrollBarView (label, true, true) {
+				Size = 100,
+			};
+			sbv.OtherScrollBarView.Size = 100;
+			Application.Begin (Application.Top);
+
+			Assert.Equal (100, sbv.Size);
+			Assert.Equal (100, sbv.OtherScrollBarView.Size);
+			Assert.True (sbv.ShowScrollIndicator);
+			Assert.True (sbv.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (sbv.Visible);
+			Assert.True (sbv.OtherScrollBarView.Visible);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes▲
+This is a tes┬
+This is a tes┴
+This is a tes░
+This is a tes▼
+◄├─┤░░░░░░░░► 
+", output);
+
+			sbv.Size = 0;
+			sbv.OtherScrollBarView.Size = 0;
+			Assert.Equal (0, sbv.Size);
+			Assert.Equal (0, sbv.OtherScrollBarView.Size);
+			Assert.False (sbv.ShowScrollIndicator);
+			Assert.False (sbv.OtherScrollBarView.ShowScrollIndicator);
+			Assert.False (sbv.Visible);
+			Assert.False (sbv.OtherScrollBarView.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+", output);
+
+			sbv.Size = 50;
+			sbv.OtherScrollBarView.Size = 50;
+			Assert.Equal (50, sbv.Size);
+			Assert.Equal (50, sbv.OtherScrollBarView.Size);
+			Assert.True (sbv.ShowScrollIndicator);
+			Assert.True (sbv.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (sbv.Visible);
+			Assert.True (sbv.OtherScrollBarView.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes▲
+This is a tes┬
+This is a tes┴
+This is a tes░
+This is a tes▼
+◄├──┤░░░░░░░► 
+", output);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentBottomRightCorner_Not_Redraw_If_One_Size_Equal_To_Zero ()
+		{
+			var text = "This is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test";
+			var label = new Label (text);
+			Application.Top.Add (label);
+
+			var sbv = new ScrollBarView (label, true, false) {
+				Size = 100,
+			};
+			Application.Begin (Application.Top);
+
+			Assert.Equal (100, sbv.Size);
+			Assert.Null (sbv.OtherScrollBarView);
+			Assert.True (sbv.ShowScrollIndicator);
+			Assert.True (sbv.Visible);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes▲
+This is a tes┬
+This is a tes┴
+This is a tes░
+This is a tes░
+This is a tes▼
+", output);
+
+			sbv.Size = 0;
+			Assert.Equal (0, sbv.Size);
+			Assert.False (sbv.ShowScrollIndicator);
+			Assert.False (sbv.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowScrollIndicator_False_Must_Also_Set_Visible_To_False_To_Not_Respond_To_Events ()
+		{
+			var clicked = false;
+			var text = "This is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test";
+			var label = new Label (text) { Width = 14, Height = 5 };
+			var btn = new Button (14, 0, "Click Me!");
+			btn.Clicked += () => clicked = true;
+			Application.Top.Add (label, btn);
+
+			var sbv = new ScrollBarView (label, true, false) {
+				Size = 5,
+			};
+			Application.Begin (Application.Top);
+
+			Assert.Equal (5, sbv.Size);
+			Assert.Null (sbv.OtherScrollBarView);
+			Assert.False (sbv.ShowScrollIndicator);
+			Assert.False (sbv.Visible);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a test[ Click Me! ]
+This is a test             
+This is a test             
+This is a test             
+This is a test             
+", output);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 15,
+					Y = 0,
+					Flags = MouseFlags.Button1Clicked
+				});
+
+			Assert.Null (Application.mouseGrabView);
+			Assert.True (clicked);
+
+			clicked = false;
+
+			sbv.Visible = true;
+			Assert.Equal (5, sbv.Size);
+			Assert.False (sbv.ShowScrollIndicator);
+			Assert.True (sbv.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a test[ Click Me! ]
+This is a test             
+This is a test             
+This is a test             
+This is a test             
+", output);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 15,
+					Y = 0,
+					Flags = MouseFlags.Button1Clicked
+				});
+
+			Assert.Null (Application.mouseGrabView);
+			Assert.True (clicked);
+			Assert.Equal (5, sbv.Size);
+			Assert.False (sbv.ShowScrollIndicator);
+			Assert.False (sbv.Visible);
+		}
 	}
 }

+ 149 - 24
UnitTests/TextFormatterTests.cs

@@ -1093,11 +1093,11 @@ namespace Terminal.Gui.Core {
 			text = "A sentence has words.";
 			// should fit
 			maxWidth = text.RuneCount + 1;
-			expectedClippedWidth = Math.Min (text.RuneCount, maxWidth);
+			expectedClippedWidth = Math.Max (text.RuneCount, maxWidth);
 			justifiedText = TextFormatter.ClipAndJustify (text, maxWidth, align);
-			//Assert.Equal (expectedClippedWidth, justifiedText.RuneCount);
+			Assert.Equal (expectedClippedWidth, justifiedText.RuneCount);
 			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText);
+			Assert.Throws<ArgumentOutOfRangeException> (() => ustring.Make (text.ToRunes () [0..expectedClippedWidth]));
 
 			// Should fit.
 			maxWidth = text.RuneCount + 0;
@@ -1205,7 +1205,6 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText);
 
 			// see Justify_ tests below
-
 		}
 
 		[Fact]
@@ -1310,7 +1309,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "012++456+89";
 			forceToWidth = text.RuneCount + 1;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1322,7 +1321,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "012+++456++89";
 			forceToWidth = text.RuneCount + 3;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1334,7 +1333,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+++");
+			justifiedText = "012++++456+++89";
 			forceToWidth = text.RuneCount + 5;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1352,7 +1351,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++++++++++++");
+			justifiedText = "012+++++++++++++456++++++++++++89";
 			forceToWidth = text.RuneCount + 23;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1368,13 +1367,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "012++456+89+end";
 			forceToWidth = text.RuneCount + 1;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "012++456++89+end";
 			forceToWidth = text.RuneCount + 2;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1386,13 +1385,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "012+++456++89++end";
 			forceToWidth = text.RuneCount + 4;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "012+++456+++89++end";
 			forceToWidth = text.RuneCount + 5;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1404,13 +1403,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+++++++");
+			justifiedText = "012++++++++456++++++++89+++++++end";
 			forceToWidth = text.RuneCount + 20;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++++++++");
+			justifiedText = "012+++++++++456+++++++++89++++++++end";
 			forceToWidth = text.RuneCount + 23;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1427,7 +1426,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "пÑÐ++вÐ+Ñ";
 			forceToWidth = text.RuneCount + 1;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1439,7 +1438,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "пÑÐ+++вÐ++Ñ";
 			forceToWidth = text.RuneCount + 3;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1451,7 +1450,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+++");
+			justifiedText = "пÑÐ++++вÐ+++Ñ";
 			forceToWidth = text.RuneCount + 5;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1469,7 +1468,7 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++++++++++++");
+			justifiedText = "пÑÐ+++++++++++++вÐ++++++++++++Ñ";
 			forceToWidth = text.RuneCount + 23;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1486,13 +1485,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "Ð++ÑÐ+вÐ+Ñ";
 			forceToWidth = text.RuneCount + 1;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+");
+			justifiedText = "Ð++ÑÐ++вÐ+Ñ";
 			forceToWidth = text.RuneCount + 2;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1504,13 +1503,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "Ð+++ÑÐ++вÐ++Ñ";
 			forceToWidth = text.RuneCount + 4;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++");
+			justifiedText = "Ð+++ÑÐ+++вÐ++Ñ";
 			forceToWidth = text.RuneCount + 5;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -1522,13 +1521,13 @@ namespace Terminal.Gui.Core {
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "+++++++");
+			justifiedText = "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ";
 			forceToWidth = text.RuneCount + 20;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
 			Assert.True (Math.Abs (forceToWidth - justifiedText.ConsoleWidth) < text.Count (" "));
 
-			justifiedText = text.Replace (" ", "++++++++");
+			justifiedText = "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ";
 			forceToWidth = text.RuneCount + 23;
 			Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, forceToWidth, fillChar).ToString ());
 			Assert.True (Math.Abs (forceToWidth - justifiedText.RuneCount) < text.Count (" "));
@@ -2934,6 +2933,38 @@ namespace Terminal.Gui.Core {
 			Assert.Null (exception);
 		}
 
+
+
+		[Fact, AutoInitShutdown]
+		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal ()
+		{
+			ustring text = "Hello world, how are you today? Pretty neat!";
+
+			Assert.Equal (44, text.RuneCount);
+
+			for (int i = 44; i < 80; i++) {
+				var fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0];
+				Assert.Equal (i, fmtText.RuneCount);
+				var c = (char)fmtText [^1];
+				Assert.Equal ('!', c);
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical ()
+		{
+			ustring text = "Hello world, how are you today? Pretty neat!";
+
+			Assert.Equal (44, text.RuneCount);
+
+			for (int i = 44; i < 80; i++) {
+				var fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0];
+				Assert.Equal (i, fmtText.RuneCount);
+				var c = (char)fmtText [^1];
+				Assert.Equal ('!', c);
+			}
+		}
+
 		[Fact]
 		public void Draw_Horizontal_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
 		{
@@ -3334,6 +3365,48 @@ e
 			Assert.Equal (new Rect (0, 0, 13, height + 2), pos);
 		}
 
+		[Fact, AutoInitShutdown]
+		public void Draw_Fill_Remaining ()
+		{
+			var view = new View ("This view needs to be cleared before rewritten.");
+
+			var tf1 = new TextFormatter ();
+			tf1.Text = "This TextFormatter (tf1) without fill will not be cleared on rewritten.";
+			var tf1Size = tf1.Size;
+
+			var tf2 = new TextFormatter ();
+			tf2.Text = "This TextFormatter (tf2) with fill will be cleared on rewritten.";
+			var tf2Size = tf2.Size;
+
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			tf1.Draw (new Rect (new Point (0, 1), tf1Size), view.GetNormalColor (), view.ColorScheme.HotNormal, default, false);
+
+			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This view needs to be cleared before rewritten.                        
+This TextFormatter (tf1) without fill will not be cleared on rewritten.
+This TextFormatter (tf2) with fill will be cleared on rewritten.       
+", output);
+
+			view.Text = "This view is rewritten.";
+			view.Redraw (view.Bounds);
+
+			tf1.Text = "This TextFormatter (tf1) is rewritten.";
+			tf1.Draw (new Rect (new Point (0, 1), tf1Size), view.GetNormalColor (), view.ColorScheme.HotNormal, default, false);
+
+			tf2.Text = "This TextFormatter (tf2) is rewritten.";
+			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This view is rewritten.                                                
+This TextFormatter (tf1) is rewritten.will not be cleared on rewritten.
+This TextFormatter (tf2) is rewritten.                                 
+", output);
+		}
+
 		[Fact]
 		public void GetTextWidth_Simple_And_Wide_Runes ()
 		{
@@ -4012,5 +4085,57 @@ e
 			Assert.Equal ("Line2", formated [1]);
 			Assert.Equal ("Line3", formated [^1]);
 		}
+
+		[Fact]
+		public void SplitNewLine_Ending_Without_NewLine_Probably_CRLF ()
+		{
+			var text = $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界";
+			var splited = TextFormatter.SplitNewLine (text);
+			Assert.Equal ("First Line 界", splited [0]);
+			Assert.Equal ("Second Line 界", splited [1]);
+			Assert.Equal ("Third Line 界", splited [^1]);
+		}
+
+		[Fact]
+		public void SplitNewLine_Ending_With_NewLine_Probably_CRLF ()
+		{
+			var text = $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}";
+			var splited = TextFormatter.SplitNewLine (text);
+			Assert.Equal ("First Line 界", splited [0]);
+			Assert.Equal ("Second Line 界", splited [1]);
+			Assert.Equal ("Third Line 界", splited [2]);
+			Assert.Equal ("", splited [^1]);
+		}
+
+		[Fact]
+		public void SplitNewLine_Ending_Without_NewLine_Only_LF ()
+		{
+			var text = $"First Line 界\nSecond Line 界\nThird Line 界";
+			var splited = TextFormatter.SplitNewLine (text);
+			Assert.Equal ("First Line 界", splited [0]);
+			Assert.Equal ("Second Line 界", splited [1]);
+			Assert.Equal ("Third Line 界", splited [^1]);
+		}
+
+		[Fact]
+		public void SplitNewLine_Ending_With_NewLine_Only_LF ()
+		{
+			var text = $"First Line 界\nSecond Line 界\nThird Line 界\n";
+			var splited = TextFormatter.SplitNewLine (text);
+			Assert.Equal ("First Line 界", splited [0]);
+			Assert.Equal ("Second Line 界", splited [1]);
+			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));
+		}
 	}
 }

+ 537 - 1
UnitTests/TextViewTests.cs

@@ -1323,20 +1323,26 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
 			Assert.True (_textView.Selecting);
 			_textView.Selecting = false;
 			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
+			Assert.Equal (new Point (28, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 			_textView.SelectionStartColumn = 24;
 			_textView.SelectionStartRow = 0;
 			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
-			Assert.True (_textView.Selecting);
 			_textView.Selecting = false;
 			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
+			Assert.Equal (new Point (28, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 		}
 
@@ -5873,5 +5879,535 @@ line.
 			tv.CursorPosition = new Point (tv.LeftColumn, tv.TopRow);
 			Assert.Equal (new Point (0, 50), tv.CursorPosition);
 		}
+
+		[Fact]
+		[InitShutdown]
+		public void Mouse_Button_Shift_Preserves_Selection ()
+		{
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 12, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (12, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump ", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 12, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (12, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump ", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 19, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (19, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 19, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (19, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between text", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between text", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Pressed }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void UnwrappedCursorPosition_Event ()
+		{
+			var cp = Point.Empty;
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = "This is the first line.\nThis is the second line.\n"
+			};
+			tv.UnwrappedCursorPosition += (e) => {
+				cp = e;
+			};
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.False (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.Equal (Point.Empty, cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.WordWrap = true;
+			tv.CursorPosition = new Point (12, 0);
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (6, 25);
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (1, 3), tv.CursorPosition);
+			Assert.Equal (new Point (13, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+
+			Assert.True (tv.MouseEvent (new MouseEvent () { X = 0, Y = 3, Flags = MouseFlags.Button1Pressed }));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void DeleteTextBackwards_WordWrap_False_Return_Undo ()
+		{
+			const string text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = text
+			};
+			var envText = tv.Text;
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.False (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (3, 0);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (0, 1);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.This is the second line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			while (tv.Text != envText) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal (envText, tv.Text);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void DeleteTextBackwards_WordWrap_True_Return_Undo ()
+		{
+			const string text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = text,
+				WordWrap = true
+			};
+			var envText = tv.Text;
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.True (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (3, 0);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (0, 1);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.This is the second line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			while (tv.Text != envText) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal (envText, tv.Text);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void DeleteTextForwards_WordWrap_False_Return_Undo ()
+		{
+			const string text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = text
+			};
+			var envText = tv.Text;
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.False (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (2, 0);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (22, 0);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.This is the second line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			while (tv.Text != envText) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal (envText, tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void DeleteTextForwards_WordWrap_True_Return_Undo ()
+		{
+			const string text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = text,
+				WordWrap = true
+			};
+			var envText = tv.Text;
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.True (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (2, 0);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			tv.CursorPosition = new Point (22, 0);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (22, 0), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.This is the second line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Ths is the first line.  
+This is the second line.
+", output);
+
+			while (tv.Text != envText) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal (envText, tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void TextView_InsertText_Newline_LF ()
+		{
+			var tv = new TextView {
+				Width = 10,
+				Height = 10,
+			};
+			tv.InsertText ("\naaa\nbbb");
+			var p = Environment.OSVersion.Platform;
+			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
+				Assert.Equal ("\r\naaa\r\nbbb", tv.Text);
+			} else {
+				Assert.Equal ("\naaa\nbbb", tv.Text);
+			}
+			Assert.Equal ($"{Environment.NewLine}aaa{Environment.NewLine}bbb", tv.Text);
+
+			var win = new Window ();
+			win.Add (tv);
+			Application.Top.Add (win);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (15, 15);
+			Application.Refresh ();
+			//this passes
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (
+			@"
+┌─────────────┐
+│             │
+│aaa          │
+│bbb          │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘", output);
+
+			Assert.Equal (new Rect (0, 0, 15, 15), pos);
+
+			Assert.True (tv.Used);
+			tv.Used = false;
+			tv.CursorPosition = new Point (0, 0);
+			tv.InsertText ("\naaa\nbbb");
+			Application.Refresh ();
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (
+			@"
+┌─────────────┐
+│             │
+│aaa          │
+│bbb          │
+│aaa          │
+│bbb          │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘", output);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void TextView_InsertText_Newline_CRLF ()
+		{
+			var tv = new TextView {
+				Width = 10,
+				Height = 10,
+			};
+			tv.InsertText ("\r\naaa\r\nbbb");
+			var p = Environment.OSVersion.Platform;
+			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
+				Assert.Equal ("\r\naaa\r\nbbb", tv.Text);
+			} else {
+				Assert.Equal ("\naaa\nbbb", tv.Text);
+			}
+			Assert.Equal ($"{Environment.NewLine}aaa{Environment.NewLine}bbb", tv.Text);
+
+			var win = new Window ();
+			win.Add (tv);
+			Application.Top.Add (win);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (15, 15);
+			Application.Refresh ();
+
+			//this passes
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (
+			@"
+┌─────────────┐
+│             │
+│aaa          │
+│bbb          │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘", output);
+
+			Assert.Equal (new Rect (0, 0, 15, 15), pos);
+
+			Assert.True (tv.Used);
+			tv.Used = false;
+			tv.CursorPosition = new Point (0, 0);
+			tv.InsertText ("\r\naaa\r\nbbb");
+			Application.Refresh ();
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (
+			@"
+┌─────────────┐
+│             │
+│aaa          │
+│bbb          │
+│aaa          │
+│bbb          │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘", output);
+		}
 	}
 }

+ 9 - 0
UnitTests/TreeViewTests.cs

@@ -543,6 +543,15 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (1, tree.ScrollOffsetVertical);
 		}
 
+		[Fact]
+		public void GoToEnd_ShouldNotFailOnEmptyTreeView ()
+		{
+			var tree = new TreeView ();
+
+			var exception = Record.Exception (() => tree.GoToEnd ());
+
+			Assert.Null (exception);
+		}
 
 		[Fact]
 		public void ObjectActivated_CustomKey ()

+ 2 - 2
UnitTests/UnitTests.csproj

@@ -18,8 +18,8 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
-    <PackageReference Include="ReportGenerator" Version="5.1.9" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
+    <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">

+ 89 - 0
UnitTests/ViewTests.cs

@@ -3803,5 +3803,94 @@ e
 ", output);
 		}
 
+		[Fact, AutoInitShutdown]
+		public void Visible_Clear_The_View_Output ()
+		{
+			var label = new Label ("Testing visibility.");
+			var win = new Window ();
+			win.Add (label);
+			var top = Application.Top;
+			top.Add (win);
+			Application.Begin (top);
+
+			Assert.True (label.Visible);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────────────────────────┐
+│Testing visibility.         │
+│                            │
+│                            │
+└────────────────────────────┘
+", output);
+
+			label.Visible = false;
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────────────────────────┐
+│                            │
+│                            │
+│                            │
+└────────────────────────────┘
+", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ClearOnVisibleFalse_Gets_Sets ()
+		{
+			var text = "This is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test\nThis is a test";
+			var label = new Label (text);
+			Application.Top.Add (label);
+
+			var sbv = new ScrollBarView (label, true, false) {
+				Size = 100,
+				ClearOnVisibleFalse = false
+			};
+			Application.Begin (Application.Top);
+
+			Assert.True (sbv.Visible);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes▲
+This is a tes┬
+This is a tes┴
+This is a tes░
+This is a tes░
+This is a tes▼
+", output);
+
+			sbv.Visible = false;
+			Assert.False (sbv.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+This is a test
+", output);
+
+			sbv.Visible = true;
+			Assert.True (sbv.Visible);
+			Application.Top.Redraw (Application.Top.Bounds);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes▲
+This is a tes┬
+This is a tes┴
+This is a tes░
+This is a tes░
+This is a tes▼
+", output);
+
+			sbv.ClearOnVisibleFalse = true;
+			sbv.Visible = false;
+			Assert.False (sbv.Visible);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is a tes
+This is a tes
+This is a tes
+This is a tes
+This is a tes
+This is a tes
+", output);
+		}
 	}
 }