diff --git a/Terminal.Gui/Views/TableView.cs b/Terminal.Gui/Views/TableView.cs
index 1a69dec07..c1647437b 100644
--- a/Terminal.Gui/Views/TableView.cs
+++ b/Terminal.Gui/Views/TableView.cs
@@ -99,7 +99,7 @@ namespace Terminal.Gui {
/// When is enabled this property contain all rectangles of selected cells. Rectangles describe column/rows selected in (not screen coordinates)
///
///
- public Stack MultiSelectedRegions { get; } = new Stack ();
+ public Stack MultiSelectedRegions { get; private set; } = new Stack ();
///
/// Horizontal scroll offset. The index of the first column in to display when when rendering the view.
@@ -109,7 +109,7 @@ namespace Terminal.Gui {
get => columnOffset;
//try to prevent this being set to an out of bounds column
- set => columnOffset = TableIsNullOrInvisible() ? 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));
}
///
@@ -186,7 +186,7 @@ namespace Terminal.Gui {
set {
if (cellActivationKey != value) {
ReplaceKeyBinding (cellActivationKey, value);
-
+
// of API user is mixing and matching old and new methods of keybinding then they may have lost
// the old binding (e.g. with ClearKeybindings) so ReplaceKeyBinding alone will fail
AddKeyBinding (value, Command.Accept);
@@ -218,9 +218,9 @@ namespace Terminal.Gui {
AddCommand (Command.LineDown, () => { ChangeSelectionByOffset (0, 1, false); return true; });
AddCommand (Command.PageUp, () => { PageUp (false); return true; });
AddCommand (Command.PageDown, () => { PageDown (false); return true; });
- AddCommand (Command.LeftHome, () => { ChangeSelectionToStartOfRow (false); return true; });
+ AddCommand (Command.LeftHome, () => { ChangeSelectionToStartOfRow (false); return true; });
AddCommand (Command.RightEnd, () => { ChangeSelectionToEndOfRow (false); return true; });
- AddCommand (Command.TopHome, () => { ChangeSelectionToStartOfTable(false); return true; });
+ AddCommand (Command.TopHome, () => { ChangeSelectionToStartOfTable (false); return true; });
AddCommand (Command.BottomEnd, () => { ChangeSelectionToEndOfTable (false); return true; });
AddCommand (Command.RightExtend, () => { ChangeSelectionByOffset (1, 0, true); return true; });
@@ -234,8 +234,10 @@ namespace Terminal.Gui {
AddCommand (Command.TopHomeExtend, () => { ChangeSelectionToStartOfTable (true); return true; });
AddCommand (Command.BottomEndExtend, () => { ChangeSelectionToEndOfTable (true); return true; });
- AddCommand (Command.SelectAll, () => { SelectAll(); return true; });
- AddCommand (Command.Accept, () => { OnCellActivated(new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow)); return true; });
+ AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
+ AddCommand (Command.Accept, () => { OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow)); return true; });
+
+ AddCommand (Command.ToggleChecked, () => { ToggleCurrentCellSelection (); return true; });
// Default keybindings for this view
AddKeyBinding (Key.CursorLeft, Command.Left);
@@ -252,7 +254,7 @@ namespace Terminal.Gui {
AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
- AddKeyBinding (Key.CursorDown| Key.ShiftMask, Command.LineDownExtend);
+ AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
AddKeyBinding (Key.Home | Key.ShiftMask, Command.LeftHomeExtend);
@@ -264,33 +266,34 @@ namespace Terminal.Gui {
AddKeyBinding (CellActivationKey, Command.Accept);
}
+
///
public override void Redraw (Rect bounds)
- {
- Move (0, 0);
- var frame = Frame;
+ {
+ Move (0, 0);
+ var frame = Frame;
- scrollRightPoint = null;
- scrollLeftPoint = null;
+ scrollRightPoint = null;
+ scrollLeftPoint = null;
- // What columns to render at what X offset in viewport
- var columnsToRender = CalculateViewport (bounds).ToArray ();
+ // What columns to render at what X offset in viewport
+ var columnsToRender = CalculateViewport (bounds).ToArray ();
- Driver.SetAttribute (GetNormalColor ());
+ Driver.SetAttribute (GetNormalColor ());
- //invalidate current row (prevents scrolling around leaving old characters in the frame
- Driver.AddStr (new string (' ', bounds.Width));
+ //invalidate current row (prevents scrolling around leaving old characters in the frame
+ Driver.AddStr (new string (' ', bounds.Width));
- int line = 0;
+ int line = 0;
- if (ShouldRenderHeaders ()) {
- // Render something like:
- /*
- ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐
- │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│
- └────────────────────┴──────────┴───────────┴──────────────┴─────────┘
- */
- if (Style.ShowHorizontalHeaderOverline) {
+ if (ShouldRenderHeaders ()) {
+ // Render something like:
+ /*
+ ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐
+ │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│
+ └────────────────────┴──────────┴───────────┴──────────────┴─────────┘
+ */
+ if (Style.ShowHorizontalHeaderOverline) {
RenderHeaderOverline (line, bounds.Width, columnsToRender);
line++;
}
@@ -436,19 +439,19 @@ namespace Terminal.Gui {
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 _)) {
+ 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 _)) {
+ if (!TryGetNearestVisibleColumn (lastColumnIdxRendered + 1, true, false, out _)) {
// no we would not
moreColumnsToRight = false;
}
@@ -466,7 +469,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
@@ -475,12 +478,11 @@ namespace Terminal.Gui {
// unless we have horizontally scrolled along
// in which case render an arrow, to indicate user
// can scroll left
- if(Style.ShowHorizontalScrollIndicators && moreColumnsToLeft)
- {
+ if (Style.ShowHorizontalScrollIndicators && moreColumnsToLeft) {
rune = Driver.LeftArrow;
- scrollLeftPoint = new Point(c,row);
+ scrollLeftPoint = new Point (c, row);
}
-
+
}
// if the next column is the start of a header
else if (columnsToRender.Any (r => r.X == c + 1)) {
@@ -495,10 +497,9 @@ 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 && moreColumnsToRight)
- {
+ if (Style.ShowHorizontalScrollIndicators && moreColumnsToRight) {
rune = Driver.RightArrow;
- scrollRightPoint = new Point(c,row);
+ scrollRightPoint = new Point (c, row);
}
}
@@ -518,7 +519,7 @@ namespace Terminal.Gui {
var focused = HasFocus;
var rowScheme = (Style.RowColorGetter?.Invoke (
- new RowColorGetterArgs(Table,rowToRender))) ?? ColorScheme;
+ new RowColorGetterArgs (Table, rowToRender))) ?? ColorScheme;
//render start of line
if (style.ShowVerticalCellLines)
@@ -529,11 +530,9 @@ namespace Terminal.Gui {
Attribute color;
- if(FullRowSelect && IsSelected (0, rowToRender)) {
+ if (FullRowSelect && IsSelected (0, rowToRender)) {
color = focused ? rowScheme.HotFocus : rowScheme.HotNormal;
- }
- else
- {
+ } else {
color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
}
@@ -562,17 +561,16 @@ namespace Terminal.Gui {
var colorSchemeGetter = colStyle?.ColorGetter;
ColorScheme scheme;
- if(colorSchemeGetter != null) {
+ if (colorSchemeGetter != null) {
// user has a delegate for defining row color per cell, call it
- scheme = colorSchemeGetter(
- new CellColorGetterArgs (Table, rowToRender, current.Column.Ordinal, val, representation,rowScheme));
+ scheme = colorSchemeGetter (
+ new CellColorGetterArgs (Table, rowToRender, current.Column.Ordinal, val, representation, rowScheme));
// if users custom color getter returned null, use the row scheme
- if(scheme == null) {
+ if (scheme == null) {
scheme = rowScheme;
}
- }
- else {
+ } else {
// There is no custom cell coloring delegate so use the scheme for the row
scheme = rowScheme;
}
@@ -588,16 +586,15 @@ namespace Terminal.Gui {
// While many cells can be selected (see MultiSelectedRegions) only one cell is the primary (drives navigation etc)
bool isPrimaryCell = current.Column.Ordinal == selectedColumn && rowToRender == selectedRow;
-
- RenderCell (cellColor,render,isPrimaryCell);
-
+
+ RenderCell (cellColor, render, isPrimaryCell);
+
// Reset color scheme to normal for drawing separators if we drew text with custom scheme
if (scheme != rowScheme) {
- if(isSelectedCell) {
+ if (isSelectedCell) {
color = focused ? rowScheme.HotFocus : rowScheme.HotNormal;
- }
- else {
+ } else {
color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
}
Driver.SetAttribute (color);
@@ -629,7 +626,7 @@ namespace Terminal.Gui {
///
///
///
- protected virtual void RenderCell (Attribute cellColor, string render,bool isPrimaryCell)
+ protected virtual void RenderCell (Attribute cellColor, string render, bool isPrimaryCell)
{
// 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
@@ -740,12 +737,15 @@ namespace Terminal.Gui {
col = GetNearestVisibleColumn (col, lookRight, true);
- if (!MultiSelect || !extendExistingSelection)
- MultiSelectedRegions.Clear ();
+ if (!MultiSelect || !extendExistingSelection) {
+ ClearMultiSelectedRegions (true);
+ }
+
if (extendExistingSelection) {
+
// If we are extending current selection but there isn't one
- if (MultiSelectedRegions.Count == 0) {
+ if (MultiSelectedRegions.Count == 0 || MultiSelectedRegions.All(m=>m.IsToggled)) {
// Create a new region between the old active cell and the new cell
var rect = CreateTableSelection (SelectedColumn, SelectedRow, col, row);
MultiSelectedRegions.Push (rect);
@@ -761,6 +761,24 @@ namespace Terminal.Gui {
SelectedRow = row;
}
+ private void ClearMultiSelectedRegions (bool keepToggledSelections)
+ {
+ if (!keepToggledSelections) {
+ MultiSelectedRegions.Clear ();
+ return;
+ }
+
+ var oldRegions = MultiSelectedRegions.ToArray ().Reverse ();
+
+ MultiSelectedRegions.Clear ();
+
+ foreach (var region in oldRegions) {
+ if (region.IsToggled) {
+ MultiSelectedRegions.Push (region);
+ }
+ }
+ }
+
///
/// Unions the current selected cell (and/or regions) with the provided cell and makes
/// it the active one.
@@ -769,10 +787,10 @@ namespace Terminal.Gui {
///
private void UnionSelection (int col, int row)
{
- if (!MultiSelect || TableIsNullOrInvisible()) {
+ if (!MultiSelect || TableIsNullOrInvisible ()) {
return;
}
-
+
EnsureValidSelection ();
var oldColumn = SelectedColumn;
@@ -812,7 +830,7 @@ namespace Terminal.Gui {
/// Moves the selection up by one page
///
/// true to extend the current selection (if any) instead of replacing
- public void PageUp(bool extend)
+ public void PageUp (bool extend)
{
ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), extend);
Update ();
@@ -822,7 +840,7 @@ namespace Terminal.Gui {
/// Moves the selection down by one page
///
/// true to extend the current selection (if any) instead of replacing
- public void PageDown(bool extend)
+ public void PageDown (bool extend)
{
ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), extend);
Update ();
@@ -846,7 +864,7 @@ namespace Terminal.Gui {
/// to (,nY) i.e. no horizontal scrolling.
///
/// true to extend the current selection (if any) instead of replacing
- public void ChangeSelectionToEndOfTable(bool extend)
+ public void ChangeSelectionToEndOfTable (bool extend)
{
var finalColumn = Table.Columns.Count - 1;
@@ -880,10 +898,10 @@ namespace Terminal.Gui {
///
public void SelectAll ()
{
- if (TableIsNullOrInvisible() || !MultiSelect || Table.Rows.Count == 0)
+ if (TableIsNullOrInvisible () || !MultiSelect || Table.Rows.Count == 0)
return;
- MultiSelectedRegions.Clear ();
+ ClearMultiSelectedRegions (true);
// Create a single region over entire table, set the origin of the selection to the active cell so that a followup spread selection e.g. shift-right behaves properly
MultiSelectedRegions.Push (new TableSelection (new Point (SelectedColumn, SelectedRow), new Rect (0, 0, Table.Columns.Count, table.Rows.Count)));
@@ -893,16 +911,18 @@ namespace Terminal.Gui {
///
/// Returns all cells in any (if is enabled) and the selected cell
///
- /// Return value is not affected by (i.e. returned s are not expanded to
- /// include all points on row).
///
public IEnumerable GetAllSelectedCells ()
{
if (TableIsNullOrInvisible () || Table.Rows.Count == 0)
- yield break;
+ {
+ return Enumerable.Empty();
+ }
EnsureValidSelection ();
+ var toReturn = new HashSet();
+
// If there are one or more rectangular selections
if (MultiSelect && MultiSelectedRegions.Any ()) {
@@ -916,25 +936,27 @@ namespace Terminal.Gui {
for (int y = yMin; y < yMax; y++) {
for (int x = xMin; x < xMax; x++) {
if (IsSelected (x, y)) {
- yield return new Point (x, y);
+ toReturn.Add(new Point (x, y));
}
}
}
- } else {
+ }
- // if there are no region selections then it is just the active cell
- // if we are selecting the full row
- if (FullRowSelect) {
- // all cells in active row are selected
- for (int x = 0; x < Table.Columns.Count; x++) {
- yield return new Point (x, SelectedRow);
- }
- } else {
- // Not full row select and no multi selections
- yield return new Point (SelectedColumn, SelectedRow);
+ // if there are no region selections then it is just the active cell
+
+ // if we are selecting the full row
+ if (FullRowSelect) {
+ // all cells in active row are selected
+ for (int x = 0; x < Table.Columns.Count; x++) {
+ toReturn.Add(new Point (x, SelectedRow));
}
+ } else {
+ // Not full row select and no multi selections
+ toReturn.Add(new Point (SelectedColumn, SelectedRow));
}
+
+ return toReturn;
}
///
@@ -944,17 +966,60 @@ namespace Terminal.Gui {
/// Origin point for the selection in Y
/// End point for the selection in X
/// End point for the selection in Y
+ /// True if selection is result of
///
- private TableSelection CreateTableSelection (int pt1X, int pt1Y, int pt2X, int pt2Y)
+ private TableSelection CreateTableSelection (int pt1X, int pt1Y, int pt2X, int pt2Y, bool toggle = false)
{
- var top = Math.Max(Math.Min (pt1Y, pt2Y), 0);
- var bot = Math.Max(Math.Max (pt1Y, pt2Y), 0);
+ var top = Math.Max (Math.Min (pt1Y, pt2Y), 0);
+ var bot = Math.Max (Math.Max (pt1Y, pt2Y), 0);
- var left = Math.Max(Math.Min (pt1X, pt2X), 0);
- var right = Math.Max(Math.Max (pt1X, pt2X), 0);
+ var left = Math.Max (Math.Min (pt1X, pt2X), 0);
+ var right = Math.Max (Math.Max (pt1X, pt2X), 0);
// Rect class is inclusive of Top Left but exclusive of Bottom Right so extend by 1
- return new TableSelection (new Point (pt1X, pt1Y), new Rect (left, top, right - left + 1, bot - top + 1));
+ return new TableSelection (new Point (pt1X, pt1Y), new Rect (left, top, right - left + 1, bot - top + 1)) {
+ IsToggled = toggle
+ };
+ }
+
+ private void ToggleCurrentCellSelection ()
+ {
+ if (!MultiSelect) {
+ return;
+ }
+
+ var regions = GetMultiSelectedRegionsContaining(selectedColumn, selectedRow).ToArray();
+ var toggles = regions.Where(s=>s.IsToggled).ToArray ();
+
+ // Toggle it off
+ if (toggles.Any ()) {
+
+ var oldRegions = MultiSelectedRegions.ToArray ().Reverse ();
+ MultiSelectedRegions.Clear ();
+
+ foreach (var region in oldRegions) {
+ if (!toggles.Contains (region))
+ MultiSelectedRegions.Push (region);
+ }
+ } else {
+
+ // user is toggling selection within a rectangular
+ // select. So toggle the full region
+ if(regions.Any())
+ {
+ foreach(var r in regions)
+ {
+ r.IsToggled = true;
+ }
+ }
+ else{
+ // Toggle on a single cell selection
+ MultiSelectedRegions.Push (
+ CreateTableSelection (selectedColumn, SelectedRow, selectedColumn, selectedRow, true)
+ );
+ }
+
+ }
}
///
@@ -978,22 +1043,36 @@ namespace Terminal.Gui {
///
public bool IsSelected (int col, int row)
{
- if(!IsColumnVisible(col)) {
+ 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;
-
- // Cell is also selected if Y axis appears in any region (when FullRowSelect is enabled)
- if (FullRowSelect && MultiSelect && MultiSelectedRegions.Any (r => r.Rect.Bottom > row && r.Rect.Top <= row))
+ if(GetMultiSelectedRegionsContaining(col,row).Any())
+ {
return true;
+ }
return row == SelectedRow &&
(col == SelectedColumn || FullRowSelect);
}
+ private IEnumerable GetMultiSelectedRegionsContaining(int col, int row)
+ {
+ if(!MultiSelect)
+ {
+ return Enumerable.Empty();
+ }
+
+ if(FullRowSelect)
+ {
+ return MultiSelectedRegions.Where (r => r.Rect.Bottom > row && r.Rect.Top <= row);
+ }
+ else
+ {
+ return MultiSelectedRegions.Where (r => r.Rect.Contains (col, row));
+ }
+ }
+
///
/// Returns true if the given indexes a visible
/// column otherwise false. Returns false for indexes that are out of bounds.
@@ -1071,19 +1150,17 @@ namespace Terminal.Gui {
if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
- if (scrollLeftPoint != null
+ if (scrollLeftPoint != null
&& scrollLeftPoint.Value.X == me.X
- && scrollLeftPoint.Value.Y == me.Y)
- {
+ && scrollLeftPoint.Value.Y == me.Y) {
ColumnOffset--;
EnsureValidScrollOffsets ();
SetNeedsDisplay ();
}
- if (scrollRightPoint != null
+ if (scrollRightPoint != null
&& scrollRightPoint.Value.X == me.X
- && scrollRightPoint.Value.Y == me.Y)
- {
+ && scrollRightPoint.Value.Y == me.Y) {
ColumnOffset++;
EnsureValidScrollOffsets ();
SetNeedsDisplay ();
@@ -1092,8 +1169,8 @@ namespace Terminal.Gui {
var hit = ScreenToCell (me.X, me.Y);
if (hit != null) {
- if(MultiSelect && HasControlOrAlt(me)) {
- UnionSelection(hit.Value.X, hit.Value.Y);
+ if (MultiSelect && HasControlOrAlt (me)) {
+ UnionSelection (hit.Value.X, hit.Value.Y);
} else {
SetSelection (hit.Value.X, hit.Value.Y, me.Flags.HasFlag (MouseFlags.ButtonShift));
}
@@ -1128,7 +1205,7 @@ namespace Terminal.Gui {
/// Cell clicked or null.
public Point? ScreenToCell (int clientX, int clientY)
{
- return ScreenToCell(clientX, clientY, out _);
+ return ScreenToCell (clientX, clientY, out _);
}
///
@@ -1153,7 +1230,7 @@ namespace Terminal.Gui {
headerIfAny = col?.Column;
return null;
}
-
+
var rowIdx = RowOffset - headerHeight + clientY;
@@ -1161,7 +1238,7 @@ namespace Terminal.Gui {
// invalid index back to user!
if (rowIdx >= Table.Rows.Count) {
return null;
- }
+ }
if (col != null && rowIdx >= 0) {
@@ -1242,10 +1319,10 @@ namespace Terminal.Gui {
/// Changes will not be immediately visible in the display until you call
public void EnsureValidSelection ()
{
- if (TableIsNullOrInvisible()) {
+ if (TableIsNullOrInvisible ()) {
// Table doesn't exist, we should probably clear those selections
- MultiSelectedRegions.Clear ();
+ ClearMultiSelectedRegions (false);
return;
}
@@ -1315,8 +1392,7 @@ namespace Terminal.Gui {
/// Use false if you are primarily interested in learning about directional column visibility.
private int GetNearestVisibleColumn (int columnIndex, bool lookRight, bool allowBumpingInOppositeDirection)
{
- if(TryGetNearestVisibleColumn(columnIndex,lookRight,allowBumpingInOppositeDirection, out var answer))
- {
+ if (TryGetNearestVisibleColumn (columnIndex, lookRight, allowBumpingInOppositeDirection, out var answer)) {
return answer;
}
@@ -1335,7 +1411,7 @@ namespace Terminal.Gui {
// get the column visibility by index (if no style visible is true)
bool [] columnVisibility = Table.Columns.Cast ()
.Select (c => this.Style.GetColumnStyleIfAny (c)?.Visible ?? true)
- .ToArray();
+ .ToArray ();
// column is visible
if (columnVisibility [columnIndex]) {
@@ -1346,10 +1422,9 @@ namespace Terminal.Gui {
int increment = lookRight ? 1 : -1;
// move in that direction
- for (int i = columnIndex; i >=0 && i < columnVisibility.Length; i += increment) {
+ for (int i = columnIndex; i >= 0 && i < columnVisibility.Length; i += increment) {
// if we find a visible column
- if(columnVisibility [i])
- {
+ if (columnVisibility [i]) {
idx = i;
return true;
}
@@ -1357,7 +1432,7 @@ namespace Terminal.Gui {
// Caller only wants to look in one direction and we did not find any
// visible columns in that direction
- if(!allowBumpingInOppositeDirection) {
+ if (!allowBumpingInOppositeDirection) {
idx = columnIndex;
return false;
}
@@ -1400,10 +1475,10 @@ namespace Terminal.Gui {
//if we have scrolled too far to the right
if (SelectedColumn > columnsToRender.Max (r => r.Column.Ordinal)) {
- if(Style.SmoothHorizontalScrolling) {
+ if (Style.SmoothHorizontalScrolling) {
// Scroll right 1 column at a time until the users selected column is visible
- while(SelectedColumn > columnsToRender.Max (r => r.Column.Ordinal)) {
+ while (SelectedColumn > columnsToRender.Max (r => r.Column.Ordinal)) {
ColumnOffset++;
columnsToRender = CalculateViewport (Bounds).ToArray ();
@@ -1414,11 +1489,10 @@ namespace Terminal.Gui {
break;
}
- }
- else {
+ } else {
ColumnOffset = SelectedColumn;
}
-
+
}
//if we have scrolled too far down
@@ -1482,7 +1556,7 @@ namespace Terminal.Gui {
int colWidth;
// if column is not being rendered
- if(colStyle?.Visible == false) {
+ if (colStyle?.Visible == false) {
// do not add it to the returned columns
continue;
}
@@ -1492,16 +1566,14 @@ namespace Terminal.Gui {
// there is not enough space for this columns
// visible content
- if (usedSpace + colWidth > availableHorizontalSpace)
- {
+ if (usedSpace + colWidth > availableHorizontalSpace) {
bool showColumn = false;
// if this column accepts flexible width rendering and
// is therefore happy rendering into less space
- if ( colStyle != null && colStyle.MinAcceptableWidth > 0 &&
+ if (colStyle != null && colStyle.MinAcceptableWidth > 0 &&
// is there enough space to meet the MinAcceptableWidth
- (availableHorizontalSpace - usedSpace) >= colStyle.MinAcceptableWidth)
- {
+ (availableHorizontalSpace - usedSpace) >= colStyle.MinAcceptableWidth) {
// show column and use use whatever space is
// left for rendering it
showColumn = true;
@@ -1510,14 +1582,13 @@ namespace Terminal.Gui {
// If its the only column we are able to render then
// accept it anyway (that must be one massively wide column!)
- if (first)
- {
+ if (first) {
showColumn = true;
}
// no special exceptions and we are out of space
// so stop accepting new columns for the render area
- if(!showColumn)
+ if (!showColumn)
break;
}
@@ -1771,7 +1842,7 @@ namespace Terminal.Gui {
/// Delegate for coloring specific rows in a different color. For cell color
///
///
- public RowColorGetterDelegate RowColorGetter {get;set;}
+ public RowColorGetterDelegate RowColorGetter { get; set; }
///
/// Determines rendering when the last column in the table is visible but it's
@@ -1781,7 +1852,7 @@ namespace Terminal.Gui {
/// and leave a blank column that cannot be selected in the remaining space.
///
///
- public bool ExpandLastColumn {get;set;} = true;
+ public bool ExpandLastColumn { get; set; } = true;
///
///
@@ -1798,7 +1869,7 @@ namespace Terminal.Gui {
///
///
public bool SmoothHorizontalScrolling { get; set; } = true;
-
+
///
/// Returns the entry from for the given or null if no custom styling is defined for it
///
@@ -2003,6 +2074,12 @@ namespace Terminal.Gui {
///
public Rect Rect { get; set; }
+ ///
+ /// True if the selection was made through
+ /// and therefore should persist even through keyboard navigation.
+ ///
+ public bool IsToggled { get; set; }
+
///
/// Creates a new selected area starting at the origin corner and covering the provided rectangular area
///
diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs
index 9f50ccbad..f34b81da4 100644
--- a/UICatalog/Scenarios/TableEditor.cs
+++ b/UICatalog/Scenarios/TableEditor.cs
@@ -148,6 +148,8 @@ namespace UICatalog.Scenarios {
}
}
};
+
+ tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
}
private void ShowAllColumns ()
diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs
index bb08a22ff..2bcc46d95 100644
--- a/UnitTests/Views/TableViewTests.cs
+++ b/UnitTests/Views/TableViewTests.cs
@@ -321,6 +321,8 @@ namespace Terminal.Gui.ViewTests {
Bounds = new Rect (0, 0, 10, 5)
};
+ tableView.ChangeSelectionToEndOfTable(false);
+
// select the last row
tableView.MultiSelectedRegions.Clear ();
tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (0, 3), new Rect (0, 3, 4, 1)));
@@ -1506,6 +1508,185 @@ namespace Terminal.Gui.ViewTests {
Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
}
+
+ [Fact, AutoInitShutdown]
+ public void TestToggleCells_MultiSelectOn ()
+ {
+ // 2 row table
+ var tableView = GetABCDEFTableView (out var dt);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+
+ tableView.MultiSelect = true;
+ tableView.AddKeyBinding(Key.Space,Command.ToggleChecked);
+
+ var selectedCell = tableView.GetAllSelectedCells().Single();
+ Assert.Equal(0,selectedCell.X);
+ Assert.Equal(0,selectedCell.Y);
+
+ // Go Right
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
+
+ selectedCell = tableView.GetAllSelectedCells().Single();
+ Assert.Equal(1,selectedCell.X);
+ Assert.Equal(0,selectedCell.Y);
+
+ // Toggle Select
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+ var m = tableView.MultiSelectedRegions.Single();
+ Assert.True(m.IsToggled);
+ Assert.Equal(1,m.Origin.X);
+ Assert.Equal(0,m.Origin.Y);
+ selectedCell = tableView.GetAllSelectedCells().Single();
+ Assert.Equal(1,selectedCell.X);
+ Assert.Equal(0,selectedCell.Y);
+
+ // Go Left
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
+
+ // Both Toggled and Moved to should be selected
+ Assert.Equal(2,tableView.GetAllSelectedCells().Count());
+ var s1 = tableView.GetAllSelectedCells().ElementAt(0);
+ var s2 = tableView.GetAllSelectedCells().ElementAt(1);
+ Assert.Equal(1,s1.X);
+ Assert.Equal(0,s1.Y);
+ Assert.Equal(0,s2.X);
+ Assert.Equal(0,s2.Y);
+
+ // Go Down
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
+
+ // Both Toggled and Moved to should be selected but not 0,0
+ // which we moved down from
+ Assert.Equal(2,tableView.GetAllSelectedCells().Count());
+ s1 = tableView.GetAllSelectedCells().ElementAt(0);
+ s2 = tableView.GetAllSelectedCells().ElementAt(1);
+ Assert.Equal(1,s1.X);
+ Assert.Equal(0,s1.Y);
+ Assert.Equal(0,s2.X);
+ Assert.Equal(1,s2.Y);
+
+
+ // Go back to the toggled cell
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight});
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp});
+
+ // Toggle off
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+
+ // Go Left
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft});
+
+ selectedCell = tableView.GetAllSelectedCells().Single();
+ Assert.Equal(0,selectedCell.X);
+ Assert.Equal(0,selectedCell.Y);
+ }
+
+ [Fact, AutoInitShutdown]
+ public void TestToggleCells_MultiSelectOn_FullRowSelect ()
+ {
+ // 2 row table
+ var tableView = GetABCDEFTableView (out var dt);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ tableView.FullRowSelect = true;
+ tableView.MultiSelect = true;
+ tableView.AddKeyBinding(Key.Space,Command.ToggleChecked);
+
+ // Toggle Select Cell 0,0
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+
+ // Go Down
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
+
+ var m = tableView.MultiSelectedRegions.Single();
+ Assert.True(m.IsToggled);
+ Assert.Equal(0,m.Origin.X);
+ Assert.Equal(0,m.Origin.Y);
+
+ //First row toggled and Second row active = 12 selected cells
+ Assert.Equal(12,tableView.GetAllSelectedCells().Count());
+
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
+
+ Assert.Single(tableView.MultiSelectedRegions.Where(r=>r.IsToggled));
+
+ // Can untoggle at 1,0 even though 0,0 was initial toggle because FullRowSelect is on
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+
+ Assert.Empty(tableView.MultiSelectedRegions.Where(r=>r.IsToggled));
+
+ }
+
+
+ [Fact, AutoInitShutdown]
+ public void TestToggleCells_MultiSelectOn_SquareSelectToggled ()
+ {
+ // 3 row table
+ var tableView = GetABCDEFTableView (out var dt);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ tableView.MultiSelect = true;
+ tableView.AddKeyBinding(Key.Space,Command.ToggleChecked);
+
+ // Make a square selection
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown});
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight});
+
+ Assert.Equal(4,tableView.GetAllSelectedCells().Count());
+
+ // Toggle the square selected region on
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+
+ // Go Right
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
+
+ //Toggled on square + the active cell (x=2,y=1)
+ Assert.Equal(5,tableView.GetAllSelectedCells().Count());
+ Assert.Equal(2,tableView.SelectedColumn);
+ Assert.Equal(1,tableView.SelectedRow);
+
+ // Untoggle the rectangular region by hitting toggle in
+ // any cell in that rect
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
+
+ Assert.Equal(4,tableView.GetAllSelectedCells().Count());
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space });
+ Assert.Equal(1,tableView.GetAllSelectedCells().Count());
+ }
+
+
+
+ [Fact, AutoInitShutdown]
+ public void TestToggleCells_MultiSelectOn_Two_SquareSelects_BothToggled ()
+ {
+ // 6 row table
+ var tableView = GetABCDEFTableView (out var dt);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ dt.Rows.Add (1, 2, 3, 4, 5, 6);
+ tableView.MultiSelect = true;
+ tableView.AddKeyBinding(Key.Space,Command.ToggleChecked);
+
+ // Make first square selection (0,0 to 1,1)
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown});
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight});
+ tableView.ProcessKey (new KeyEvent { Key = Key.Space});
+ Assert.Equal(4,tableView.GetAllSelectedCells().Count());
+
+ // Make second square selection leaving 1 unselected line between them
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
+ tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown});
+ tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight});
+
+ // 2 square selections
+ Assert.Equal(8,tableView.GetAllSelectedCells().Count());
+ }
+
[Theory, AutoInitShutdown]
[InlineData(new object[] { true,true })]