|
@@ -99,7 +99,7 @@ namespace Terminal.Gui {
|
|
|
/// When <see cref="MultiSelect"/> is enabled this property contain all rectangles of selected cells. Rectangles describe column/rows selected in <see cref="Table"/> (not screen coordinates)
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
- public Stack<TableSelection> MultiSelectedRegions { get; } = new Stack<TableSelection> ();
|
|
|
+ public Stack<TableSelection> MultiSelectedRegions { get; private set; } = new Stack<TableSelection> ();
|
|
|
|
|
|
/// <summary>
|
|
|
/// Horizontal scroll offset. The index of the first column in <see cref="Table"/> 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));
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -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);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
///<inheritdoc/>
|
|
|
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 {
|
|
|
/// <param name="cellColor"></param>
|
|
|
/// <param name="render"></param>
|
|
|
/// <param name="isPrimaryCell"></param>
|
|
|
- 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 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 {
|
|
|
/// <param name="row"></param>
|
|
|
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
|
|
|
/// </summary>
|
|
|
/// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
|
|
|
- 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
|
|
|
/// </summary>
|
|
|
/// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
|
|
|
- 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 (<see cref="SelectedColumn"/>,nY) i.e. no horizontal scrolling.
|
|
|
/// </summary>
|
|
|
/// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
|
|
|
- public void ChangeSelectionToEndOfTable(bool extend)
|
|
|
+ public void ChangeSelectionToEndOfTable (bool extend)
|
|
|
{
|
|
|
var finalColumn = Table.Columns.Count - 1;
|
|
|
|
|
@@ -880,10 +898,10 @@ namespace Terminal.Gui {
|
|
|
/// </summary>
|
|
|
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 {
|
|
|
/// <summary>
|
|
|
/// Returns all cells in any <see cref="MultiSelectedRegions"/> (if <see cref="MultiSelect"/> is enabled) and the selected cell
|
|
|
/// </summary>
|
|
|
- /// <remarks>Return value is not affected by <see cref="FullRowSelect"/> (i.e. returned <see cref="Point"/>s are not expanded to
|
|
|
- /// include all points on row).</remarks>
|
|
|
/// <returns></returns>
|
|
|
public IEnumerable<Point> GetAllSelectedCells ()
|
|
|
{
|
|
|
if (TableIsNullOrInvisible () || Table.Rows.Count == 0)
|
|
|
- yield break;
|
|
|
+ {
|
|
|
+ return Enumerable.Empty<Point>();
|
|
|
+ }
|
|
|
|
|
|
EnsureValidSelection ();
|
|
|
|
|
|
+ var toReturn = new HashSet<Point>();
|
|
|
+
|
|
|
// 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;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -944,17 +966,60 @@ namespace Terminal.Gui {
|
|
|
/// <param name="pt1Y">Origin point for the selection in Y</param>
|
|
|
/// <param name="pt2X">End point for the selection in X</param>
|
|
|
/// <param name="pt2Y">End point for the selection in Y</param>
|
|
|
+ /// <param name="toggle">True if selection is result of <see cref="Command.ToggleChecked"/></param>
|
|
|
/// <returns></returns>
|
|
|
- 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)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -978,22 +1043,36 @@ namespace Terminal.Gui {
|
|
|
/// <returns></returns>
|
|
|
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<TableSelection> GetMultiSelectedRegionsContaining(int col, int row)
|
|
|
+ {
|
|
|
+ if(!MultiSelect)
|
|
|
+ {
|
|
|
+ return Enumerable.Empty<TableSelection>();
|
|
|
+ }
|
|
|
+
|
|
|
+ if(FullRowSelect)
|
|
|
+ {
|
|
|
+ return MultiSelectedRegions.Where (r => r.Rect.Bottom > row && r.Rect.Top <= row);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return MultiSelectedRegions.Where (r => r.Rect.Contains (col, row));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Returns true if the given <paramref name="columnIndex"/> 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 {
|
|
|
/// <returns>Cell clicked or null.</returns>
|
|
|
public Point? ScreenToCell (int clientX, int clientY)
|
|
|
{
|
|
|
- return ScreenToCell(clientX, clientY, out _);
|
|
|
+ return ScreenToCell (clientX, clientY, out _);
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc cref="ScreenToCell(int, int)"/>
|
|
@@ -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 {
|
|
|
/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/></remarks>
|
|
|
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.</param>
|
|
|
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<DataColumn> ()
|
|
|
.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 <see cref="ColumnStyle.ColorGetter"/>
|
|
|
/// </summary>
|
|
|
/// <value></value>
|
|
|
- public RowColorGetterDelegate RowColorGetter {get;set;}
|
|
|
+ public RowColorGetterDelegate RowColorGetter { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 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.
|
|
|
/// </summary>
|
|
|
/// <value></value>
|
|
|
- public bool ExpandLastColumn {get;set;} = true;
|
|
|
+ public bool ExpandLastColumn { get; set; } = true;
|
|
|
|
|
|
/// <summary>
|
|
|
/// <para>
|
|
@@ -1798,7 +1869,7 @@ namespace Terminal.Gui {
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
public bool SmoothHorizontalScrolling { get; set; } = true;
|
|
|
-
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Returns the entry from <see cref="ColumnStyles"/> for the given <paramref name="col"/> or null if no custom styling is defined for it
|
|
|
/// </summary>
|
|
@@ -2003,6 +2074,12 @@ namespace Terminal.Gui {
|
|
|
/// <value></value>
|
|
|
public Rect Rect { get; set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// True if the selection was made through <see cref="Command.ToggleChecked"/>
|
|
|
+ /// and therefore should persist even through keyboard navigation.
|
|
|
+ /// </summary>
|
|
|
+ public bool IsToggled { get; set; }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Creates a new selected area starting at the origin corner and covering the provided rectangular area
|
|
|
/// </summary>
|