Browse Source

Merge branch 'develop' into textview-textfield-isdrirty-fix-2212

BDisp 2 years ago
parent
commit
2f5d0f1ab1

+ 7 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1483,7 +1483,13 @@ namespace Terminal.Gui {
 							output.Append (WriteAttributes (attr));
 						}
 						outputWidth++;
-						output.Append ((char)contents [row, col, 0]);
+						var rune = contents [row, col, 0];
+						char [] spair;
+						if (Rune.DecodeSurrogatePair((uint) rune, out spair)) {
+							output.Append (spair);
+						} else {
+							output.Append ((char)rune);
+						}
 						contents [row, col, 2] = 0;
 					}
 				}

+ 5 - 11
Terminal.Gui/Core/Toplevel.cs

@@ -613,11 +613,8 @@ namespace Terminal.Gui {
 			}
 			nx = Math.Max (x, 0);
 			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
-			var canChange = SetWidth (top.Frame.Width, out int rWidth);
-			if (canChange && rWidth < 0 && nx >= top.Frame.X) {
-				nx = Math.Max (top.Frame.Right - 2, 0);
-			} else if (rWidth < 0 && nx >= top.Frame.X) {
-				nx = Math.Min (nx + 1, top.Frame.Right - 2);
+			if (nx + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.X + top.Frame.Width) {
+				nx = Math.Max (top.Frame.Right - (top.Border.DrawMarginFrame ? 2 : 1), 0);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
@@ -656,11 +653,8 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
-			canChange = SetHeight (top.Frame.Height, out int rHeight);
-			if (canChange && rHeight < 0 && ny >= top.Frame.Y) {
-				ny = Math.Max (top.Frame.Bottom - 2, 0);
-			} else if (rHeight < 0 && ny >= top.Frame.Y) {
-				ny = Math.Min (ny + 1, top.Frame.Bottom - 2);
+			if (ny + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.Y + top.Frame.Height) {
+				ny = Math.Max (top.Frame.Bottom - (top.Border.DrawMarginFrame ? 2 : 1), 0);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 
@@ -701,7 +695,7 @@ namespace Terminal.Gui {
 			}
 
 			if (sb != null && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
-					&& top.Height is Dim.DimFill) {
+				&& top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
 
 				top.Height = Dim.Fill (sb.Visible ? 1 : 0);
 				layoutSubviews = true;

+ 3 - 1
Terminal.Gui/Views/Menu.cs

@@ -1182,7 +1182,9 @@ namespace Terminal.Gui {
 		public virtual void OnMenuOpened ()
 		{
 			MenuItem mi = null;
-			if (openCurrentMenu.barItems.Children != null && openCurrentMenu?.current > -1) {
+			if (openCurrentMenu.barItems.Children != null && openCurrentMenu.barItems.Children.Length > 0
+				&& openCurrentMenu?.current > -1) {
+
 				mi = openCurrentMenu.barItems.Children [openCurrentMenu.current];
 			} else if (openCurrentMenu.barItems.IsTopLevel) {
 				mi = openCurrentMenu.barItems;

+ 1 - 50
Terminal.Gui/Views/StatusBar.cs

@@ -70,8 +70,6 @@ namespace Terminal.Gui {
 	/// So for each context must be a new instance of a statusbar.
 	/// </summary>
 	public class StatusBar : View {
-		bool disposedValue;
-
 		/// <summary>
 		/// The items that compose the <see cref="StatusBar"/>
 		/// </summary>
@@ -93,39 +91,9 @@ namespace Terminal.Gui {
 			CanFocus = false;
 			ColorScheme = Colors.Menu;
 			X = 0;
+			Y = Pos.AnchorEnd (1);
 			Width = Dim.Fill ();
 			Height = 1;
-
-			Initialized += StatusBar_Initialized;
-			Application.Resized += Application_Resized ();
-		}
-
-		private void StatusBar_Initialized (object sender, EventArgs e)
-		{
-			if (SuperView.Frame == Rect.Empty) {
-				((Toplevel)SuperView).Loaded += StatusBar_Loaded;
-			} else {
-				Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0);
-			}
-		}
-
-		private void StatusBar_Loaded ()
-		{
-			Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0);
-			((Toplevel)SuperView).Loaded -= StatusBar_Loaded;
-		}
-
-		private Action<Application.ResizedEventArgs> Application_Resized ()
-		{
-			return delegate {
-				X = 0;
-				Height = 1;
-				if (SuperView != null || SuperView is Toplevel) {
-					if (Frame.Y != SuperView.Frame.Height - (Visible ? 1 : 0)) {
-						Y = SuperView.Frame.Height - (Visible ? 1 : 0);
-					}
-				}
-			};
 		}
 
 		static ustring shortcutDelimiter = "-";
@@ -151,12 +119,6 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			//if (Frame.Y != Driver.Rows - 1) {
-			//	Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height);
-			//	Y = Driver.Rows - 1;
-			//	SetNeedsDisplay ();
-			//}
-
 			Move (0, 0);
 			Driver.SetAttribute (GetNormalColor ());
 			for (int i = 0; i < Frame.Width; i++)
@@ -234,17 +196,6 @@ namespace Terminal.Gui {
 			});
 		}
 
-		/// <inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			if (!disposedValue) {
-				if (disposing) {
-					Application.Resized -= Application_Resized ();
-				}
-				disposedValue = true;
-			}
-		}
-
 		///<inheritdoc/>
 		public override bool OnEnter (View view)
 		{

+ 192 - 22
Terminal.Gui/Views/TableView.cs

@@ -109,7 +109,7 @@ namespace Terminal.Gui {
 			get => columnOffset;
 
 			//try to prevent this being set to an out of bounds column
-			set => columnOffset = Table == null ? 0 : Math.Max (0, Math.Min (Table.Columns.Count - 1, value));
+			set => columnOffset = TableIsNullOrInvisible() ? 0 : Math.Max (0, Math.Min (Table.Columns.Count - 1, value));
 		}
 
 		/// <summary>
@@ -117,7 +117,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public int RowOffset {
 			get => rowOffset;
-			set => rowOffset = Table == null ? 0 : Math.Max (0, Math.Min (Table.Rows.Count - 1, value));
+			set => rowOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Rows.Count - 1, value));
 		}
 
 		/// <summary>
@@ -130,7 +130,7 @@ namespace Terminal.Gui {
 				var oldValue = selectedColumn;
 
 				//try to prevent this being set to an out of bounds column
-				selectedColumn = Table == null ? 0 : Math.Min (Table.Columns.Count - 1, Math.Max (0, value));
+				selectedColumn = TableIsNullOrInvisible () ? 0 : Math.Min (Table.Columns.Count - 1, Math.Max (0, value));
 
 				if (oldValue != selectedColumn)
 					OnSelectedCellChanged (new SelectedCellChangedEventArgs (Table, oldValue, SelectedColumn, SelectedRow, SelectedRow));
@@ -146,7 +146,7 @@ namespace Terminal.Gui {
 
 				var oldValue = selectedRow;
 
-				selectedRow = Table == null ? 0 : Math.Min (Table.Rows.Count - 1, Math.Max (0, value));
+				selectedRow = TableIsNullOrInvisible () ? 0 : Math.Min (Table.Rows.Count - 1, Math.Max (0, value));
 
 				if (oldValue != selectedRow)
 					OnSelectedCellChanged (new SelectedCellChangedEventArgs (Table, SelectedColumn, SelectedColumn, oldValue, selectedRow));
@@ -315,7 +315,7 @@ namespace Terminal.Gui {
 				var rowToRender = RowOffset + (line - headerLinesConsumed);
 
 				//if we have run off the end of the table
-				if (Table == null || rowToRender >= Table.Rows.Count || rowToRender < 0)
+				if (TableIsNullOrInvisible () || rowToRender >= Table.Rows.Count || rowToRender < 0)
 					continue;
 
 				RenderRow (line, rowToRender, columnsToRender);
@@ -427,6 +427,36 @@ namespace Terminal.Gui {
 
 		private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender [] columnsToRender)
 		{
+			/*
+			 *  First lets work out if we should be rendering scroll indicators
+			 */
+
+			// are there are visible columns to the left that have been pushed
+			// off the screen due to horizontal scrolling?
+			bool moreColumnsToLeft = ColumnOffset > 0;
+
+			// if we moved left would we find a new column (or are they all invisible?)
+			if(!TryGetNearestVisibleColumn (ColumnOffset-1, false, false, out _)) {
+				moreColumnsToLeft = false;
+			}
+
+			// are there visible columns to the right that have not yet been reached?
+			// lets find out, what is the column index of the last column we are rendering
+			int lastColumnIdxRendered = ColumnOffset + columnsToRender.Length - 1;
+			
+			// are there more valid indexes?
+			bool moreColumnsToRight = lastColumnIdxRendered < Table.Columns.Count;
+
+			// if we went right from the last column would we find a new visible column?
+			if(!TryGetNearestVisibleColumn (lastColumnIdxRendered + 1, true, false, out _)) {
+				// no we would not
+				moreColumnsToRight = false;
+			}
+
+			/*
+			 *  Now lets draw the line itself
+			 */
+
 			// Renders a line below the table headers (when visible) like:
 			// ├──────────┼───────────┼───────────────────┼──────────┼────────┼─────────────┤
 
@@ -436,7 +466,7 @@ namespace Terminal.Gui {
 				// whole way but update to instead draw a header indicator
 				// or scroll arrow etc
 				var rune = Driver.HLine;
-
+				
 				if (Style.ShowVerticalHeaderLines) {
 					if (c == 0) {
 						// for first character render line
@@ -445,7 +475,7 @@ namespace Terminal.Gui {
 						// unless we have horizontally scrolled along
 						// in which case render an arrow, to indicate user
 						// can scroll left
-						if(Style.ShowHorizontalScrollIndicators && ColumnOffset > 0)
+						if(Style.ShowHorizontalScrollIndicators && moreColumnsToLeft)
 						{
 							rune = Driver.LeftArrow;
 							scrollLeftPoint = new Point(c,row);
@@ -465,8 +495,7 @@ namespace Terminal.Gui {
 						// unless there is more of the table we could horizontally
 						// scroll along to see. In which case render an arrow,
 						// to indicate user can scroll right
-						if(Style.ShowHorizontalScrollIndicators &&
-							ColumnOffset + columnsToRender.Length < Table.Columns.Count)
+						if(Style.ShowHorizontalScrollIndicators && moreColumnsToRight)
 						{
 							rune = Driver.RightArrow;
 							scrollRightPoint = new Point(c,row);
@@ -683,7 +712,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool ProcessKey (KeyEvent keyEvent)
 		{
-			if (Table == null || Table.Columns.Count <= 0) {
+			if (TableIsNullOrInvisible ()) {
 				PositionCursor ();
 				return false;
 			}
@@ -705,6 +734,12 @@ namespace Terminal.Gui {
 		/// <param name="extendExistingSelection">True to create a multi cell selection or adjust an existing one</param>
 		public void SetSelection (int col, int row, bool extendExistingSelection)
 		{
+			// if we are trying to increase the column index then
+			// we are moving right otherwise we are moving left
+			bool lookRight = col > selectedColumn;
+
+			col = GetNearestVisibleColumn (col, lookRight, true);
+
 			if (!MultiSelect || !extendExistingSelection)
 				MultiSelectedRegions.Clear ();
 
@@ -804,7 +839,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void SelectAll ()
 		{
-			if (Table == null || !MultiSelect || Table.Rows.Count == 0)
+			if (TableIsNullOrInvisible() || !MultiSelect || Table.Rows.Count == 0)
 				return;
 
 			MultiSelectedRegions.Clear ();
@@ -820,7 +855,7 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public IEnumerable<Point> GetAllSelectedCells ()
 		{
-			if (Table == null || Table.Rows.Count == 0)
+			if (TableIsNullOrInvisible () || Table.Rows.Count == 0)
 				yield break;
 
 			EnsureValidSelection ();
@@ -880,13 +915,20 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Returns true if the given cell is selected either because it is the active cell or part of a multi cell selection (e.g. <see cref="FullRowSelect"/>)
+		/// <para>
+		/// Returns true if the given cell is selected either because it is the active cell or part of a multi cell selection (e.g. <see cref="FullRowSelect"/>).
+		/// </para>
+		/// <remarks>Returns <see langword="false"/> if <see cref="ColumnStyle.Visible"/> is <see langword="false"/>.</remarks>
 		/// </summary>
 		/// <param name="col"></param>
 		/// <param name="row"></param>
 		/// <returns></returns>
 		public bool IsSelected (int col, int row)
 		{
+			if(!IsColumnVisible(col)) {
+				return false;
+			}	
+
 			// Cell is also selected if in any multi selection region
 			if (MultiSelect && MultiSelectedRegions.Any (r => r.Rect.Contains (col, row)))
 				return true;
@@ -899,12 +941,28 @@ namespace Terminal.Gui {
 					(col == SelectedColumn || FullRowSelect);
 		}
 
+		/// <summary>
+		/// Returns true if the given <paramref name="columnIndex"/> indexes a visible
+		/// column otherwise false.  Returns false for indexes that are out of bounds.
+		/// </summary>
+		/// <param name="columnIndex"></param>
+		/// <returns></returns>
+		private bool IsColumnVisible (int columnIndex)
+		{
+			// if the column index provided is out of bounds
+			if (columnIndex < 0 || columnIndex >= table.Columns.Count) {
+				return false;
+			}
+
+			return this.Style.GetColumnStyleIfAny (Table.Columns [columnIndex])?.Visible ?? true;
+		}
+
 		/// <summary>
 		/// Positions the cursor in the area of the screen in which the start of the active cell is rendered.  Calls base implementation if active cell is not visible due to scrolling or table is loaded etc
 		/// </summary>
 		public override void PositionCursor ()
 		{
-			if (Table == null) {
+			if (TableIsNullOrInvisible ()) {
 				base.PositionCursor ();
 				return;
 			}
@@ -927,7 +985,7 @@ namespace Terminal.Gui {
 				SetFocus ();
 			}
 
-			if (Table == null || Table.Columns.Count <= 0) {
+			if (TableIsNullOrInvisible ()) {
 				return false;
 			}
 
@@ -1018,7 +1076,7 @@ namespace Terminal.Gui {
 		{
 			headerIfAny = null;
 
-			if (Table == null || Table.Columns.Count <= 0)
+			if (TableIsNullOrInvisible ())
 				return null;
 
 			var viewPort = CalculateViewport (Bounds);
@@ -1058,7 +1116,7 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public Point? CellToScreen (int tableColumn, int tableRow)
 		{
-			if (Table == null || Table.Columns.Count <= 0)
+			if (TableIsNullOrInvisible ())
 				return null;
 
 			var viewPort = CalculateViewport (Bounds);
@@ -1087,7 +1145,7 @@ namespace Terminal.Gui {
 		/// <remarks>This always calls <see cref="View.SetNeedsDisplay()"/></remarks>
 		public void Update ()
 		{
-			if (Table == null) {
+			if (TableIsNullOrInvisible ()) {
 				SetNeedsDisplay ();
 				return;
 			}
@@ -1106,7 +1164,7 @@ namespace Terminal.Gui {
 		/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/></remarks>
 		public void EnsureValidScrollOffsets ()
 		{
-			if (Table == null) {
+			if (TableIsNullOrInvisible ()) {
 				return;
 			}
 
@@ -1121,7 +1179,7 @@ namespace Terminal.Gui {
 		/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/></remarks>
 		public void EnsureValidSelection ()
 		{
-			if (Table == null) {
+			if (TableIsNullOrInvisible()) {
 
 				// Table doesn't exist, we should probably clear those selections
 				MultiSelectedRegions.Clear ();
@@ -1131,6 +1189,9 @@ namespace Terminal.Gui {
 			SelectedColumn = Math.Max (Math.Min (SelectedColumn, Table.Columns.Count - 1), 0);
 			SelectedRow = Math.Max (Math.Min (SelectedRow, Table.Rows.Count - 1), 0);
 
+			// If SelectedColumn is invisible move it to a visible one
+			SelectedColumn = GetNearestVisibleColumn (SelectedColumn, lookRight: true, true);
+
 			var oldRegions = MultiSelectedRegions.ToArray ().Reverse ();
 
 			MultiSelectedRegions.Clear ();
@@ -1159,7 +1220,100 @@ namespace Terminal.Gui {
 
 				MultiSelectedRegions.Push (region);
 			}
+		}
+
+		/// <summary>
+		/// Returns true if the <see cref="Table"/> is not set or all the
+		/// <see cref="DataColumn"/> in the <see cref="Table"/> have an explicit
+		/// <see cref="ColumnStyle"/> that marks them <see cref="ColumnStyle.visible"/>
+		/// <see langword="false"/>.
+		/// </summary>
+		/// <returns></returns>
+		private bool TableIsNullOrInvisible ()
+		{
+			return Table == null ||
+				Table.Columns.Count <= 0 ||
+				Table.Columns.Cast<DataColumn> ().All (
+				c => (Style.GetColumnStyleIfAny (c)?.Visible ?? true) == false);
+		}
+
+		/// <summary>
+		/// Returns <paramref name="columnIndex"/> unless the <see cref="ColumnStyle.Visible"/> is false for
+		/// the indexed <see cref="DataColumn"/>.  If so then the index returned is nudged to the nearest visible
+		/// column.
+		/// </summary>
+		/// <remarks>Returns <paramref name="columnIndex"/> unchanged if it is invalid (e.g. out of bounds).</remarks>
+		/// <param name="columnIndex">The input column index.</param>
+		/// <param name="lookRight">When nudging invisible selections look right first.
+		/// <see langword="true"/> to look right, <see langword="false"/> to look left.</param>
+		/// <param name="allowBumpingInOppositeDirection">If we cannot find anything visible when
+		/// looking in direction of <paramref name="lookRight"/> then should we look in the opposite
+		/// direction instead? Use true if you want to push a selection to a valid index no matter what.
+		/// Use false if you are primarily interested in learning about directional column visibility.</param>
+		private int GetNearestVisibleColumn (int columnIndex, bool lookRight, bool allowBumpingInOppositeDirection)
+		{
+			if(TryGetNearestVisibleColumn(columnIndex,lookRight,allowBumpingInOppositeDirection, out var answer))
+			{
+				return answer;
+			}
+
+			return columnIndex;
+		}
+
+		private bool TryGetNearestVisibleColumn (int columnIndex, bool lookRight, bool allowBumpingInOppositeDirection, out int idx)
+		{
+			// if the column index provided is out of bounds
+			if (columnIndex < 0 || columnIndex >= table.Columns.Count) {
+
+				idx = columnIndex;
+				return false;
+			}
+
+			// get the column visibility by index (if no style visible is true)
+			bool [] columnVisibility = Table.Columns.Cast<DataColumn> ()
+				.Select (c => this.Style.GetColumnStyleIfAny (c)?.Visible ?? true)
+				.ToArray();
+
+			// column is visible
+			if (columnVisibility [columnIndex]) {
+				idx = columnIndex;
+				return true;
+			}
+
+			int increment = lookRight ? 1 : -1;
+
+			// move in that direction
+			for (int i = columnIndex; i >=0 && i < columnVisibility.Length; i += increment) {
+				// if we find a visible column
+				if(columnVisibility [i]) 
+				{
+					idx = i;
+					return true;
+				}
+			}
+
+			// Caller only wants to look in one direction and we did not find any
+			// visible columns in that direction
+			if(!allowBumpingInOppositeDirection) {
+				idx = columnIndex;
+				return false;
+			}
+
+			// Caller will let us look in the other direction so
+			// now look other way
+			increment = -increment;
+
+			for (int i = columnIndex; i >= 0 && i < columnVisibility.Length; i += increment) {
+				// if we find a visible column
+				if (columnVisibility [i]) {
+					idx = i;
+					return true;
+				}
+			}
 
+			// nothing seems to be visible so just return input index
+			idx = columnIndex;
+			return false;
 		}
 
 		/// <summary>
@@ -1239,7 +1393,7 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		private IEnumerable<ColumnToRender> CalculateViewport (Rect bounds, int padding = 1)
 		{
-			if (Table == null || Table.Columns.Count <= 0)
+			if (TableIsNullOrInvisible ())
 				yield break;
 
 			int usedSpace = 0;
@@ -1264,6 +1418,12 @@ namespace Terminal.Gui {
 				var colStyle = Style.GetColumnStyleIfAny (col);
 				int colWidth;
 
+				// if column is not being rendered
+				if(colStyle?.Visible == false) {
+					// do not add it to the returned columns
+					continue;
+				}
+
 				// is there enough space for this column (and it's data)?
 				colWidth = CalculateMaxCellWidth (col, rowsToRender, colStyle) + padding;
 
@@ -1311,7 +1471,7 @@ namespace Terminal.Gui {
 
 		private bool ShouldRenderHeaders ()
 		{
-			if (Table == null || Table.Columns.Count == 0)
+			if (TableIsNullOrInvisible ())
 				return false;
 
 			return Style.AlwaysShowHeaders || rowOffset == 0;
@@ -1419,6 +1579,7 @@ namespace Terminal.Gui {
 			/// Return null for the default
 			/// </summary>
 			public CellColorGetterDelegate ColorGetter;
+			private bool visible = true;
 
 			/// <summary>
 			/// Defines the format for values e.g. "yyyy-MM-dd" for dates
@@ -1449,6 +1610,15 @@ namespace Terminal.Gui {
 			/// </summary>
 			public int MinAcceptableWidth { get; set; } = DefaultMinAcceptableWidth;
 
+			/// <summary>
+			/// Gets or Sets a value indicating whether the column should be visible to the user.
+			/// This affects both whether it is rendered and whether it can be selected. Defaults to
+			/// true.
+			/// </summary>
+			/// <remarks>If <see cref="MaxWidth"/> is 0 then <see cref="Visible"/> will always return false.</remarks>
+			public bool Visible { get => MaxWidth >= 0 && visible; set => visible = value; }
+
+
 			/// <summary>
 			/// Returns the alignment for the cell based on <paramref name="cellValue"/> and <see cref="AlignmentGetter"/>/<see cref="Alignment"/>
 			/// </summary>

+ 29 - 11
UICatalog/Scenarios/CsvEditor.cs

@@ -9,6 +9,7 @@ using System.IO;
 using System.Text;
 using NStack;
 using System.Text.RegularExpressions;
+using CsvHelper;
 
 namespace UICatalog.Scenarios {
 
@@ -371,16 +372,23 @@ namespace UICatalog.Scenarios {
 				MessageBox.ErrorQuery ("No file loaded", "No file is currently loaded", "Ok");
 				return;
 			}
+			using var writer = new CsvWriter (
+				new StreamWriter (File.OpenWrite (currentFile)),
+				CultureInfo.InvariantCulture);
 
-			var sb = new StringBuilder ();
+			foreach (var col in tableView.Table.Columns.Cast<DataColumn> ().Select (c => c.ColumnName)) {
+				writer.WriteField (col);
+			}
 
-			sb.AppendLine (string.Join (",", tableView.Table.Columns.Cast<DataColumn> ().Select (c => c.ColumnName)));
+			writer.NextRecord ();
 
 			foreach (DataRow row in tableView.Table.Rows) {
-				sb.AppendLine (string.Join (",", row.ItemArray));
+				foreach (var item in row.ItemArray) {
+					writer.WriteField (item);
+				}
+				writer.NextRecord ();
 			}
 
-			File.WriteAllText (currentFile, sb.ToString ());
 		}
 
 		private void Open ()
@@ -402,24 +410,34 @@ namespace UICatalog.Scenarios {
 			int lineNumber = 0;
 			currentFile = null;
 
+			using var reader = new CsvReader (File.OpenText (filename), CultureInfo.InvariantCulture);
+
 			try {
 				var dt = new DataTable ();
-				var lines = File.ReadAllLines (filename);
 
-				foreach (var h in lines [0].Split (',')) {
-					dt.Columns.Add (h);
-				}
+				reader.Read ();
 
+				if (reader.ReadHeader ()) {
+					foreach (var h in reader.HeaderRecord) {
+						dt.Columns.Add (h);
+					}
+				}
 
-				foreach (var line in lines.Skip (1)) {
+				while (reader.Read ()) {
 					lineNumber++;
-					dt.Rows.Add (line.Split (','));
+
+					var newRow = dt.Rows.Add ();
+					for (int i = 0; i < dt.Columns.Count; i++) {
+						newRow [i] = reader [i];
+					}
 				}
 
 				tableView.Table = dt;
 
-				// Only set the current filename if we succesfully loaded the entire file
+				// Only set the current filename if we successfully loaded the entire file
 				currentFile = filename;
+				Win.Title = $"{this.GetName ()} - {Path.GetFileName(currentFile)}";
+
 			} catch (Exception ex) {
 				MessageBox.ErrorQuery ("Open Failed", $"Error on line {lineNumber}{Environment.NewLine}{ex.Message}", "Ok");
 			}

+ 1 - 1
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -143,7 +143,7 @@ namespace UICatalog.Scenarios {
 					TextAlignment = TextAlignment.Centered,
 					X = Pos.Right (_btnPrevious) + 1,
 					Y = Pos.Top (_btnPrevious),
-					Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
+					Width = Dim.Fill () - Dim.Function (() => _btnAdd.Frame.Width + 1),
 					Height = 1
 				};
 				_frmMenu.Add (_lblMenuBar);

+ 3 - 3
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -38,19 +38,19 @@ namespace UICatalog.Scenarios {
 
 			_label = new Label () {
 				X = Pos.Center (),
-				Y = 0,
+				Y = 1,
 				ColorScheme = new ColorScheme () {
 					Normal = Colors.Base.Focus
 				}
 			};
 			_text = new TextField () {
 				X = Pos.Center (),
-				Y = 2,
+				Y = 3,
 				Width = 20
 			};
 			_button = new Button () {
 				X = Pos.Center (),
-				Y = 4
+				Y = 5
 			};
 			_labelR = new Label () {
 				X = Pos.AnchorEnd (30),

+ 90 - 30
UICatalog/Scenarios/TableEditor.cs

@@ -70,6 +70,7 @@ namespace UICatalog.Scenarios {
 					miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked},
 					miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked},
 					new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()),
+					new MenuItem ("Sho_w All Columns", "", ()=>ShowAllColumns())
 				}),
 				new MenuBarItem ("_Column", new MenuItem [] {
 					new MenuItem ("_Set Max Width", "", SetMaxWidth),
@@ -137,45 +138,104 @@ namespace UICatalog.Scenarios {
 				tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out DataColumn clickedCol);
 
 				if (clickedCol != null) {
+					if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+						
+						// left click in a header
+						SortColumn (clickedCol);
+					} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-					// work out new sort order
-					var sort = tableView.Table.DefaultView.Sort;
-					bool isAsc;
-
-					if(sort?.EndsWith("ASC") ?? false) {
-						sort = $"{clickedCol.ColumnName} DESC";
-						isAsc = false;
-					} else {
-						sort = $"{clickedCol.ColumnName} ASC";
-						isAsc = true;
-					}
-					
-					// set a sort order
-					tableView.Table.DefaultView.Sort = sort;
-					
-					// copy the rows from the view
-					var sortedCopy = tableView.Table.DefaultView.ToTable ();
-					tableView.Table.Rows.Clear ();
-					foreach(DataRow r in sortedCopy.Rows) {
-						tableView.Table.ImportRow (r);
+						// right click in a header
+						ShowHeaderContextMenu (clickedCol, e);
 					}
+				}
+			};
+		}
 
-					foreach(DataColumn col in tableView.Table.Columns) {
+		private void ShowAllColumns ()
+		{
+			foreach(var colStyle in tableView.Style.ColumnStyles) {
+				colStyle.Value.Visible = true;
+			}
+			tableView.Update ();
+		}
 
-						// remove any lingering sort indicator
-						col.ColumnName = col.ColumnName.TrimEnd ('▼', '▲');
+		private void SortColumn (DataColumn clickedCol)
+		{
+			var sort = GetProposedNewSortOrder (clickedCol, out var isAsc);
 
-						// add a new one if this the one that is being sorted
-						if (col == clickedCol) {
-							col.ColumnName += isAsc ? '▲': '▼';
-						}
-					}
+			SortColumn (clickedCol, sort, isAsc);
+		}
+
+		private void SortColumn (DataColumn clickedCol, string sort, bool isAsc)
+		{
+			// set a sort order
+			tableView.Table.DefaultView.Sort = sort;
+
+			// copy the rows from the view
+			var sortedCopy = tableView.Table.DefaultView.ToTable ();
+			tableView.Table.Rows.Clear ();
+			foreach (DataRow r in sortedCopy.Rows) {
+				tableView.Table.ImportRow (r);
+			}
+
+			foreach (DataColumn col in tableView.Table.Columns) {
+
+				// remove any lingering sort indicator
+				col.ColumnName = TrimArrows(col.ColumnName);
 
-					tableView.Update ();
+				// add a new one if this the one that is being sorted
+				if (col == clickedCol) {
+					col.ColumnName += isAsc ? '▲' : '▼';
 				}
-			};
+			}
+
+			tableView.Update ();
+		}
+
+		private string TrimArrows (string columnName)
+		{
+			return columnName.TrimEnd ('▼', '▲');
+		}
+		private string StripArrows (string columnName)
+		{
+			return columnName.Replace ("▼", "").Replace ("▲", "");
+		}
+		private string GetProposedNewSortOrder (DataColumn clickedCol, out bool isAsc)
+		{
+			// work out new sort order
+			var sort = tableView.Table.DefaultView.Sort;
+
+			if (sort?.EndsWith ("ASC") ?? false) {
+				sort = $"{clickedCol.ColumnName} DESC";
+				isAsc = false;
+			} else {
+				sort = $"{clickedCol.ColumnName} ASC";
+				isAsc = true;
+			}
+
+			return sort;
+		}
+
+		private void ShowHeaderContextMenu (DataColumn clickedCol, View.MouseEventArgs e)
+		{
+			var sort = GetProposedNewSortOrder (clickedCol, out var isAsc);
+
+			var contextMenu = new ContextMenu (e.MouseEvent.X + 1, e.MouseEvent.Y + 1,
+				new MenuBarItem (new MenuItem [] {
+					new MenuItem ($"Hide {TrimArrows(clickedCol.ColumnName)}", "", () => HideColumn(clickedCol)),
+					new MenuItem ($"Sort {StripArrows(sort)}","",()=>SortColumn(clickedCol,sort,isAsc)),
+				})
+			);
+
+			contextMenu.Show ();
 		}
 
+		private void HideColumn (DataColumn clickedCol)
+		{
+			var style = tableView.Style.GetOrCreateColumnStyle (clickedCol);
+			style.Visible = false;
+			tableView.Update ();
+		}
 
 		private DataColumn GetColumn ()
 		{

+ 1 - 1
UICatalog/Scenarios/Unicode.cs

@@ -97,7 +97,7 @@ namespace UICatalog.Scenarios {
 
 			label = new Label ("RadioGroup:") { X = Pos.X (label), Y = Pos.Bottom (listView) + 1 };
 			Win.Add (label);
-			var radioGroup = new RadioGroup (new ustring [] { "item #1", gitString, "Со_хранить" }, selected: 0) {
+			var radioGroup = new RadioGroup (new ustring [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" }, selected: 0) {
 				X = 20,
 				Y = Pos.Y (label),
 				Width = Dim.Percent (60),

+ 2 - 2
UICatalog/UICatalog.cs

@@ -150,12 +150,12 @@ namespace UICatalog {
 		class UICatalogTopLevel : Toplevel {
 			public MenuItem miIsMouseDisabled;
 			public MenuItem miHeightAsBuffer;
-	    
+
 			public FrameView LeftPane;
 			public ListView CategoryListView;
 			public FrameView RightPane;
 			public ListView ScenarioListView;
-	    
+
 			public StatusItem Capslock;
 			public StatusItem Numlock;
 			public StatusItem Scrolllock;

+ 3 - 0
UICatalog/UICatalog.csproj

@@ -18,6 +18,9 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="CsvHelper" Version="30.0.0" />
+  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>

+ 19 - 1
UnitTests/MenuTests.cs

@@ -1498,7 +1498,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen(0), output);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 			// Right - Edit has no sub menu; this tests that no sub menu shows
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -1636,5 +1636,23 @@ Edit
 00000000000000
 00000000000000", attributes);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void MenuBar_With_Action_But_Without_MenuItems_Not_Throw ()
+		{
+			var menu = new MenuBar (
+			    menus: new []
+			    {
+				new MenuBarItem { Title = "Test 1", Action = () => { } },
+				new MenuBarItem { Title = "Test 2", Action = () => { } },
+			    });
+
+			Application.Top.Add (menu);
+			Application.Begin (Application.Top);
+
+			Assert.False (Application.Top.OnKeyDown (new KeyEvent (Key.AltMask, new KeyModifiers { Alt = true })));
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+		}
 	}
 }

+ 3 - 4
UnitTests/StatusBarTests.cs

@@ -31,11 +31,10 @@ namespace Terminal.Gui.Views {
 			Assert.False (sb.CanFocus);
 			Assert.Equal (Colors.Menu, sb.ColorScheme);
 			Assert.Equal (0, sb.X);
+			Assert.Equal ("Pos.AnchorEnd(margin=1)", sb.Y.ToString ());
 			Assert.Equal (Dim.Fill (), sb.Width);
 			Assert.Equal (1, sb.Height);
 
-			Assert.Null (sb.Y);
-
 			var driver = new FakeDriver ();
 			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
@@ -47,11 +46,11 @@ namespace Terminal.Gui.Views {
 			Assert.True (FakeConsole.CursorVisible);
 
 			Application.Iteration += () => {
-				Assert.Equal (24, sb.Y);
+				Assert.Equal (24, sb.Frame.Y);
 
 				driver.SetWindowSize (driver.Cols, 15);
 
-				Assert.Equal (14, sb.Y);
+				Assert.Equal (14, sb.Frame.Y);
 
 				sb.OnEnter (null);
 				driver.GetCursorVisibility (out cv);

+ 325 - 0
UnitTests/TableViewTests.cs

@@ -1098,6 +1098,331 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
+		private TableView GetABCDEFTableView (out DataTable dt)
+		{
+			var tableView = new TableView ();
+			tableView.ColorScheme = Colors.TopLevel;
+
+			// 3 columns are visible
+			tableView.Bounds = new Rect (0, 0, 7, 5);
+			tableView.Style.ShowHorizontalHeaderUnderline = false;
+			tableView.Style.ShowHorizontalHeaderOverline = false;
+			tableView.Style.AlwaysShowHeaders = true;
+			tableView.Style.SmoothHorizontalScrolling = false;
+
+			dt = new DataTable ();
+			dt.Columns.Add ("A");
+			dt.Columns.Add ("B");
+			dt.Columns.Add ("C");
+			dt.Columns.Add ("D");
+			dt.Columns.Add ("E");
+			dt.Columns.Add ("F");
+
+
+			dt.Rows.Add (1, 2, 3, 4, 5, 6);
+			tableView.Table = dt;
+
+			return tableView;
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_VisibleFalse_IsNotRendered()
+		{
+			var tableView = GetABCDEFTableView (out DataTable dt);
+
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
+
+			tableView.Redraw (tableView.Bounds);
+
+			string expected =
+				@"
+│A│C│D│
+│1│3│4│";
+
+			TestHelpers.AssertDriverContentsAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_FirstColumnVisibleFalse_IsNotRendered ()
+		{
+			var tableView = GetABCDEFTableView (out DataTable dt);
+
+			tableView.Style.ShowHorizontalScrollIndicators = true;
+			tableView.Style.ShowHorizontalHeaderUnderline = true;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
+
+			tableView.Redraw (tableView.Bounds);
+
+			string expected =
+				@"
+│B│C│D│
+├─┼─┼─►
+│2│3│4│";
+
+			TestHelpers.AssertDriverContentsAre (expected, output);
+		}
+
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull ()
+		{
+			var tableView = GetABCDEFTableView (out DataTable dt);
+
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["C"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
+
+
+			// expect nothing to be rendered when all columns are invisible
+			string expected =
+				@"
+";
+
+			tableView.Redraw (tableView.Bounds);
+			TestHelpers.AssertDriverContentsAre (expected, output);
+
+
+			// expect behavior to match when Table is null
+			tableView.Table = null;
+
+			tableView.Redraw (tableView.Bounds);
+			TestHelpers.AssertDriverContentsAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_RemainingColumnsInvisible_NoScrollIndicator ()
+		{
+			var tableView = GetABCDEFTableView (out DataTable dt);
+
+			tableView.Style.ShowHorizontalScrollIndicators = true;
+			tableView.Style.ShowHorizontalHeaderUnderline = true;
+
+			tableView.Redraw (tableView.Bounds);
+
+			// normally we should have scroll indicators because DEF are of screen
+			string expected =
+				@"
+│A│B│C│
+├─┼─┼─►
+│1│2│3│";
+
+			TestHelpers.AssertDriverContentsAre (expected, output);
+
+			// but if DEF are invisible we shouldn't be showing the indicator
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
+
+			expected =
+			       @"
+│A│B│C│
+├─┼─┼─┤
+│1│2│3│";
+			tableView.Redraw (tableView.Bounds);
+			TestHelpers.AssertDriverContentsAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_PreceedingColumnsInvisible_NoScrollIndicator ()
+		{
+			var tableView = GetABCDEFTableView (out DataTable dt);
+
+			tableView.Style.ShowHorizontalScrollIndicators = true;
+			tableView.Style.ShowHorizontalHeaderUnderline = true;
+
+			tableView.ColumnOffset = 1;
+			tableView.Redraw (tableView.Bounds);
+
+			// normally we should have scroll indicators because A,E and F are of screen
+			string expected =
+				@"
+│B│C│D│
+◄─┼─┼─►
+│2│3│4│";
+
+			TestHelpers.AssertDriverContentsAre (expected, output);
+
+			// but if E and F are invisible so we shouldn't show right
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
+
+			expected =
+			       @"
+│B│C│D│
+◄─┼─┼─┤
+│2│3│4│";
+			tableView.Redraw (tableView.Bounds);
+			TestHelpers.AssertDriverContentsAre (expected, output);
+
+			// now also A is invisible so we cannot scroll in either direction
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
+
+			expected =
+			       @"
+│B│C│D│
+├─┼─┼─┤
+│2│3│4│";
+			tableView.Redraw (tableView.Bounds);
+			TestHelpers.AssertDriverContentsAre (expected, output);
+		}
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_VisibleFalse_CursorStepsOverInvisibleColumns ()
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+			
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
+			tableView.SelectedColumn = 0;
+
+			tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
+
+			// Expect the cursor navigation to skip over the invisible column(s)
+			Assert.Equal(2,tableView.SelectedColumn);
+
+			tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
+
+			// Expect the cursor navigation backwards to skip over invisible column too
+			Assert.Equal (0, tableView.SelectedColumn);
+		}
+
+		[InlineData(true)]
+		[InlineData (false)]
+		[Theory, AutoInitShutdown]
+		public void TestColumnStyle_FirstColumnVisibleFalse_CursorStaysAt1(bool useHome)
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
+			tableView.SelectedColumn = 0;
+
+			Assert.Equal (0, tableView.SelectedColumn);
+
+			// column 0 is invisible so this method should move to 1
+			tableView.EnsureValidSelection();
+			Assert.Equal (1, tableView.SelectedColumn);
+
+			tableView.ProcessKey (new KeyEvent 
+			{
+				Key = useHome ? Key.Home : Key.CursorLeft 
+			});
+
+			// Expect the cursor to stay at 1
+			Assert.Equal (1, tableView.SelectedColumn);
+		}
+
+		[InlineData (true)]
+		[InlineData (false)]
+		[Theory, AutoInitShutdown]
+		public void TestColumnStyle_LastColumnVisibleFalse_CursorStaysAt2 (bool useEnd)
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+						
+			// select D 
+			tableView.SelectedColumn = 3;
+			Assert.Equal (3, tableView.SelectedColumn);
+
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
+
+			// column D is invisible so this method should move to 2 (C)
+			tableView.EnsureValidSelection ();
+			Assert.Equal (2, tableView.SelectedColumn);
+
+			tableView.ProcessKey (new KeyEvent {
+				Key = useEnd ? Key.End : Key.CursorRight
+			});
+
+			// Expect the cursor to stay at 2
+			Assert.Equal (2, tableView.SelectedColumn);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_VisibleFalse_MultiSelected ()
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+
+			// user has rectangular selection 
+			tableView.MultiSelectedRegions.Push (
+				new TableView.TableSelection(
+					new Point(0,0),
+					new Rect(0, 0, 3, 1))
+				);
+
+			Assert.Equal (3, tableView.GetAllSelectedCells ().Count());
+			Assert.True (tableView.IsSelected (0, 0));
+			Assert.True (tableView.IsSelected (1, 0));
+			Assert.True (tableView.IsSelected (2, 0));
+			Assert.False (tableView.IsSelected (3, 0));
+
+			// if middle column is invisible
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
+
+			// it should not be included in the selection
+			Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
+			Assert.True (tableView.IsSelected (0, 0));
+			Assert.False (tableView.IsSelected (1, 0));
+			Assert.True (tableView.IsSelected (2, 0));
+			Assert.False (tableView.IsSelected (3, 0));
+
+			Assert.DoesNotContain(new Point(1,0),tableView.GetAllSelectedCells ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestColumnStyle_VisibleFalse_MultiSelectingStepsOverInvisibleColumns ()
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+
+			// if middle column is invisible
+			tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
+
+			tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight | Key.ShiftMask });
+
+			// Selection should extend from A to C but skip B
+			Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
+			Assert.True (tableView.IsSelected (0, 0));
+			Assert.False (tableView.IsSelected (1, 0));
+			Assert.True (tableView.IsSelected (2, 0));
+			Assert.False (tableView.IsSelected (3, 0));
+
+			Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
+		}
+		
+		[Theory, AutoInitShutdown]
+		[InlineData(new object[] { true,true })]
+		[InlineData (new object[] { false,true })]
+		[InlineData (new object [] { true, false})]
+		[InlineData (new object [] { false, false})]
+		public void TestColumnStyle_VisibleFalse_DoesNotEffect_EnsureSelectedCellIsVisible (bool smooth, bool invisibleCol)
+		{
+			var tableView = GetABCDEFTableView (out var dt);
+			tableView.Style.SmoothHorizontalScrolling = smooth;
+			
+			if(invisibleCol) {
+				tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
+			}
+
+			// New TableView should have first cell selected 
+			Assert.Equal (0,tableView.SelectedColumn);
+			// With no scrolling
+			Assert.Equal (0, tableView.ColumnOffset);
+
+			// A,B and C are visible on screen at the moment so these should have no effect
+			tableView.SelectedColumn = 1;
+			tableView.EnsureSelectedCellIsVisible ();
+			Assert.Equal (0, tableView.ColumnOffset);
+
+			tableView.SelectedColumn = 2;
+			tableView.EnsureSelectedCellIsVisible ();
+			Assert.Equal (0, tableView.ColumnOffset);
+
+			// Selecting D should move the visible table area to fit D onto the screen
+			tableView.SelectedColumn = 3;
+			tableView.EnsureSelectedCellIsVisible ();
+			Assert.Equal (smooth ? 1 : 3, tableView.ColumnOffset);
+		}
 		[Fact]
 		public void LongColumnTest ()
 		{

+ 143 - 1
UnitTests/ToplevelTests.cs

@@ -671,7 +671,7 @@ namespace Terminal.Gui.Core {
 		}
 
 		[Fact, AutoInitShutdown]
-		public void Mouse_Drag ()
+		public void Mouse_Drag_On_Top_With_Superview_Null ()
 		{
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem("File", new MenuItem [] {
@@ -825,5 +825,147 @@ namespace Terminal.Gui.Core {
 
 			Application.Run ();
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Mouse_Drag_On_Top_With_Superview_Not_Null ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem("File", new MenuItem [] {
+					new MenuItem("New", "", null)
+				})
+			});
+
+			var sbar = new StatusBar (new StatusItem [] {
+				new StatusItem(Key.N, "~CTRL-N~ New", null)
+			});
+
+			var win = new Window ("Window") {
+				X = 3,
+				Y = 2,
+				Width = Dim.Fill (10),
+				Height = Dim.Fill (5)
+			};
+			var top = Application.Top;
+			top.Add (menu, sbar, win);
+
+			var iterations = -1;
+
+			Application.Iteration = () => {
+				iterations++;
+				if (iterations == 0) {
+					((FakeDriver)Application.Driver).SetBufferSize (20, 10);
+
+					Assert.Null (Application.MouseGrabView);
+					// Grab the mouse
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 4,
+							Y = 2,
+							Flags = MouseFlags.Button1Pressed
+						});
+
+					Assert.Equal (win, Application.MouseGrabView);
+					Assert.Equal (new Rect (3, 2, 7, 3), Application.MouseGrabView.Frame);
+
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
+ File      
+           
+   ┌─────┐ 
+   │     │ 
+   └─────┘ 
+           
+           
+           
+           
+ CTRL-N New", output);
+
+
+				} else if (iterations == 1) {
+					Assert.Equal (win, Application.MouseGrabView);
+					// Grab to left
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 5,
+							Y = 2,
+							Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+						});
+
+					Assert.Equal (win, Application.MouseGrabView);
+
+				} else if (iterations == 2) {
+					Assert.Equal (win, Application.MouseGrabView);
+
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
+ File      
+           
+    ┌────┐ 
+    │    │ 
+    └────┘ 
+           
+           
+           
+           
+ CTRL-N New", output);
+
+					Assert.Equal (win, Application.MouseGrabView);
+					Assert.Equal (new Rect (4, 2, 6, 3), Application.MouseGrabView.Frame);
+
+				} else if (iterations == 3) {
+					Assert.Equal (win, Application.MouseGrabView);
+					// Grab to top
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 5,
+							Y = 1,
+							Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+						});
+
+					Assert.Equal (win, Application.MouseGrabView);
+
+				} else if (iterations == 4) {
+					Assert.Equal (win, Application.MouseGrabView);
+
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
+ File      
+    ┌────┐ 
+    │    │ 
+    │    │ 
+    └────┘ 
+           
+           
+           
+           
+ CTRL-N New", output);
+
+					Assert.Equal (win, Application.MouseGrabView);
+					Assert.Equal (new Rect (4, 1, 6, 4), Application.MouseGrabView.Frame);
+
+				} else if (iterations == 5) {
+					Assert.Equal (win, Application.MouseGrabView);
+					// Ungrab the mouse
+					ReflectionTools.InvokePrivate (
+						typeof (Application),
+						"ProcessMouseEvent",
+						new MouseEvent () {
+							X = 7,
+							Y = 4,
+							Flags = MouseFlags.Button1Released
+						});
+
+					Assert.Null (Application.MouseGrabView);
+
+				} else if (iterations == 8) {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
 	}
 }

+ 83 - 0
UnitTests/WindowTests.cs

@@ -153,5 +153,88 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (expected, r.Title.ToString ());
 			r.Dispose ();
 		}
+
+		[Fact,AutoInitShutdown]
+		public void MenuBar_And_StatusBar_Inside_Window ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("Open", "", null),
+					new MenuItem ("Quit", "", null),
+				}),
+				new MenuBarItem ("Edit", new MenuItem [] {
+					new MenuItem ("Copy", "", null),
+				})
+			});
+
+			var sb = new StatusBar (new StatusItem [] {
+				new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null),
+				new StatusItem (Key.CtrlMask | Key.O, "~^O~ Open", null),
+				new StatusItem (Key.CtrlMask | Key.C, "~^C~ Copy", null),
+			});
+
+			var fv = new FrameView ("Frame View") {
+				Y = 1,
+				Width = Dim.Fill(),
+				Height = Dim.Fill (1)
+			};
+			var win = new Window ();
+			win.Add (menu, sb, fv);
+			var top = Application.Top;
+			top.Add (win);
+			Application.Begin (top);
+			((FakeDriver)Application.Driver).SetBufferSize (20, 10);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+┌──────────────────┐
+│ File  Edit       │
+│┌ Frame View ────┐│
+││                ││
+││                ││
+││                ││
+││                ││
+│└────────────────┘│
+│ ^Q Quit │ ^O Open│
+└──────────────────┘", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (40, 20);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+┌──────────────────────────────────────┐
+│ File  Edit                           │
+│┌ Frame View ────────────────────────┐│
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+││                                    ││
+│└────────────────────────────────────┘│
+│ ^Q Quit │ ^O Open │ ^C Copy          │
+└──────────────────────────────────────┘", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (20, 10);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+┌──────────────────┐
+│ File  Edit       │
+│┌ Frame View ────┐│
+││                ││
+││                ││
+││                ││
+││                ││
+│└────────────────┘│
+│ ^Q Quit │ ^O Open│
+└──────────────────┘", output);
+
+		}
 	}
 }