diff --git a/Terminal.Gui/Views/TableView.cs b/Terminal.Gui/Views/TableView.cs index 0c04d5061..5a674f3e7 100644 --- a/Terminal.Gui/Views/TableView.cs +++ b/Terminal.Gui/Views/TableView.cs @@ -761,6 +761,41 @@ namespace Terminal.Gui { SelectedRow = row; } + /// + /// Unions the current selected cell (and/or regions) with the provided cell and makes + /// it the active one. + /// + /// + /// + private void UnionSelection (int col, int row) + { + if (!MultiSelect || TableIsNullOrInvisible()) { + return; + } + + EnsureValidSelection (); + + var oldColumn = SelectedColumn; + var oldRow = SelectedRow; + + // move us to the new cell + SelectedColumn = col; + SelectedRow = row; + MultiSelectedRegions.Push ( + CreateTableSelection (col, row) + ); + + // if the old cell was not part of a rectangular select + // or otherwise selected we need to retain it in the selection + + if (!IsSelected (oldColumn, oldRow)) { + MultiSelectedRegions.Push ( + CreateTableSelection (oldColumn, oldRow) + ); + } + } + + /// /// Moves the and by the provided offsets. Optionally starting a box selection (see ) /// @@ -852,6 +887,8 @@ 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 () { @@ -914,6 +951,16 @@ namespace Terminal.Gui { return new TableSelection (new Point (pt1X, pt1Y), new Rect (left, top, right - left + 1, bot - top + 1)); } + /// + /// Returns a single point as a + /// + /// + /// + /// + private TableSelection CreateTableSelection (int x, int y) + { + return CreateTableSelection (x, y, x, y); + } /// /// /// Returns true if the given cell is selected either because it is the active cell or part of a multi cell selection (e.g. ). @@ -1039,7 +1086,12 @@ namespace Terminal.Gui { var hit = ScreenToCell (me.X, me.Y); if (hit != null) { - SetSelection (hit.Value.X, hit.Value.Y, me.Flags.HasFlag (MouseFlags.ButtonShift)); + if(MultiSelect && HasControlOrAlt(me)) { + UnionSelection(hit.Value.X, hit.Value.Y); + } else { + SetSelection (hit.Value.X, hit.Value.Y, me.Flags.HasFlag (MouseFlags.ButtonShift)); + } + Update (); } } @@ -1055,6 +1107,11 @@ namespace Terminal.Gui { return false; } + private bool HasControlOrAlt (MouseEvent me) + { + return me.Flags.HasFlag (MouseFlags.ButtonAlt) || me.Flags.HasFlag (MouseFlags.ButtonCtrl); + } + /// . /// Returns the column and row of that corresponds to a given point /// on the screen (relative to the control client area). Returns null if the point is diff --git a/UnitTests/TableViewTests.cs b/UnitTests/TableViewTests.cs index f29bfd25d..3ef1bfee7 100644 --- a/UnitTests/TableViewTests.cs +++ b/UnitTests/TableViewTests.cs @@ -646,6 +646,75 @@ namespace Terminal.Gui.Views { Application.Shutdown (); } + [Fact, AutoInitShutdown] + public void TestShiftClick_MultiSelect_TwoRowTable_FullRowSelect() + { + var tv = GetTwoRowSixColumnTable (); + + tv.MultiSelect = true; + + // Clicking in bottom row + tv.MouseEvent (new MouseEvent { + X = 1, + Y = 3, + Flags = MouseFlags.Button1Clicked + }); + + // should select that row + Assert.Equal (1, tv.SelectedRow); + + // shift clicking top row + tv.MouseEvent (new MouseEvent { + X = 1, + Y = 2, + Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonShift + }); + + // should extend the selection + Assert.Equal (0, tv.SelectedRow); + + var selected = tv.GetAllSelectedCells ().ToArray(); + + Assert.Contains (new Point(0,0), selected); + Assert.Contains (new Point (0, 1), selected); + } + + [Fact, AutoInitShutdown] + public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect () + { + var tv = GetTwoRowSixColumnTable (); + tv.Table.Rows.Add (1, 2, 3, 4, 5, 6); + + tv.MultiSelect = true; + + // Clicking in bottom row + tv.MouseEvent (new MouseEvent { + X = 1, + Y = 4, + Flags = MouseFlags.Button1Clicked + }); + + // should select that row + Assert.Equal (2, tv.SelectedRow); + + // shift clicking top row + tv.MouseEvent (new MouseEvent { + X = 1, + Y = 2, + Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl + }); + + // should extend the selection + // to include bottom and top row but not middle + Assert.Equal (0, tv.SelectedRow); + + var selected = tv.GetAllSelectedCells ().ToArray (); + + Assert.Contains (new Point (0, 0), selected); + Assert.DoesNotContain (new Point (0, 1), selected); + Assert.Contains (new Point (0, 2), selected); + } + [Theory] [InlineData (false)] [InlineData (true)]