diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 35479ed979..84db12bb00 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Terminal.Gui; @@ -9,6 +10,36 @@ namespace Terminal.Gui; /// public class TableStyle { + /// + /// Gets or sets the LineStyle for the borders surrounding header rows of a . + /// Defaults to . + /// + public LineStyle OuterHeaderBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the vertical lines separating header items in a . + /// Defaults to . + /// + public LineStyle InnerHeaderBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the borders surrounding the regular (non-header) portion of a . + /// Defaults to . + /// + public LineStyle OuterBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the lines separating regular (non-header) items in a . + /// Defaults to . + /// + public LineStyle InnerBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the color Attribute of the inner and outer borders of a . + /// Defaults to Attribute(-1, -1) which results in . + /// + public Attribute BorderColor { get; set; } = new Attribute(-1, -1); + /// /// Gets or sets a flag indicating whether to render headers of a . /// Defaults to . @@ -32,6 +63,11 @@ public class TableStyle { /// public bool ShowHorizontalHeaderUnderline { get; set; } = true; + /// + /// True to render a solid line through the headers (only when Overline and/or Underline are ) + /// + public bool ShowHorizontalHeaderThroughline { get; set; } = false; + /// /// True to render a solid line vertical line between cells /// @@ -57,6 +93,13 @@ public class TableStyle { /// public bool ShowHorizontalBottomline { get; set; } = false; + /// + /// True to invert the colors of the entire selected cell in the . + /// Helpful for when is on, especially when the doesn't show + /// the cursor + /// + public bool InvertSelectedCell { get; set; } = false; + /// /// True to invert the colors of the first symbol of the selected cell in the . /// This gives the appearance of a cursor for when the doesn't otherwise show @@ -64,11 +107,48 @@ public class TableStyle { /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; + // NOTE: This is equivalent to True by default after change to LineCanvas borders and can't be turned off + // without disabling ShowVerticalCellLines, however SeparatorSymbol and HeaderSeparatorSymbol could be + // used to approximate the previous default behavior with FullRowSelect + // TODO: Explore ways of changing this without a workaround /// /// Gets or sets a flag indicating whether to force use when rendering /// vertical cell lines (even when is on). /// - public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; + //public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; + + /// + /// The symbol to add after each header value to visually seperate values (if not using vertical gridlines) + /// CM.Glyphs.VLine can be used to emulate vertical grindlines that highlight with + /// + public Rune HeaderSeparatorSymbol { get; set; } = (Rune)' '; + + /// + /// The symbol to add after each cell value to visually seperate values (if not using vertical gridlines) + /// CM.Glyphs.VLine can be used to emulate vertical grindlines that highlight with + /// + public Rune SeparatorSymbol { get; set; } = (Rune)' '; + + /// + /// The text representation that should be rendered for cells with the value + /// + public string NullSymbol { get; set; } = "-"; + + /// + /// The symbol to pad around values (between separators) in the header line + /// + public char HeaderPaddingSymbol { get; set; } = ' '; + + /// + /// The symbol to pad around values (between separators) + /// + public char CellPaddingSymbol { get; set; } = ' '; + + /// + /// The symbol to pad outside table (if both and + /// are False) + /// + public Rune BackgroundSymbol { get; set; } = (Rune)' '; /// /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) @@ -82,15 +162,25 @@ public class TableStyle { public RowColorGetterDelegate RowColorGetter { get; set; } /// - /// Determines rendering when the last column in the table is visible but it's + /// Determines rendering when the last column in the table is visible but its /// content or is less than the remaining /// space in the control. True (the default) will expand the column to fill - /// the remaining bounds of the control. False will draw a column ending line - /// and leave a blank column that cannot be selected in the remaining space. + /// the remaining bounds of the control. If false, + /// determines the behavior of the remaining space. /// /// public bool ExpandLastColumn { get; set; } = true; + /// + /// Determines rendering when the last column in the table is visible but its + /// content or is less than the remaining + /// space in the control *and* is False. True (the default) + /// will add a blank column that cannot be selected in the remaining space. + /// False will fill the remaining space with . + /// + /// + public bool AddEmptyColumn { get; set; } = true; + /// /// /// Determines how is updated when scrolling diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index c04d1252bd..c7364f9fb4 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -28,6 +28,7 @@ namespace Terminal.Gui { /// public class TableView : View { + private LineCanvas grid = new LineCanvas (); private int columnOffset; private int rowOffset; private int selectedRow; @@ -145,16 +146,6 @@ public int SelectedRow { /// public int MaxCellWidth { get; set; } = DefaultMaxCellWidth; - /// - /// The text representation that should be rendered for cells with the value - /// - public string NullSymbol { get; set; } = "-"; - - /// - /// The symbol to add after each cell value and header value to visually seperate values (if not using vertical gridlines) - /// - public char SeparatorSymbol { get; set; } = ' '; - /// /// This event is raised when the selected cell in the table changes. /// @@ -273,9 +264,7 @@ public override void OnDrawContent (Rect contentArea) base.OnDrawContent (contentArea); Move (0, 0); - - scrollRightPoint = null; - scrollLeftPoint = null; + var frame = Frame; // What columns to render at what X offset in viewport var columnsToRender = CalculateViewport (Bounds).ToArray (); @@ -285,40 +274,124 @@ public override void OnDrawContent (Rect contentArea) //invalidate current row (prevents scrolling around leaving old characters in the frame Driver.AddStr (new string (' ', Bounds.Width)); - int line = 0; + if (Table == null || columnsToRender.Length < 1) { + return; + } - if (ShouldRenderHeaders ()) { - // Render something like: - /* - ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ - │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ - └────────────────────┴──────────┴───────────┴──────────────┴─────────┘ - */ - if (Style.ShowHorizontalHeaderOverline) { - RenderHeaderOverline (line, Bounds.Width, columnsToRender); - line++; - } + var lastCol = columnsToRender [columnsToRender.Length - 1]; + var width = Bounds.Width; + if (!Style.ExpandLastColumn) { + width = lastCol.X + lastCol.Width; + } - if (Style.ShowHeaders) { - RenderHeaderMidline (line, columnsToRender); - line++; + // render the cell lines + grid.Clear (); + Color fg; + Color bg; + if (Style.BorderColor.Foreground == -1) { + fg = this.Border.ColorScheme.Normal.Foreground; + } else { + fg = Style.BorderColor.Foreground; + } + if (Style.BorderColor.Background == -1) { + bg = this.Border.ColorScheme.Normal.Background; + } else { + bg = Style.BorderColor.Background; + } + Driver.SetAttribute (new Attribute(fg, bg)); + + var lineWidth = width; + + if (!Style.ExpandLastColumn && Style.AddEmptyColumn) { + lineWidth = contentArea.Width; + } + RenderCellLines (lineWidth, Table.Rows, columnsToRender); + + foreach (var p in grid.GetMap (Bounds)) { + this.AddRune (p.Key.X, p.Key.Y, p.Value); + } + + int hh = GetHeaderHeightIfAny (); + + // render arrows + if (Style.ShowHorizontalScrollIndicators) { + if (hh > 0 && MoreColumnsToLeft ()) { + scrollLeftPoint = new Point (0, hh); + AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, CM.Glyphs.LeftArrow); } + if (hh > 0 && MoreColumnsToRight (columnsToRender)) { + scrollRightPoint = new Point (lineWidth - 1, hh); + AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, CM.Glyphs.RightArrow); + } + } + + // render the header contents + if (Style.ShowHeaders && hh > 0) { + var padChar = Style.HeaderPaddingSymbol; + var yh = hh - 1; if (Style.ShowHorizontalHeaderUnderline) { - RenderHeaderUnderline (line, Bounds.Width, columnsToRender); - line++; + yh--; } - } - int headerLinesConsumed = line; + for (var i = 0; i < columnsToRender.Length; i++) { + + var current = columnsToRender [i]; - //render the cells - for (; line < Bounds.Height; line++) { + var colStyle = Style.GetColumnStyleIfAny (current.Column); + var colName = table.ColumnNames [current.Column]; - ClearLine (line, Bounds.Width); + if (!Style.ShowVerticalHeaderLines && current.X > 0) { + AddRune (current.X - 1, yh, (Rune)Style.HeaderSeparatorSymbol); + } + + Move (current.X, yh); + + if (current.Width > colName.Length && Style.ShowHorizontalHeaderThroughline) { + + if (colName.Sum (c => ((Rune)c).GetColumns ()) < current.Width) { + Driver.AddStr (colName); + } else { + Driver.AddStr (new string (colName.TakeWhile (h => (current.Width -= ((Rune)h).GetColumns ()) > 0).ToArray ())); + } + } else { + Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); + } + + if (!Style.ShowVerticalHeaderLines + && current.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && current.X + current.Width - 1 <= lineWidth) { + AddRune (current.X + current.Width - 1, yh, (Rune)Style.HeaderSeparatorSymbol); + } + + if (!Style.ExpandLastColumn) { + if (!Style.AddEmptyColumn) { + if (i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, yh, (Rune)Style.BackgroundSymbol); + if (Style.ShowHorizontalHeaderOverline) { + AddRune (j, yh - 1, (Rune)Style.BackgroundSymbol); + } + if (Style.ShowHorizontalHeaderUnderline) { + AddRune (j, yh + 1, (Rune)Style.BackgroundSymbol); + } + } + } + } else if (!Style.ShowVerticalHeaderLines) { + AddRune (Bounds.Width - 1, yh, (Rune)Style.HeaderSeparatorSymbol); + } + } + } + } + + // render the cell contents + for (var line = hh; line < frame.Height; line++) { + + var padChar = Style.CellPaddingSymbol; //work out what Row to render - var rowToRender = RowOffset + (line - headerLinesConsumed); + var rowToRender = RowOffset + (line - hh); //if we have run off the end of the table if (TableIsNullOrInvisible () || rowToRender < 0) @@ -326,30 +399,23 @@ public override void OnDrawContent (Rect contentArea) // No more data if (rowToRender >= Table.Rows) { - - if (rowToRender == Table.Rows && Style.ShowHorizontalBottomline) { - RenderBottomLine (line, Bounds.Width, columnsToRender); + if (rowToRender == Table.Rows + && Style.ShowHorizontalBottomline + && !Style.ExpandLastColumn + && !Style.AddEmptyColumn) { + var start = columnsToRender [^1]; + for (int i = start.X + start.Width; i < Bounds.Width; i++) { + AddRune (i, Table.Rows + hh, (Rune)Style.BackgroundSymbol); + } } continue; } - RenderRow (line, rowToRender, columnsToRender); + RenderRow (line, rowToRender, columnsToRender, padChar); } } - /// - /// Clears a line of the console by filling it with spaces - /// - /// - /// - private void ClearLine (int row, int width) - { - Move (0, row); - Driver.SetAttribute (GetNormalColor ()); - Driver.AddStr (new string (' ', width)); - } - /// /// Returns the amount of vertical space currently occupied by the header or 0 if it is not visible. /// @@ -376,86 +442,100 @@ internal int GetHeaderHeight () return heightRequired; } - private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [] columnsToRender) + private void RenderCellLines (int width, int height, ColumnToRender [] columnsToRender) { - // Renders a line above table headers (when visible) like: - // ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ - - for (int c = 0; c < availableWidth; c++) { - - var rune = CM.Glyphs.HLine; + var row = 0; + int hh = GetHeaderHeightIfAny (); - if (Style.ShowVerticalHeaderLines) { + // First render the header, something like: + /* + ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ + │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ + └────────────────────┴──────────┴───────────┴──────────────┴─────────┘ + */ - if (c == 0) { - rune = CM.Glyphs.ULCorner; - } - // if the next column is the start of a header - else if (columnsToRender.Any (r => r.X == c + 1)) { - rune = CM.Glyphs.TopTee; - } else if (c == availableWidth - 1) { - rune = CM.Glyphs.URCorner; - } - // if the next console column is the lastcolumns end - else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - rune = CM.Glyphs.TopTee; + if (hh > 0) { + if (Style.ShowHorizontalHeaderOverline) { + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderBorderStyle); + row++; + } + if (Style.ShowHeaders) { + if (Style.ShowHorizontalHeaderThroughline) { + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.InnerHeaderBorderStyle); } + row++; + } + if (Style.ShowHorizontalHeaderUnderline) { + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderBorderStyle); + row++; } - AddRuneAt (Driver, c, row, rune); - } - } - - private void RenderHeaderMidline (int row, ColumnToRender [] columnsToRender) - { - // Renders something like: - // │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ - - ClearLine (row, Bounds.Width); - - //render start of line - if (style.ShowVerticalHeaderLines) - AddRune (0, row, CM.Glyphs.VLine); - - for (int i = 0; i < columnsToRender.Length; i++) { - - var current = columnsToRender [i]; + if (row > 1 && Style.ShowVerticalHeaderLines && Style.InnerHeaderBorderStyle != LineStyle.None) { + foreach (var col in columnsToRender) { + var lineStyle = Style.InnerHeaderBorderStyle; + if (col.X - 1 == 0) { + lineStyle = Style.OuterHeaderBorderStyle; + } + grid.AddLine (new Point (col.X - 1, 0), row, Orientation.Vertical, lineStyle); - var colStyle = Style.GetColumnStyleIfAny (current.Column); - var colName = table.ColumnNames [current.Column]; + // left side of empty column + if (col.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && !Style.ExpandLastColumn + && Style.AddEmptyColumn) { + grid.AddLine (new Point (col.X + col.Width - 1, 0), row, Orientation.Vertical, lineStyle); + } + } - RenderSeparator (current.X - 1, row, true); + // right side + grid.AddLine (new Point (width - 1, 0), row, Orientation.Vertical, Style.OuterHeaderBorderStyle); + } + } - Move (current.X, row); + if (Style.ShowHorizontalBottomline) { + height++; + } - Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle)); + // render the vertical cell lines + if (Style.ShowVerticalCellLines) { + foreach (var col in columnsToRender) { + var lineStyle = Style.InnerBorderStyle; + if (col.X - 1 == 0) { + lineStyle = Style.OuterBorderStyle; + } + grid.AddLine (new Point (col.X - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, row, true); + // left side of empty column + if (col.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && !Style.ExpandLastColumn + && Style.AddEmptyColumn) { + grid.AddLine (new Point (col.X + col.Width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); + } } + grid.AddLine (new Point (width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, Style.OuterBorderStyle); } - //render end of line - if (style.ShowVerticalHeaderLines) - AddRune (Bounds.Width - 1, row, CM.Glyphs.VLine); + // render the bottom line + if (Style.ShowHorizontalBottomline) { + grid.AddLine (new Point (0, height - RowOffset + hh - 1), width, Orientation.Horizontal, Style.OuterBorderStyle); + } } - private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender [] columnsToRender) + private bool MoreColumnsToLeft () { - /* - * 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; + return false; } + return moreColumnsToLeft; + } + + private bool MoreColumnsToRight (ColumnToRender [] columnsToRender) + { // 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; @@ -466,102 +546,13 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender // 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: - // ├──────────┼───────────┼───────────────────┼──────────┼────────┼─────────────┤ - - for (int c = 0; c < availableWidth; c++) { - - // Start by assuming we just draw a straight line the - // whole way but update to instead draw a header indicator - // or scroll arrow etc - var rune = CM.Glyphs.HLine; - - if (Style.ShowVerticalHeaderLines) { - if (c == 0) { - // for first character render line - rune = Style.ShowVerticalCellLines ? CM.Glyphs.LeftTee : CM.Glyphs.LLCorner; - - // unless we have horizontally scrolled along - // in which case render an arrow, to indicate user - // can scroll left - if (Style.ShowHorizontalScrollIndicators && moreColumnsToLeft) { - rune = CM.Glyphs.LeftArrow; - scrollLeftPoint = new Point (c, row); - } - - } - // if the next column is the start of a header - else if (columnsToRender.Any (r => r.X == c + 1)) { - - /*TODO: is ┼ symbol in Driver?*/ - rune = Style.ShowVerticalCellLines ? CM.Glyphs.Cross : CM.Glyphs.BottomTee; - } else if (c == availableWidth - 1) { - - // for the last character in the table - rune = Style.ShowVerticalCellLines ? CM.Glyphs.RightTee : CM.Glyphs.LRCorner; - - // 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 && moreColumnsToRight) { - rune = CM.Glyphs.RightArrow; - scrollRightPoint = new Point (c, row); - } - - } - // if the next console column is the lastcolumns end - else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - rune = Style.ShowVerticalCellLines ? CM.Glyphs.Cross : CM.Glyphs.BottomTee; - } - } - - AddRuneAt (Driver, c, row, rune); + return false; } + return moreColumnsToRight; } - private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] columnsToRender) - { - // Renders a line at the bottom of the table after all the data like: - // └─────────────────────────────────┴──────────┴──────┴──────────┴────────┴────────────────────────────────────────────┘ - - for (int c = 0; c < availableWidth; c++) { - - // Start by assuming we just draw a straight line the - // whole way but update to instead draw BottomTee / Corner etc - var rune = CM.Glyphs.HLine; - - if (Style.ShowVerticalCellLines) { - if (c == 0) { - // for first character render line - rune = CM.Glyphs.LLCorner; - - } else if (columnsToRender.Any (r => r.X == c + 1)) { - // if the next column is the start of a header - rune = CM.Glyphs.BottomTee; - } else if (c == availableWidth - 1) { - // for the last character in the table - rune = CM.Glyphs.LRCorner; - - } else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - // if the next console column is the lastcolumns end - rune = CM.Glyphs.BottomTee; - } - } - - AddRuneAt (Driver, c, row, rune); - } - } - private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender) + private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender, char padChar) { var focused = HasFocus; @@ -579,9 +570,6 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen color = Enabled ? rowScheme.Normal : rowScheme.Disabled; } - Driver.SetAttribute (color); - Driver.AddStr (new string (' ', Bounds.Width)); - // Render cells for each visible header for the current row for (int i = 0; i < columnsToRender.Length; i++) { @@ -625,49 +613,53 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen cellColor = Enabled ? scheme.Normal : scheme.Disabled; } - var render = TruncateOrPad (val, representation, current.Width, colStyle); + var render = TruncateOrPad (val, representation, current.Width, colStyle, padChar); // While many cells can be selected (see MultiSelectedRegions) only one cell is the primary (drives navigation etc) bool isPrimaryCell = current.Column == selectedColumn && rowToRender == selectedRow; RenderCell (cellColor, render, isPrimaryCell); - // Reset color scheme to normal for drawing separators if we drew text with custom scheme - if (scheme != rowScheme) { + // Style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas + // except when vertical cell lines are disabled (though a Style.SeparatorSymbol could be used) - if (isSelectedCell) { + if (!Style.ShowVerticalCellLines) { + if (isSelectedCell && FullRowSelect) { color = focused ? rowScheme.Focus : rowScheme.HotNormal; } else { color = Enabled ? rowScheme.Normal : rowScheme.Disabled; } Driver.SetAttribute (color); - } - - // If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell - if (!FullRowSelect) - Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); - if (style.AlwaysUseNormalColorForVerticalCellLines && style.ShowVerticalCellLines) { + if (current.X > 0) { + AddRune (current.X - 1, row, (Rune)Style.SeparatorSymbol); + } + if (current.X + current.Width - 1 < Bounds.Width) { + AddRune (current.X + current.Width - 1, row, (Rune)Style.SeparatorSymbol); + } - Driver.SetAttribute (rowScheme.Normal); + if (isSelectedCell) { + color = focused ? rowScheme.Focus : rowScheme.HotNormal; + } else { + color = Enabled ? rowScheme.Normal : rowScheme.Disabled; + } + Driver.SetAttribute (color); } - RenderSeparator (current.X - 1, row, false); - - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, row, false); + if (!Style.ExpandLastColumn) { + if (!Style.AddEmptyColumn) { + if (i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, row, (Rune)Style.BackgroundSymbol); + } + } + } else if (!Style.ShowVerticalCellLines) { + Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); + AddRune (Bounds.Width - 1, row, (Rune)Style.SeparatorSymbol); + } } } - - if (style.ShowVerticalCellLines) { - - Driver.SetAttribute (rowScheme.Normal); - - //render start and end of line - AddRune (0, row, CM.Glyphs.VLine); - AddRune (Bounds.Width - 1, row, CM.Glyphs.VLine); - } - } /// @@ -685,7 +677,7 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr // If the cell is the selected col/row then draw the first rune in inverted colors // this allows the user to track which cell is the active one during a multi cell // selection or in full row select mode - if (Style.InvertSelectedCellFirstCharacter && isPrimaryCell) { + if ((Style.InvertSelectedCell || Style.InvertSelectedCellFirstCharacter) && isPrimaryCell) { if (render.Length > 0) { // invert the color of the current cell for the first character @@ -693,8 +685,10 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr Driver.AddRune ((Rune)render [0]); if (render.Length > 1) { - Driver.SetAttribute (cellColor); - Driver.AddStr (render.Substring (1)); + if (!Style.InvertSelectedCell) { + Driver.SetAttribute (cellColor); + } + Driver.AddStr (render[1..]); } } } else { @@ -703,17 +697,6 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr } } - private void RenderSeparator (int col, int row, bool isHeader) - { - if (col < 0) - return; - - var renderLines = isHeader ? style.ShowVerticalHeaderLines : style.ShowVerticalCellLines; - - Rune symbol = renderLines ? CM.Glyphs.VLine : (Rune)SeparatorSymbol; - AddRune (col, row, symbol); - } - void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch) { Move (col, row); @@ -727,11 +710,12 @@ void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch) /// The string representation of /// /// Optional style indicating custom alignment for the cell + /// Character used to pad string (defaults to space) /// - private string TruncateOrPad (object originalCellValue, string representation, int availableHorizontalSpace, ColumnStyle colStyle) + private string TruncateOrPad (object originalCellValue, string representation, int availableHorizontalSpace, ColumnStyle colStyle, char padChar = ' ') { if (string.IsNullOrEmpty (representation)) - return new string (' ', availableHorizontalSpace); + return new string (padChar, availableHorizontalSpace); // if value is not wide enough if (representation.EnumerateRunes ().Sum (c => c.GetColumns ()) < availableHorizontalSpace) { @@ -742,17 +726,17 @@ private string TruncateOrPad (object originalCellValue, string representation, i switch (colStyle?.GetAlignment (originalCellValue) ?? TextAlignment.Left) { case TextAlignment.Left: - return representation + new string (' ', toPad); + return representation + new string (padChar, toPad); case TextAlignment.Right: - return new string (' ', toPad) + representation; + return new string (padChar, toPad) + representation; // TODO: With single line cells, centered and justified are the same right? case TextAlignment.Centered: case TextAlignment.Justified: return - new string (' ', (int)Math.Floor (toPad / 2.0)) + // round down + new string (padChar, (int)Math.Floor (toPad / 2.0)) + // round down representation + - new string (' ', (int)Math.Ceiling (toPad / 2.0)); // round up + new string (padChar, (int)Math.Ceiling (toPad / 2.0)); // round up } } @@ -1792,15 +1776,13 @@ private int CalculateMaxCellWidth (int col, int rowsToRender, ColumnStyle colSty /// private string GetRepresentation (object value, ColumnStyle colStyle) { - if (value == null || value == DBNull.Value) { - return NullSymbol; + if (value is null || value == DBNull.Value) { + return string.IsNullOrEmpty(Style.NullSymbol) ? " " : Style.NullSymbol; } return colStyle != null ? colStyle.GetRepresentation (value) : value.ToString (); } - - /// /// Describes a desire to render a column at a given horizontal position in the UI /// diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 5e8983d673..ac66eb50f3 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -73,12 +73,14 @@ public override void Setup () Checked = _listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, + /* _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = _listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + */ _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = _listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked @@ -218,6 +220,7 @@ void ToggleExpandLastColumn () } + /* void ToggleAlwaysUseNormalColorForVerticalCellLines () { _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; @@ -225,6 +228,7 @@ void ToggleAlwaysUseNormalColorForVerticalCellLines () _listColView.Update (); } + */ void ToggleSmoothScrolling () { diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 7d0ccc9ece..cf49f4c9c0 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -22,15 +22,18 @@ public class TableEditor : Scenario { private MenuItem _miShowHeaders; private MenuItem _miAlwaysShowHeaders; private MenuItem _miHeaderOverline; - private MenuItem _miHeaderMidline; + private MenuItem _miHeaderThruline; private MenuItem _miHeaderUnderline; + private MenuItem _miHeaderVertline; private MenuItem _miShowHorizontalScrollIndicators; private MenuItem _miCellLines; private MenuItem _miFullRowSelect; private MenuItem _miExpandLastColumn; - private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; + private MenuItem _miAddEmptyColumn; + //private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; private MenuItem _miSmoothScrolling; private MenuItem _miAlternatingColors; + private MenuItem _miInvert; private MenuItem _miCursor; private MenuItem _miBottomline; private MenuItem _miCheckboxes; @@ -42,6 +45,14 @@ public class TableEditor : Scenario { ColorScheme redColorSchemeAlt; ColorScheme alternatingColorScheme; + enum TableBorderStyleType + { + OuterHeaderBorderStyle, + InnerHeaderBorderStyle, + OuterBorderStyle, + InnerBorderStyle, + } + HashSet _checkedFileSystemInfos = new HashSet (); public override void Setup () @@ -59,40 +70,45 @@ public override void Setup () var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_OpenBigExample", "", () => OpenExample(true)), - new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)), + new MenuItem ("Open_BigExample", "", () => OpenExample(true)), + new MenuItem ("Open_SmallExample", "", () => OpenExample(false)), new MenuItem ("OpenCharacter_Map","",()=>OpenUnicodeMap()), new MenuItem ("OpenTreeExample","",()=>OpenTreeExample()), new MenuItem ("_CloseExample", "", () => CloseExample()), new MenuItem ("_Quit", "", () => Quit()), }), new MenuBarItem ("_View", new MenuItem [] { - _miShowHeaders = new MenuItem ("_ShowHeaders", "", () => ToggleShowHeaders()){Checked = tableView.Style.ShowHeaders, CheckType = MenuItemCheckStyle.Checked }, + _miShowHeaders = new MenuItem ("Show_Headers", "", () => ToggleShowHeaders()){Checked = tableView.Style.ShowHeaders, CheckType = MenuItemCheckStyle.Checked }, _miAlwaysShowHeaders = new MenuItem ("_AlwaysShowHeaders", "", () => ToggleAlwaysShowHeaders()){Checked = tableView.Style.AlwaysShowHeaders, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderOverline = new MenuItem ("_HeaderOverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderMidline = new MenuItem ("_HeaderMidLine", "", () => ToggleHeaderMidline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderUnderline = new MenuItem ("_HeaderUnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderOverline = new MenuItem ("Header_OverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderThruline = new MenuItem ("Header_ThroughLine", "", () => ToggleThruline()){Checked = tableView.Style.ShowHorizontalHeaderThroughline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderUnderline = new MenuItem ("Header_UnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderVertline = new MenuItem ("_VerticalHeaderLines", "", () => ToggleHeaderVertline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline()){Checked = tableView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, - _miShowHorizontalScrollIndicators = new MenuItem ("_HorizontalScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, + _miShowHorizontalScrollIndicators = new MenuItem ("Horizontal_ScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, _miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked }, - _miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + _miCellLines =new MenuItem ("Cell_Lines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + //_miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("AlwaysUse_NormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + new MenuItem ("_BorderStyles", "", () => SetBorderStyles()), + new MenuItem ("_BorderColor", "", () => SetBorderColor()), + new MenuItem ("AllBorders", "", () => ToggleAllCellLines()), + new MenuItem ("NoBorders", "", () => ToggleNoCellLines()), _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, - _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, - new MenuItem ("_AllLines", "", () => ToggleAllCellLines()), - new MenuItem ("_NoLines", "", () => ToggleNoCellLines()), + _miAddEmptyColumn = new MenuItem ("A_ddEmptyColumn", "", () => ToggleAddEmptyColumn()){Checked = tableView.Style.AddEmptyColumn, CheckType = MenuItemCheckStyle.Checked }, + _miSmoothScrolling = new MenuItem ("S_moothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, + _miAlternatingColors = new MenuItem ("Alte_rnatingColors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, + _miInvert = new MenuItem ("InvertSelectedCell", "", () => ToggleInvertSelectedCell()){Checked = tableView.Style.InvertSelectedCell,CheckType = MenuItemCheckStyle.Checked}, + _miCursor = new MenuItem ("_InvertSelectedCellFirstCharacter", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, + new MenuItem ("ClearColumnSt_yles", "", () => ClearColumnStyles()), + new MenuItem ("Sho_wAllColumns", "", ()=>ShowAllColumns()), _miCheckboxes = new MenuItem ("_Checkboxes", "", () => ToggleCheckboxes(false)){Checked = false, CheckType = MenuItemCheckStyle.Checked }, _miRadioboxes = new MenuItem ("_Radioboxes", "", () => ToggleCheckboxes(true)){Checked = false, CheckType = MenuItemCheckStyle.Checked }, - _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), - new MenuItem ("_Set Min Width", "", SetMinWidth), - new MenuItem ("_Set MinAcceptableWidth", "",SetMinAcceptableWidth), - new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne), + new MenuItem ("Set Ma_x Width", "", SetMaxWidth), + new MenuItem ("Set Mi_n Width", "", SetMinWidth), + new MenuItem ("Set MinAcceptable_Width", "",SetMinAcceptableWidth), + new MenuItem ("Set _All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne), }), }); @@ -276,11 +292,13 @@ private void HideColumn (int clickedCol) private int? GetColumn () { - if (tableView.Table == null) + if (tableView.Table == null) { return null; + } - if (tableView.SelectedColumn < 0 || tableView.SelectedColumn > tableView.Table.Columns) + if (tableView.SelectedColumn < 0 || tableView.SelectedColumn > tableView.Table.Columns) { return null; + } return tableView.SelectedColumn; } @@ -355,6 +373,168 @@ private void RunColumnWidthDialog (int? col, string prompt, Action e.ToString ()).ToArray ()) { + X = 0, + Y = 0, + }; + + var accepted = false; + var ok = new Button ("Ok", is_default: true); + ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = "BorderStyles" }; + + var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => e.ToString ()).ToArray ()) { + X = Pos.Right (rbBorderType) + 2, + Y = 0, + SelectedItem = (int) chosenStyle + }; + + // Change selection to existing style value for given type + rbBorderType.SelectedItemChanged += (s, e) => { + chosenType = (TableBorderStyleType)e.SelectedItem; + var str = chosenType.ToString (); + switch (e.SelectedItem) { + case 0: + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.OuterHeaderBorderStyle; + break; + case 1: + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.InnerHeaderBorderStyle; + break; + case 2: + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.OuterBorderStyle; + break; + case 3: + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.InnerBorderStyle; + break; + } + }; + rbBorderStyle.SelectedItemChanged += (s, e) => { + chosenStyle = (LineStyle)e.SelectedItem; + }; + + d.Add (rbBorderType, rbBorderStyle); + + Application.Run (d); + + if (accepted) { + + try { + switch ((int)chosenType) { + case 0: + Action setterHdrOut = (s, v) => s.OuterHeaderBorderStyle = v; + setterHdrOut (style, chosenStyle); + break; + case 1: + Action setterHdrIn = (s, v) => s.InnerHeaderBorderStyle = v; + setterHdrIn (style, chosenStyle); + break; + case 2: + Action setterOut = (s, v) => s.OuterBorderStyle = v; + setterOut (style, chosenStyle); + break; + case 3: + Action setterIn = (s, v) => s.InnerBorderStyle = v; + setterIn (style, chosenStyle); + break; + } + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); + } + + tableView.Update (); + } + } + + private void SetBorderColor () + { + var style = tableView.Style; + Color chosenForeground; + Color chosenBackground; + if (style.BorderColor.Foreground != -1) { + chosenForeground = style.BorderColor.Foreground; + } else { + chosenForeground = tableView.Border.ColorScheme.Normal.Foreground; + } + if (style.BorderColor.Background != -1) { + chosenBackground = style.BorderColor.Background; + } else { + chosenBackground = tableView.Border.ColorScheme.Normal.Background; + } + + var accepted = false; + var ok = new Button ("Ok", is_default: true); + ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = "BorderColor" }; + + var colorEnum = Enum.GetNames (typeof (ColorName)); + var rbBorderFgTitle = new Label ("Foreground") { + X = 0, + Y = 0 + }; + var rbBorderForeground = new RadioGroup (colorEnum.Select ( + e => e.ToString ()).ToArray ()) { + X = 0, + Y = Pos.Bottom(rbBorderFgTitle), + SelectedItem = (int)chosenForeground + }; + var rbBorderBgTitle = new Label ("Background") { + X = Pos.Right (rbBorderForeground) + 2, + Y = 0 + }; + var rbBorderBackground = new RadioGroup (colorEnum.Select ( + e => e.ToString ()).ToArray ()) { + X = Pos.Right (rbBorderForeground) + 2, + Y = Pos.Bottom(rbBorderBgTitle), + SelectedItem = (int)chosenBackground + }; + + rbBorderForeground.SelectedItemChanged += (s, e) => { + if (Color.TryParse (colorEnum [e.SelectedItem], out var c)) { + chosenForeground = (Color)c; + } + }; + rbBorderBackground.SelectedItemChanged += (s, e) => { + if (Color.TryParse (colorEnum [e.SelectedItem], out var c)) { + chosenBackground = (Color)c; + } + }; + + d.Add (rbBorderFgTitle, rbBorderForeground, rbBorderBgTitle, rbBorderBackground); + + Application.Run (d); + + if (accepted) { + + try { + Action setterCol = (s, v) => s.BorderColor = v; + setterCol (style, new Attribute (chosenForeground, chosenBackground)); + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); + } + + tableView.Update (); + } + } + private void SetupScrollBar () { var scrollBar = new ScrollBarView (tableView, true); @@ -437,10 +617,10 @@ private void ToggleOverline () tableView.Style.ShowHorizontalHeaderOverline = (bool)_miHeaderOverline.Checked; tableView.Update (); } - private void ToggleHeaderMidline () + private void ToggleThruline () { - _miHeaderMidline.Checked = !_miHeaderMidline.Checked; - tableView.Style.ShowVerticalHeaderLines = (bool)_miHeaderMidline.Checked; + _miHeaderThruline.Checked = !_miHeaderThruline.Checked; + tableView.Style.ShowHorizontalHeaderThroughline = (bool)_miHeaderThruline.Checked; tableView.Update (); } private void ToggleUnderline () @@ -449,6 +629,12 @@ private void ToggleUnderline () tableView.Style.ShowHorizontalHeaderUnderline = (bool)_miHeaderUnderline.Checked; tableView.Update (); } + private void ToggleHeaderVertline () + { + _miHeaderVertline.Checked = !_miHeaderVertline.Checked; + tableView.Style.ShowVerticalHeaderLines = (bool)_miHeaderVertline.Checked; + tableView.Update (); + } private void ToggleBottomline () { _miBottomline.Checked = !_miBottomline.Checked; @@ -472,7 +658,14 @@ private void ToggleExpandLastColumn () { _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; tableView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; + tableView.Update (); + } + + private void ToggleAddEmptyColumn () + { + _miAddEmptyColumn.Checked = !_miAddEmptyColumn.Checked; + tableView.Style.AddEmptyColumn = (bool)_miAddEmptyColumn.Checked; tableView.Update (); } @@ -532,6 +725,7 @@ private void CheckOrUncheckFile (FileSystemInfo info, bool check) } } + /* private void ToggleAlwaysUseNormalColorForVerticalCellLines () { _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; @@ -539,6 +733,8 @@ private void ToggleAlwaysUseNormalColorForVerticalCellLines () tableView.Update (); } + */ + private void ToggleSmoothScrolling () { _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; @@ -556,28 +752,36 @@ private void ToggleCellLines () private void ToggleAllCellLines () { tableView.Style.ShowHorizontalHeaderOverline = true; - tableView.Style.ShowVerticalHeaderLines = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowVerticalHeaderLines = true; tableView.Style.ShowVerticalCellLines = true; + tableView.Style.ShowHorizontalBottomline = true; _miHeaderOverline.Checked = true; - _miHeaderMidline.Checked = true; + _miHeaderThruline.Checked = true; _miHeaderUnderline.Checked = true; + _miHeaderVertline.Checked = true; _miCellLines.Checked = true; + _miBottomline.Checked = true; tableView.Update (); } private void ToggleNoCellLines () { tableView.Style.ShowHorizontalHeaderOverline = false; - tableView.Style.ShowVerticalHeaderLines = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowVerticalHeaderLines = false; tableView.Style.ShowVerticalCellLines = false; + tableView.Style.ShowHorizontalBottomline = false; _miHeaderOverline.Checked = false; - _miHeaderMidline.Checked = false; + _miHeaderThruline.Checked = false; _miHeaderUnderline.Checked = false; + _miHeaderVertline.Checked = false; _miCellLines.Checked = false; + _miBottomline.Checked = false; tableView.Update (); } @@ -595,6 +799,13 @@ private void ToggleAlternatingColors () tableView.SetNeedsDisplay (); } + private void ToggleInvertSelectedCell () + { + //toggle menu item + _miInvert.Checked = !_miInvert.Checked; + tableView.Style.InvertSelectedCell = (bool)_miInvert.Checked; + tableView.SetNeedsDisplay (); + } private void ToggleInvertSelectedCellFirstCharacter () { //toggle menu item diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 4a58ea70d5..5239895607 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -457,6 +457,7 @@ public void TableView_ShowHeadersFalse_AndNoHeaderLines () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = false; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = false; tv.Draw (); @@ -474,12 +475,13 @@ public void TableView_ShowHeadersFalse_OverlineTrue () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = true; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = false; tv.Draw (); string expected = @" -┌─┬─┐ +┌─┬─► │1│2│ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -492,15 +494,16 @@ public void TableView_ShowHeadersFalse_UnderlineTrue () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = false; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = true; - // Horizontal scrolling option is part of the underline + // Horizontal scrolling option is the lowest header line tv.Style.ShowHorizontalScrollIndicators = true; tv.Draw (); string expected = @" -├─┼─► +┌─┬─► │1│2│ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -514,8 +517,9 @@ public void TableView_ShowHeadersFalse_AllLines () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = true; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = true; - // Horizontal scrolling option is part of the underline + // Horizontal scrolling option is the lowest header line tv.Style.ShowHorizontalScrollIndicators = true; @@ -552,12 +556,36 @@ public void TableView_ExpandLastColumn_True () } [Fact, AutoInitShutdown] - public void TableView_ExpandLastColumn_False () + public void TableView_ExpandLastColumn_False_AddEmptyColumn_False () { var tv = SetUpMiniTable (); // the thing we are testing tv.Style.ExpandLastColumn = false; + tv.Style.AddEmptyColumn = false; + + tv.Draw (); + + string expected = @" +┌─┬─┐ +│A│B│ +├─┼─┤ +│1│2│ +"; + TestHelpers.AssertDriverContentsAre (expected, output); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [Fact, AutoInitShutdown] + public void TableView_ExpandLastColumn_False_AddEmptyColumn_True () + { + var tv = SetUpMiniTable (); + + // the thing we are testing + tv.Style.ExpandLastColumn = false; + tv.Style.AddEmptyColumn = true; tv.Draw (); @@ -846,6 +874,137 @@ public void TableView_ColorTests_FocusedOrNot (bool focused) Application.Shutdown (); } + [Fact, AutoInitShutdown] + public void TableView_NullSymbol_Is_EmptyString () + { + var tv = new TableView () { + Width = 20, + Height = 4 + }; + + var dt = new DataTable (); + dt.Columns.Add ("C1"); + dt.Columns.Add ("C2"); + dt.Columns.Add ("C3"); + + dt.Rows.Add ("Hello", DBNull.Value, "f"); + + tv.Table = new DataTableSource (dt); + tv.Style.NullSymbol = string.Empty; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + tv.Draw (); + + var expected = +@" +┌─────┬──┬─────────┐ +│C1 │C2│C3 │ +├─────┼──┼─────────┤ +│Hello│ │f │ +"; + + TestHelpers.AssertDriverContentsAre (expected, output); + + Application.Shutdown (); + } + + [Theory, AutoInitShutdown] + [InlineData (false)] + [InlineData (true)] + public void TableView_ColorTests_InvertSelectedCell_and_InvertSelectedCellFirstCharacter (bool focused) + { + var tv = new TableView () { + Width = 20, + Height = 4 + }; + + var dt = new DataTable (); + dt.Columns.Add ("C1"); + dt.Columns.Add ("C2"); + dt.Columns.Add ("C3"); + + dt.Rows.Add ("Hello", DBNull.Value, "f"); + + tv.Table = new DataTableSource (dt); + tv.Style.NullSymbol = "-"; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + // private method for forcing the view to be focused/not focused + var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic); + + // when the view is/isn't focused + setFocusMethod.Invoke (tv, new object [] { focused, tv, true }); + + tv.Draw (); + + var expected = +@" +┌─────┬──┬─────────┐ +│C1 │C2│C3 │ +├─────┼──┼─────────┤ +│Hello│- │f │ +"; + + TestHelpers.AssertDriverContentsAre (expected, output); + + // Now the thing we really want to test are the styles + // InvertSelectedCell and InvertSelectedCellFirstCharacter! + + tv.FullRowSelect = true; + tv.Style.InvertSelectedCell = true; + + tv.Draw (); + string expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +02222011011111111110 +"; + var invertFocus = new Attribute (tv.ColorScheme.Focus.Background, tv.ColorScheme.Focus.Foreground); + var invertHotFocus = new Attribute (tv.ColorScheme.HotFocus.Background, tv.ColorScheme.HotFocus.Foreground); + var testColors = new Attribute [] { + // 0 + tv.ColorScheme.Normal, + // 1 + focused? tv.ColorScheme.Focus : tv.ColorScheme.HotFocus, + // 2 + focused? invertFocus : invertHotFocus}; + + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + tv.Style.InvertSelectedCell = false; + tv.Style.InvertSelectedCellFirstCharacter = true; + + tv.Draw (); + expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +02111011011111111110 +"; + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + tv.Style.ShowVerticalCellLines = false; + + tv.Draw (); + expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +12111111111111111111 +"; + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + Application.Shutdown (); + } + [Theory, AutoInitShutdown] [InlineData (false)] [InlineData (true)] @@ -936,7 +1095,7 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) 00000 00000 00000 -21222 +01020 "; TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, new Attribute [] { @@ -1139,6 +1298,7 @@ public void ScrollRight_SmoothScrolling () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -1162,7 +1322,7 @@ public void ScrollRight_SmoothScrolling () string expected = @" -│A│B│C│ +│A│B│C► │1│2│3│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1181,7 +1341,7 @@ public void ScrollRight_SmoothScrolling () expected = @" -│B│C│D│ +◄B│C│D► │2│3│4│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1200,6 +1360,7 @@ public void ScrollRight_WithoutSmoothScrolling () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = false; @@ -1223,7 +1384,7 @@ public void ScrollRight_WithoutSmoothScrolling () string expected = @" -│A│B│C│ +│A│B│C► │1│2│3│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1241,7 +1402,7 @@ public void ScrollRight_WithoutSmoothScrolling () expected = @" -│D│E│F│ +◄D│E│F│ │4│5│6│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1260,6 +1421,7 @@ private TableView GetABCDEFTableView (out DataTable dt) // 3 columns are visible tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = false; @@ -1289,7 +1451,7 @@ public void TestColumnStyle_VisibleFalse_IsNotRendered () string expected = @" -│A│C│D│ +│A│C│D► │1│3│4│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1809,6 +1971,7 @@ public void LongColumnTest () // 25 characters can be printed into table tableView.Bounds = new Rect (0, 0, 25, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -1957,6 +2120,19 @@ public void LongColumnTest () "; TestHelpers.AssertDriverContentsAre (expected, output); + tableView.Style.AddEmptyColumn = false; + + tableView.LayoutSubviews (); + tableView.Draw (); + expected = +@" +│A │B │Very Long │ +├───┼───┼──────────┤ +│1 │2 │aaaaaaaaaa│ +│1 │2 │aaa │ +"; + TestHelpers.AssertDriverContentsAre (expected, output); + // MaxCellWidth limits MinCellWidth tableView.MaxCellWidth = 5; tableView.MinCellWidth = 10; @@ -1965,10 +2141,10 @@ public void LongColumnTest () tableView.Draw (); expected = @" -│A │B │Very │ │ -├─────┼─────┼─────┼─────┤ -│1 │2 │aaaaa│ │ -│1 │2 │aaa │ │ +│A │B │Very │ +├─────┼─────┼─────┤ +│1 │2 │aaaaa│ +│1 │2 │aaa │ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1986,6 +2162,7 @@ public void ScrollIndicators () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2066,7 +2243,7 @@ public void CellEventsBackgroundFill () dt.Rows.Add ("Hello", DBNull.Value, "f"); tv.Table = new DataTableSource (dt); - tv.NullSymbol = string.Empty; + tv.Style.NullSymbol = string.Empty; Application.Top.Add (tv); Application.Begin (Application.Top); @@ -2129,6 +2306,7 @@ public void ShowHorizontalBottomLine_WithVerticalCellLines () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2140,7 +2318,7 @@ public void ShowHorizontalBottomLine_WithVerticalCellLines () // Because first column in table is A string expected = @" -│A│B│C│ +┌A┬B┬C┐ ├─┼─┼─► │1│2│3│ └─┴─┴─┘"; @@ -2158,6 +2336,7 @@ public void ShowHorizontalBottomLine_NoCellLines () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2170,7 +2349,7 @@ public void ShowHorizontalBottomLine_NoCellLines () // Because first column in table is A string expected = @" -│A│B│C│ +┌A┬B┬C┐ └─┴─┴─► 1 2 3 ───────"; @@ -2234,12 +2413,13 @@ public void TestFullRowSelect_SelectionColorStopsAtTableEdge_WithCellLines () 0000000 0000000 0000000 -0111110 +0101010 0000000"; TestHelpers.AssertDriverAttributesAre (expected, driver: Application.Driver, normal, focus); } + /* [Fact, AutoInitShutdown] public void TestFullRowSelect_AlwaysUseNormalColorForVerticalCellLines () { @@ -2299,6 +2479,7 @@ public void TestFullRowSelect_AlwaysUseNormalColorForVerticalCellLines () TestHelpers.AssertDriverAttributesAre (expected, driver: Application.Driver, normal, focus); } + */ [Fact, AutoInitShutdown] public void TestTableViewCheckboxes_Simple () @@ -2769,7 +2950,7 @@ public void TestFullRowSelect_SelectionColorDoesNotStop_WhenShowVerticalCellLine string expected = @" A B C -─────── +──────► 1 2 3 1 2 3 1 2 3"; @@ -3196,6 +3377,7 @@ private TableView GetTwoRowSixColumnTable (out DataTable dt) // 3 columns are visible tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index b5563984fe..8007b7330e 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -28,6 +28,7 @@ public void TestTreeTableSource_BasicExpanding_WithKeyboard () { var tv = GetTreeTable (out _); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; tv.Draw (); @@ -85,6 +86,7 @@ public void TestTreeTableSource_BasicExpanding_WithMouse () { var tv = GetTreeTable (out _); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; tv.Draw (); @@ -150,6 +152,7 @@ public void TestTreeTableSource_CombinedWithCheckboxes () CheckBoxTableSourceWrapperByIndex checkSource; tv.Table = checkSource = new CheckBoxTableSourceWrapperByIndex (tv, tv.Table); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (2).MinAcceptableWidth = 1; tv.Draw ();