From 79219fdbb2b99853282b15dc6eafbe45e4066cde Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Tue, 25 Feb 2020 20:22:05 +0700 Subject: [PATCH 01/65] Remove unused wxGrid member variable --- include/wx/generic/grid.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index c88b2a6122..dc563ad0c1 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2332,8 +2332,6 @@ protected: bool m_waitForSlowClick; - wxGridCellCoords m_selectionStart; - wxCursor m_rowResizeCursor; wxCursor m_colResizeCursor; From 673ed29d7b57e4a49c451c394c41b586b46cdd67 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Tue, 25 Feb 2020 21:11:29 +0700 Subject: [PATCH 02/65] Make wxGridSelection to be non-friend to wxGrid Improve integrity of the wxGridSelection internal data by removing `friend` declaration. --- include/wx/generic/gridsel.h | 27 +++++++++++++++++++++++++-- src/generic/grid.cpp | 10 +++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 125fc02ef9..4e9f66dd67 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -71,6 +71,31 @@ public: void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); + const wxGridCellCoordsArray& GetCellSelection() const + { + return m_cellSelection; + } + + const wxGridCellCoordsArray& GetBlockSelectionTopLeft() const + { + return m_blockSelectionTopLeft; + } + + const wxGridCellCoordsArray& GetBlockSelectionBottomRight() const + { + return m_blockSelectionBottomRight; + } + + const wxArrayInt& GetRowSelection() const + { + return m_rowSelection; + } + + const wxArrayInt& GetColSelection() const + { + return m_colSelection; + } + private: int BlockContain( int topRow1, int leftCol1, int bottomRow1, int rightCol1, @@ -106,8 +131,6 @@ private: wxGrid *m_grid; wxGrid::wxGridSelectionModes m_selectionMode; - friend class WXDLLIMPEXP_FWD_CORE wxGrid; - wxDECLARE_NO_COPY_CLASS(wxGridSelection); }; diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 608feb1634..e5ecf32eee 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10044,7 +10044,7 @@ wxGridCellCoordsArray wxGrid::GetSelectedCells() const return a; } - return m_selection->m_cellSelection; + return m_selection->GetCellSelection(); } wxGridCellCoordsArray wxGrid::GetSelectionBlockTopLeft() const @@ -10055,7 +10055,7 @@ wxGridCellCoordsArray wxGrid::GetSelectionBlockTopLeft() const return a; } - return m_selection->m_blockSelectionTopLeft; + return m_selection->GetBlockSelectionTopLeft(); } wxGridCellCoordsArray wxGrid::GetSelectionBlockBottomRight() const @@ -10066,7 +10066,7 @@ wxGridCellCoordsArray wxGrid::GetSelectionBlockBottomRight() const return a; } - return m_selection->m_blockSelectionBottomRight; + return m_selection->GetBlockSelectionBottomRight(); } wxArrayInt wxGrid::GetSelectedRows() const @@ -10077,7 +10077,7 @@ wxArrayInt wxGrid::GetSelectedRows() const return a; } - return m_selection->m_rowSelection; + return m_selection->GetRowSelection(); } wxArrayInt wxGrid::GetSelectedCols() const @@ -10088,7 +10088,7 @@ wxArrayInt wxGrid::GetSelectedCols() const return a; } - return m_selection->m_colSelection; + return m_selection->GetColSelection(); } void wxGrid::ClearSelection() From acd72efbf15d383cd491b618f8c6602a591f87d7 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Fri, 28 Feb 2020 02:38:03 +0700 Subject: [PATCH 03/65] Implement wxGridBlockCoords class wxGridBlockCoords represents a location of a block of cells in the grid. --- include/wx/generic/grid.h | 127 ++++++++++++++++++++- interface/wx/grid.h | 189 ++++++++++++++++++++++++++++++ src/generic/grid.cpp | 221 ++++++++++++++++++++++++++++++++++++ tests/controls/gridtest.cpp | 187 ++++++++++++++++++++++++++++++ 4 files changed, 722 insertions(+), 2 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index dc563ad0c1..f90fff3954 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -747,10 +747,133 @@ private: }; +// ---------------------------------------------------------------------------- +// wxGridBlockCoords: location of a block of cells in the grid +// ---------------------------------------------------------------------------- + +struct wxGridBlockDiffResult; + +class WXDLLIMPEXP_CORE wxGridBlockCoords +{ +public: + wxGridBlockCoords() : + m_topRow(-1), + m_leftCol(-1), + m_bottomRow(-1), + m_rightCol(-1) + { + } + + wxGridBlockCoords(int topRow, int leftCol, int bottomRow, int rightCol) : + m_topRow(topRow), + m_leftCol(leftCol), + m_bottomRow(bottomRow), + m_rightCol(rightCol) + { + } + + // default copy ctor is ok + + int GetTopRow() const { return m_topRow; } + void SetTopRow(int row) { m_topRow = row; } + int GetLeftCol() const { return m_leftCol; } + void SetLeftCol(int col) { m_leftCol = col; } + int GetBottomRow() const { return m_bottomRow; } + void SetBottomRow(int row) { m_bottomRow = row; } + int GetRightCol() const { return m_rightCol; } + void SetRightCol(int col) { m_rightCol = col; } + + wxGridCellCoords GetTopLeft() const + { + return wxGridCellCoords(m_topRow, m_leftCol); + } + + wxGridCellCoords GetBottomRight() const + { + return wxGridCellCoords(m_bottomRow, m_rightCol); + } + + wxGridBlockCoords Canonicalize() const + { + wxGridBlockCoords result = *this; + + if ( result.m_topRow > result.m_bottomRow ) + wxSwap(result.m_topRow, result.m_bottomRow); + + if ( result.m_leftCol > result.m_rightCol ) + wxSwap(result.m_leftCol, result.m_rightCol); + + return result; + } + + bool Intersects(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_bottomRow && m_bottomRow >= other.m_topRow && + m_leftCol <= other.m_rightCol && m_rightCol >= other.m_leftCol; + } + + // Whether the block contains the cell. + // returns @true, if the block contains the cell, + // @false, otherwise + bool ContainCell(const wxGridCellCoords& cell) const; + + // Whether the blocks contain each other. + // returns 1, if this block contains the other, + // -1, if the other block contains this one, + // 0, otherwise + int ContainBlock(const wxGridBlockCoords& other) const; + + // Calculates the result blocks by subtracting the other block from this + // block. splitOrientation can be wxVERTICAL or wxHORIZONTAL. + wxGridBlockDiffResult + Difference(const wxGridBlockCoords& other, int splitOrientation) const; + + // Calculates the symmetric difference of the blocks. + wxGridBlockDiffResult + SymDifference(const wxGridBlockCoords& other) const; + + bool operator==(const wxGridBlockCoords& other) const + { + return m_topRow == other.m_topRow && m_leftCol == other.m_leftCol && + m_bottomRow == other.m_bottomRow && m_rightCol == other.m_rightCol; + } + + bool operator!=(const wxGridBlockCoords& other) const + { + return !(*this == other); + } + + bool operator!() const + { + return m_topRow == -1 && m_leftCol == -1 && + m_bottomRow == -1 && m_rightCol == -1; + } + +private: + int m_topRow; + int m_leftCol; + int m_bottomRow; + int m_rightCol; +}; + +// ---------------------------------------------------------------------------- +// wxGridBlockDiffResult: The helper struct uses as a result type for difference +// functions of wxGridBlockCoords class. +// Parts can be uninitialized (equals to wxGridNoBlockCoords), that means +// that the corresponding part doesn't exists in the result. +// ---------------------------------------------------------------------------- + +struct wxGridBlockDiffResult +{ + wxGridBlockCoords m_parts[4]; +}; + + // For comparisons... // -extern WXDLLIMPEXP_CORE wxGridCellCoords wxGridNoCellCoords; -extern WXDLLIMPEXP_CORE wxRect wxGridNoCellRect; +extern WXDLLIMPEXP_CORE wxGridCellCoords wxGridNoCellCoords; +extern WXDLLIMPEXP_CORE wxGridBlockCoords wxGridNoBlockCoords; +extern WXDLLIMPEXP_CORE wxRect wxGridNoCellRect; // An array of cell coords... // diff --git a/interface/wx/grid.h b/interface/wx/grid.h index dfbdb73740..1bbad92e42 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1816,6 +1816,195 @@ public: bool operator!() const; }; +/** + Represents coordinates of a block of cells in the grid. + + An object of this class contains coordinates of the left top and the bottom right + corners of a block. + + @since 3.1.4 + */ +class wxGridBlockCoords +{ +public: + /** + Default constructor initializes the object to invalid state. + + Initially the coordinates are invalid (-1) and so operator!() for an + uninitialized wxGridBlockCoords returns @true. + */ + wxGridBlockCoords(); + + /** + Constructor taking a coordinates of the left top and the bottom right + corners. + */ + wxGridBlockCoords(int topRow, int leftCol, int bottomRow, int rightCol); + + /** + Return the row of the left top corner. + */ + int GetTopRow() const; + + /** + Set the row of the left top corner. + */ + void SetTopRow(int row); + + /** + Return the column of the left top corner. + */ + int GetLeftCol() const; + + /** + Set the column of the left top corner. + */ + void SetLeftCol(int col); + + /** + Return the row of the bottom right corner. + */ + int GetBottomRow() const; + + /** + Set the row of the bottom right corner. + */ + void SetBottomRow(int row); + + /** + Return the column of the bottom right corner. + */ + int GetRightCol() const; + + /** + Set the column of the bottom right corner. + */ + void SetRightCol(int col); + + /** + Return the coordinates of the top left corner. + */ + wxGridCellCoords GetTopLeft() const + { + return wxGridCellCoords(m_topRow, m_leftCol); + } + + /** + Return the coordinates of the bottom right corner. + */ + wxGridCellCoords GetBottomRight() const + { + return wxGridCellCoords(m_bottomRow, m_rightCol); + } + + /** + Return the canonicalized block where top left coordinates is less + then bottom right coordinates. + */ + wxGridBlockCoords Canonicalize() const; + + /** + Whether the blocks intersects. + + @return + @true, if the block intersects with the other, @false, otherwise. + */ + bool Intersects(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_bottomRow && m_bottomRow >= other.m_topRow && + m_leftCol <= other.m_rightCol && m_rightCol >= other.m_leftCol; + } + + /** + Whether the block contains the cell. + + @return + @true, if the block contains the cell, @false, otherwise. + */ + bool ContainCell(const wxGridCellCoords& cell) const; + + /** + Whether the blocks contain each other. + + @return + 1, if this block contains the other, + -1, if the other block contains this one, + 0, otherwise. + */ + int ContainBlock(const wxGridBlockCoords& other) const; + + /** + Calculates the result blocks by subtracting the other block from this + block. + + @param other + The block to subtract from this block. + @param splitOrientation + The block splitting orientation (either @c wxHORIZONTAL or + @c wxVERTICAL). + @return + Up to 4 blocks. If block doesn't exist in the result, it has value + of @c wxGridNoBlockCoords. + */ + wxGridBlockDiffResult + Difference(const wxGridBlockCoords& other, int splitOrientation) const; + + /** + Calculates the symmetric difference of the blocks. + + @param other + The block to subtract from this block. + @return + Up to 4 blocks. If block doesn't exist in the result, it has value + of @c wxGridNoBlockCoords. + */ + wxGridBlockDiffResult + SymDifference(const wxGridBlockCoords& other) const; + + /** + Equality operator. + */ + bool operator==(const wxGridBlockCoords& other) const; + + /** + Inequality operator. + */ + bool operator!=(const wxGridBlockCoords& other) const; + + /** + Checks whether the cells block is invalid. + + Returns @true only if all components are -1. Notice that if one + of the components (but not all) is -1, this method returns @false even + if the object is invalid. This is done because objects in such state + should actually never exist, i.e. either all components should be -1 + or none of them should be -1. + */ + bool operator!() const; + +private: + int m_topRow; + int m_leftCol; + int m_bottomRow; + int m_rightCol; +}; + +/** + @class wxGridBlockDiffResult + + The helper struct uses as a result type for difference functions of + @c wxGridBlockCoords class. + + Parts can be uninitialized (equals to @c wxGridNoBlockCoords), that means + that the corresponding part doesn't exists in the result. + + @since 3.1.4 + */ +struct wxGridBlockDiffResult +{ + wxGridBlockCoords m_parts[4]; +}; + /** @class wxGridTableBase diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index e5ecf32eee..1fed7a841d 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -94,6 +94,7 @@ struct DefaultHeaderRenderers // ---------------------------------------------------------------------------- wxGridCellCoords wxGridNoCellCoords( -1, -1 ); +wxGridBlockCoords wxGridNoBlockCoords( -1, -1, -1, -1 ); wxRect wxGridNoCellRect( -1, -1, -1, -1 ); namespace @@ -1152,6 +1153,226 @@ const wxGridCornerHeaderRenderer& wxGridCellAttrProvider::GetCornerRenderer() return gs_defaultHeaderRenderers.cornerRenderer; } +// ---------------------------------------------------------------------------- +// wxGridBlockCoords +// ---------------------------------------------------------------------------- + +bool wxGridBlockCoords::ContainCell(const wxGridCellCoords& cell) const +{ + return m_topRow <= cell.GetRow() && cell.GetRow() <= m_bottomRow && + m_leftCol <= cell.GetCol() && cell.GetCol() <= m_rightCol; +} + +int wxGridBlockCoords::ContainBlock(const wxGridBlockCoords& other) const +{ +// returns 1, if this block contains the other, +// -1, if the other block contains this one, +// 0, otherwise + if ( m_topRow <= other.m_topRow && other.m_bottomRow <= m_bottomRow && + m_leftCol <= other.m_leftCol && other.m_rightCol <= m_rightCol ) + return 1; + else if ( other.m_topRow <= m_topRow && m_bottomRow <= other.m_bottomRow && + other.m_leftCol <= m_leftCol && m_rightCol <= other.m_rightCol ) + return -1; + + return 0; +} + +wxGridBlockDiffResult +wxGridBlockCoords::Difference(const wxGridBlockCoords& other, + int splitOrientation) const +{ + wxGridBlockDiffResult result; + + // Check whether the blocks intersect. + if (!Intersects(other)) + { + result.m_parts[0] = *this; + return result; + } + + // Split the block in up to 4 new parts, that don't contain the other + // block, like this (for wxHORIZONTAL): + // |-----------------------------| + // | | + // | part[0] | + // | | + // |-----------------------------| + // | part[2] | other | part[3] | + // |-----------------------------| + // | | + // | part[1] | + // | | + // |-----------------------------| + // And for wxVERTICAL: + // |-----------------------------| + // | | | | + // | | part[2] | | + // | | | | + // | |---------| | + // | part[0] | other | part[1] | + // | |---------| | + // | | | | + // | | part[3] | | + // | | | | + // |-----------------------------| + + if ( splitOrientation == wxHORIZONTAL ) + { + // Part[0]. + if ( m_topRow < other.m_topRow ) + result.m_parts[0] = + wxGridBlockCoords(m_topRow, m_leftCol, + other.m_topRow - 1, m_rightCol); + + // Part[1]. + if ( m_bottomRow > other.m_bottomRow ) + result.m_parts[1] = + wxGridBlockCoords(other.m_bottomRow + 1, m_leftCol, + m_bottomRow, m_rightCol); + + const int maxTopRow = wxMax(m_topRow, other.m_topRow); + const int minBottomRow = wxMin(m_bottomRow, other.m_bottomRow); + + // Part[2]. + if ( m_leftCol < other.m_leftCol ) + result.m_parts[2] = + wxGridBlockCoords(maxTopRow, m_leftCol, + minBottomRow, other.m_leftCol - 1); + + // Part[3]. + if ( m_rightCol > other.m_rightCol ) + result.m_parts[3] = + wxGridBlockCoords(maxTopRow, other.m_rightCol + 1, + minBottomRow, m_rightCol); + } + else // wxVERTICAL + { + // Part[0]. + if ( m_leftCol < other.m_leftCol ) + result.m_parts[0] = + wxGridBlockCoords(m_topRow, m_leftCol, + m_bottomRow, other.m_leftCol - 1); + + // Part[1]. + if ( m_rightCol > other.m_rightCol ) + result.m_parts[1] = + wxGridBlockCoords(m_topRow, other.m_rightCol + 1, + m_bottomRow, m_rightCol); + + const int maxLeftCol = wxMax(m_leftCol, other.m_leftCol); + const int minRightCol = wxMin(m_rightCol, other.m_rightCol); + + // Part[2]. + if ( m_topRow < other.m_topRow ) + result.m_parts[2] = + wxGridBlockCoords(m_topRow, maxLeftCol, + other.m_topRow - 1, minRightCol); + + // Part[3]. + if ( m_bottomRow > other.m_bottomRow ) + result.m_parts[3] = + wxGridBlockCoords(other.m_bottomRow + 1, maxLeftCol, + m_bottomRow, minRightCol); + } + + return result; +} + +wxGridBlockDiffResult +wxGridBlockCoords::SymDifference(const wxGridBlockCoords& other) const +{ + wxGridBlockDiffResult result; + + // Check whether the blocks intersect. + if ( !Intersects(other) ) + { + result.m_parts[0] = *this; + result.m_parts[1] = other; + return result; + } + + // Possible result blocks: + // |------------------| + // | | minUpper->m_topRow + // | part[0] | + // | | maxUpper->m_topRow - 1 + // |-----------------------------| + // | | | | maxUpper->m_topRow + // | part[2] | x | part[3] | + // | | | | minLower->m_bottomRow + // |-----------------------------| + // | | minLower->m_bottomRow + 1 + // | part[1] | + // | | maxLower->m_bottomRow + // |-------------------| + // + // The x marks the intersection of the blocks, which is not a part + // of the result. + + // Part[0]. + int maxUpperRow; + if ( m_topRow != other.m_topRow ) + { + const bool block1Min = m_topRow < other.m_topRow; + const wxGridBlockCoords& minUpper = block1Min ? *this : other; + const wxGridBlockCoords& maxUpper = block1Min ? other : *this; + maxUpperRow = maxUpper.m_topRow; + + result.m_parts[0] = wxGridBlockCoords(minUpper.m_topRow, + minUpper.m_leftCol, + maxUpper.m_topRow - 1, + minUpper.m_rightCol); + } + else + { + maxUpperRow = m_topRow; + } + + // Part[1]. + int minLowerRow; + if ( m_bottomRow != other.m_bottomRow ) + { + const bool block1Min = m_bottomRow < other.m_bottomRow; + const wxGridBlockCoords& minLower = block1Min ? *this : other; + const wxGridBlockCoords& maxLower = block1Min ? other : *this; + minLowerRow = minLower.m_bottomRow; + + result.m_parts[1] = wxGridBlockCoords(minLower.m_bottomRow + 1, + maxLower.m_leftCol, + maxLower.m_bottomRow, + maxLower.m_rightCol); + } + else + { + minLowerRow = m_bottomRow; + } + + // Part[2]. + if ( m_leftCol != other.m_leftCol ) + { + result.m_parts[2] = wxGridBlockCoords(maxUpperRow, + wxMin(m_leftCol, + other.m_leftCol), + minLowerRow, + wxMax(m_leftCol, + other.m_leftCol) - 1); + } + + // Part[3]. + if ( m_rightCol != other.m_rightCol ) + { + result.m_parts[3] = wxGridBlockCoords(maxUpperRow, + wxMin(m_rightCol, + other.m_rightCol) + 1, + minLowerRow, + wxMax(m_rightCol, + other.m_rightCol)); + } + + return result; +} + // ---------------------------------------------------------------------------- // wxGridTableBase // ---------------------------------------------------------------------------- diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 23de3046a5..a562eaafde 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -1259,4 +1259,191 @@ TEST_CASE_METHOD(GridTestCase, "Grid::AutoSizeColumn", "[grid]") } } +// Test wxGridBlockCoords here because it'a a part of grid sources. + +std::ostream& operator<<(std::ostream& os, const wxGridBlockCoords& block) { + os << "wxGridBlockCoords(" << + block.GetTopRow() << ", " << block.GetLeftCol() << ", " << + block.GetBottomRow() << ", " << block.GetRightCol() << ")"; + return os; +} + +TEST_CASE("GridBlockCoords::Canonicalize", "[grid]") +{ + const wxGridBlockCoords block = + wxGridBlockCoords(4, 3, 2, 1).Canonicalize(); + + CHECK(block.GetTopRow() == 2); + CHECK(block.GetLeftCol() == 1); + CHECK(block.GetBottomRow() == 4); + CHECK(block.GetRightCol() == 3); +} + +TEST_CASE("GridBlockCoords::Intersects", "[grid]") +{ + // Inside. + CHECK(wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(1, 2, 2, 3))); + + // Intersects. + CHECK(wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(2, 2, 4, 4))); + + // Doesn't intersects. + CHECK(!wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(4, 4, 6, 6))); +} + +TEST_CASE("GridBlockCoords::ContainCell", "[grid]") +{ + // Inside. + CHECK(wxGridBlockCoords(1, 1, 3, 3).ContainCell(wxGridCellCoords(2, 2))); + + // Outside. + CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainCell(wxGridCellCoords(5, 5))); +} + +TEST_CASE("GridBlockCoords::ContainBlock", "[grid]") +{ + wxGridBlockCoords block1(1, 1, 5, 5); + wxGridBlockCoords block2(1, 1, 3, 3); + wxGridBlockCoords block3(2, 2, 7, 7); + wxGridBlockCoords block4(10, 10, 12, 12); + + CHECK(block1.ContainBlock(block2) == 1); + CHECK(block2.ContainBlock(block1) == -1); + CHECK(block1.ContainBlock(block3) == 0); + CHECK(block1.ContainBlock(block4) == 0); +} + +TEST_CASE("GridBlockCoords::Difference", "[grid]") +{ + SECTION("Subtract contained block (splitted horizontally)") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 7)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 1, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Subtract contained block (splitted vertically)") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxVERTICAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 7, 2)); + CHECK(result.m_parts[1] == wxGridBlockCoords(1, 6, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(1, 3, 2, 5)); + CHECK(result.m_parts[3] == wxGridBlockCoords(6, 3, 7, 5)); + } + + SECTION("Blocks intersect by the corner (splitted horizontally)") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 5)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks intersect by the corner (splitted vertically)") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxVERTICAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 5, 2)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridBlockCoords(1, 3, 2, 5)); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks are the same") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(1, 1, 3, 3); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridNoBlockCoords); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks doesn't intersects") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(5, 5, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 3, 3)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } +} + + +TEST_CASE("GridBlockCoords::SymDifference", "[grid]") +{ + SECTION("With contained block") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 7)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 1, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Blocks intersect by the corner") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 5)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 3, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Blocks are the same") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(1, 1, 3, 3); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridNoBlockCoords); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks doesn't intersects") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(5, 5, 7, 7); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 3, 3)); + CHECK(result.m_parts[1] == wxGridBlockCoords(5, 5, 7, 7)); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } +} + #endif //wxUSE_GRID From d1c8bba2b6873beaf762aad5c940b0988f330072 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Tue, 3 Mar 2020 00:18:33 +0700 Subject: [PATCH 04/65] Fix SelectEmptyGrid wxGrid unit test Use CATCH nested sections correctly. --- tests/controls/gridtest.cpp | 62 ++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index a562eaafde..6e57e1bc18 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -526,42 +526,46 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") TEST_CASE_METHOD(GridTestCase, "Grid::SelectEmptyGrid", "[grid]") { - SECTION("Delete rows/columns") + for ( int i = 0; i < 2; ++i ) { - SECTION("No rows") + SECTION(i == 0 ? "No rows" : "No columns") { - m_grid->DeleteRows(0, 10); - REQUIRE( m_grid->GetNumberRows() == 0 ); - } - SECTION("No columns") - { - m_grid->DeleteCols(0, 2); - REQUIRE( m_grid->GetNumberCols() == 0 ); - } - } - - SECTION("Select") - { - SECTION("Move right") - { - m_grid->MoveCursorRight(true); - } - SECTION("Move down") - { - m_grid->MoveCursorDown(true); - } - SECTION("Select row") - { - m_grid->SelectRow(1); - } - SECTION("Select column") - { - m_grid->SelectCol(1); + + if ( i == 0 ) + { + m_grid->DeleteRows(0, 10); + REQUIRE( m_grid->GetNumberRows() == 0 ); + } + else + { + m_grid->DeleteCols(0, 2); + REQUIRE( m_grid->GetNumberCols() == 0 ); + } + + SECTION("Move right") + { + m_grid->MoveCursorRight(true); + } + SECTION("Move down") + { + m_grid->MoveCursorDown(true); + } + SECTION("Select row") + { + m_grid->SelectRow(1); + } + SECTION("Select column") + { + m_grid->SelectCol(1); + } } } + CHECK( m_grid->GetSelectedCells().Count() == 0 ); CHECK( m_grid->GetSelectionBlockTopLeft().Count() == 0 ); CHECK( m_grid->GetSelectionBlockBottomRight().Count() == 0 ); + CHECK( m_grid->GetSelectedRows().Count() == 0 ); + CHECK( m_grid->GetSelectedCols().Count() == 0 ); } TEST_CASE_METHOD(GridTestCase, "Grid::ScrollWhenSelect", "[grid]") From 02509cbc39015540cb5dbbf7f67b1cda737d4d36 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Tue, 3 Mar 2020 00:09:50 +0700 Subject: [PATCH 05/65] Refactor wxGridSelection to store selection as blocks only Store all types of selection with an array of blocks instead of arrays of cells, blocks, rows and columns. It (hopefully) simplifies the code and allows us to implement editing of the last selection block much easier. --- include/wx/generic/gridsel.h | 82 +-- interface/wx/grid.h | 2 +- src/generic/gridsel.cpp | 1243 ++++++++++------------------------ 3 files changed, 381 insertions(+), 946 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 4e9f66dd67..18016adfcd 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -17,6 +17,10 @@ #include "wx/grid.h" +#include "wx/vector.h" + +typedef wxVector wxVectorGridBlockCoords; + class WXDLLIMPEXP_CORE wxGridSelection { public: @@ -48,16 +52,6 @@ public: kbd, sendEvent); } - void SelectCell(int row, int col, - const wxKeyboardState& kbd = wxKeyboardState(), - bool sendEvent = true); - void SelectCell(const wxGridCellCoords& coords, - const wxKeyboardState& kbd = wxKeyboardState(), - bool sendEvent = true) - { - SelectCell(coords.GetRow(), coords.GetCol(), kbd, sendEvent); - } - void ToggleCellSelection(int row, int col, const wxKeyboardState& kbd = wxKeyboardState()); void ToggleCellSelection(const wxGridCellCoords& coords, @@ -66,35 +60,20 @@ public: ToggleCellSelection(coords.GetRow(), coords.GetCol(), kbd); } + void DeselectBlock(const wxGridBlockCoords& block, + const wxKeyboardState& kbd = wxKeyboardState(), + bool sendEvent = true ); + void ClearSelection(); void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); - const wxGridCellCoordsArray& GetCellSelection() const - { - return m_cellSelection; - } - - const wxGridCellCoordsArray& GetBlockSelectionTopLeft() const - { - return m_blockSelectionTopLeft; - } - - const wxGridCellCoordsArray& GetBlockSelectionBottomRight() const - { - return m_blockSelectionBottomRight; - } - - const wxArrayInt& GetRowSelection() const - { - return m_rowSelection; - } - - const wxArrayInt& GetColSelection() const - { - return m_colSelection; - } + wxGridCellCoordsArray GetCellSelection() const; + wxGridCellCoordsArray GetBlockSelectionTopLeft() const; + wxGridCellCoordsArray GetBlockSelectionBottomRight() const; + wxArrayInt GetRowSelection() const; + wxArrayInt GetColSelection() const; private: int BlockContain( int topRow1, int leftCol1, @@ -105,28 +84,27 @@ private: // -1, if Block2 contains Block1, // 0, otherwise - int BlockContainsCell( int topRow, int leftCol, - int bottomRow, int rightCol, - int row, int col ) - // returns 1, if Block contains Cell, - // 0, otherwise + void SelectBlockNoEvent(const wxGridBlockCoords& block) { - return ( topRow <= row && row <= bottomRow && - leftCol <= col && col <= rightCol ); - } - - void SelectBlockNoEvent(int topRow, int leftCol, - int bottomRow, int rightCol) - { - SelectBlock(topRow, leftCol, bottomRow, rightCol, + SelectBlock(block.GetTopRow(), block.GetLeftCol(), + block.GetBottomRow(), block.GetRightCol(), wxKeyboardState(), false); } - wxGridCellCoordsArray m_cellSelection; - wxGridCellCoordsArray m_blockSelectionTopLeft; - wxGridCellCoordsArray m_blockSelectionBottomRight; - wxArrayInt m_rowSelection; - wxArrayInt m_colSelection; + // Really select the block and don't check for the current selection mode. + void Select(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, bool sendEvent); + + // If the new block containing any of the passed blocks, remove them. + // if a new block contained in the passed blockc, return. + // Otherwise add the new block to the blocks array. + void MergeOrAddBlock(wxVectorGridBlockCoords& blocks, + const wxGridBlockCoords& block); + + // The vector of selection blocks. We expect that the users select + // relatively small amount of blocks. K-D tree can be used to speed up + // searching speed. + wxVectorGridBlockCoords m_selection; wxGrid *m_grid; wxGrid::wxGridSelectionModes m_selectionMode; diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 1bbad92e42..060f913663 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1904,7 +1904,7 @@ public: wxGridBlockCoords Canonicalize() const; /** - Whether the blocks intersects. + Whether the blocks intersect. @return @true, if the block intersects with the other, @false, otherwise. diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 0bda0caa44..20b24f1c52 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -26,16 +26,22 @@ #if wxUSE_GRID #include "wx/generic/gridsel.h" +#include "wx/dynarray.h" -// Some explanation for the members of the class: -// m_cellSelection stores individual selected cells -// -- this is only used if m_selectionMode == wxGridSelectCells -// m_blockSelectionTopLeft and m_blockSelectionBottomRight -// store the upper left and lower right corner of selected Blocks -// m_rowSelection and m_colSelection store individual selected -// rows and columns; maybe those are superfluous and should be -// treated as blocks? +namespace +{ + +// The helper function to compare wxIntSortedArray elements. +int CompareInts(int n1, int n2) +{ + return n1 - n2; +} + +} + +WX_DEFINE_SORTED_ARRAY(int, wxIntSortedArray); + wxGridSelection::wxGridSelection( wxGrid * grid, wxGrid::wxGridSelectionModes sel ) @@ -46,66 +52,19 @@ wxGridSelection::wxGridSelection( wxGrid * grid, bool wxGridSelection::IsSelection() { - return ( m_cellSelection.GetCount() || m_blockSelectionTopLeft.GetCount() || - m_rowSelection.GetCount() || m_colSelection.GetCount() ); + return !m_selection.empty(); } bool wxGridSelection::IsInSelection( int row, int col ) { - size_t count; - - // First check whether the given cell is individually selected - // (if m_selectionMode is wxGridSelectCells). - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( row == coords.GetRow() && col == coords.GetCol() ) - return true; - } - } - - // Now check whether the given cell is - // contained in one of the selected blocks. - count = m_blockSelectionTopLeft.GetCount(); + // Check whether the given cell is contained in one of the selected blocks. + const size_t count = m_selection.size(); for ( size_t n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - if ( BlockContainsCell(coords1.GetRow(), coords1.GetCol(), - coords2.GetRow(), coords2.GetCol(), - row, col ) ) + if ( m_selection[n].ContainCell(wxGridCellCoords(row, col)) ) return true; } - // Now check whether the given cell is - // contained in one of the selected rows - // (unless we are in column selection mode). - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - count = m_rowSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - if ( row == m_rowSelection[n] ) - return true; - } - } - - // Now check whether the given cell is - // contained in one of the selected columns - // (unless we are in row selection mode). - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - count = m_colSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - if ( col == m_colSelection[n] ) - return true; - } - } - return false; } @@ -116,6 +75,8 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) if (selmode == m_selectionMode) return; + // TODO: wxGridSelectRowsOrColumns? + if ( m_selectionMode != wxGrid::wxGridSelectCells ) { // if changing form row to column selection @@ -127,51 +88,34 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) } else { - // if changing from cell selection to something else, - // promote selected cells/blocks to whole rows/columns. - size_t n; - while ( ( n = m_cellSelection.GetCount() ) > 0 ) - { - n--; - wxGridCellCoords& coords = m_cellSelection[n]; - int row = coords.GetRow(); - int col = coords.GetCol(); - m_cellSelection.RemoveAt(n); - if (selmode == wxGrid::wxGridSelectRows) - SelectRow( row ); - else // selmode == wxGridSelectColumns) - SelectCol( col ); - } - // Note that m_blockSelectionTopLeft's size may be changing! - for ( n = m_blockSelectionTopLeft.GetCount(); n > 0; ) + for ( size_t n = m_selection.size(); n > 0; ) { n--; - wxGridCellCoords& coords = m_blockSelectionTopLeft[n]; - int topRow = coords.GetRow(); - int leftCol = coords.GetCol(); - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - int bottomRow = coords2.GetRow(); - int rightCol = coords2.GetCol(); + const wxGridBlockCoords& block = m_selection[n]; + const int topRow = block.GetTopRow(); + const int leftCol = block.GetLeftCol(); + const int bottomRow = block.GetBottomRow(); + const int rightCol = block.GetRightCol(); if (selmode == wxGrid::wxGridSelectRows) { if (leftCol != 0 || rightCol != m_grid->GetNumberCols() - 1 ) { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - SelectBlockNoEvent( topRow, 0, - bottomRow, m_grid->GetNumberCols() - 1); + m_selection.erase(m_selection.begin() + n); + SelectBlockNoEvent( + wxGridBlockCoords(topRow, 0, + bottomRow, m_grid->GetNumberCols() - 1)); } } else // selmode == wxGridSelectColumns) { if (topRow != 0 || bottomRow != m_grid->GetNumberRows() - 1 ) { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - SelectBlockNoEvent(0, leftCol, - m_grid->GetNumberRows() - 1, rightCol); + m_selection.erase(m_selection.begin() + n); + SelectBlockNoEvent( + wxGridBlockCoords(0, leftCol, + m_grid->GetNumberRows() - 1, rightCol)); } } } @@ -185,185 +129,17 @@ void wxGridSelection::SelectRow(int row, const wxKeyboardState& kbd) if ( m_selectionMode == wxGrid::wxGridSelectColumns ) return; - size_t count, n; - - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( row, 0, row, m_grid->GetNumberCols() - 1, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // Simplify list of selected blocks (if possible) - count = m_blockSelectionTopLeft.GetCount(); - bool done = false; - - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - // Remove block if it is a subset of the row - if ( coords1.GetRow() == row && row == coords2.GetRow() ) - { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - } - else if ( coords1.GetCol() == 0 && - coords2.GetCol() == m_grid->GetNumberCols() - 1 ) - { - // silently return, if row is contained in block - if ( coords1.GetRow() <= row && row <= coords2.GetRow() ) - return; - // expand block, if it touched row - else if ( coords1.GetRow() == row + 1) - { - coords1.SetRow(row); - done = true; - } - else if ( coords2.GetRow() == row - 1) - { - coords2.SetRow(row); - done = true; - } - } - } - - // Unless we successfully handled the row, - // check whether row is already selected. - if ( !done ) - { - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( row == m_rowSelection[n] ) - return; - } - - // Add row to selection - m_rowSelection.Add(row); - } - - // Update View: - if ( !m_grid->GetBatchCount() && m_grid->GetNumberCols() != 0 ) - { - m_grid->RefreshBlock(row, 0, row, m_grid->GetNumberCols() - 1); - } - - // Send Event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, 0 ), - wxGridCellCoords( row, m_grid->GetNumberCols() - 1 ), - true, - kbd); - - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + Select(wxGridBlockCoords(row, 0, row, m_grid->GetNumberCols() - 1), + kbd, true); } void wxGridSelection::SelectCol(int col, const wxKeyboardState& kbd) { if ( m_selectionMode == wxGrid::wxGridSelectRows ) return; - size_t count, n; - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( 0, col, m_grid->GetNumberRows() - 1, col, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // Simplify list of selected blocks (if possible) - count = m_blockSelectionTopLeft.GetCount(); - bool done = false; - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - // Remove block if it is a subset of the column - if ( coords1.GetCol() == col && col == coords2.GetCol() ) - { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - } - else if ( coords1.GetRow() == 0 && - coords2.GetRow() == m_grid->GetNumberRows() - 1 ) - { - // silently return, if row is contained in block - if ( coords1.GetCol() <= col && col <= coords2.GetCol() ) - return; - // expand block, if it touched col - else if ( coords1.GetCol() == col + 1) - { - coords1.SetCol(col); - done = true; - } - else if ( coords2.GetCol() == col - 1) - { - coords2.SetCol(col); - done = true; - } - } - } - - // Unless we successfully handled the column, - // Check whether col is already selected. - if ( !done ) - { - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( col == m_colSelection[n] ) - return; - } - - // Add col to selection - m_colSelection.Add(col); - } - - // Update View: - if ( !m_grid->GetBatchCount() && m_grid->GetNumberRows() != 0 ) - { - m_grid->RefreshBlock(0, col, m_grid->GetNumberRows() - 1, col); - } - - // Send Event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( 0, col ), - wxGridCellCoords( m_grid->GetNumberRows() - 1, col ), - true, - kbd ); - - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + Select(wxGridBlockCoords(0, col, m_grid->GetNumberRows() - 1, col), + kbd, true); } void wxGridSelection::SelectBlock( int topRow, int leftCol, @@ -372,264 +148,39 @@ void wxGridSelection::SelectBlock( int topRow, int leftCol, bool sendEvent ) { // Fix the coordinates of the block if needed. + int allowed = -1; switch ( m_selectionMode ) { - default: - wxFAIL_MSG( "unknown selection mode" ); - wxFALLTHROUGH; - case wxGrid::wxGridSelectCells: - // nothing to do -- in this mode arbitrary blocks can be selected + // In this mode arbitrary blocks can be selected. + allowed = 1; break; case wxGrid::wxGridSelectRows: leftCol = 0; rightCol = m_grid->GetNumberCols() - 1; + allowed = 1; break; case wxGrid::wxGridSelectColumns: topRow = 0; bottomRow = m_grid->GetNumberRows() - 1; + allowed = 1; break; case wxGrid::wxGridSelectRowsOrColumns: // block selection doesn't make sense for this mode, we could only // select the entire grid but this wouldn't be useful - return; + allowed = 0; + break; } - if ( topRow > bottomRow ) - { - int temp = topRow; - topRow = bottomRow; - bottomRow = temp; - } - - if ( leftCol > rightCol ) - { - int temp = leftCol; - leftCol = rightCol; - rightCol = temp; - } - - // Handle single cell selection in SelectCell. - // (MB: added check for selection mode here to prevent - // crashes if, for example, we are select rows and the - // grid only has 1 col) - if ( m_selectionMode == wxGrid::wxGridSelectCells && - topRow == bottomRow && leftCol == rightCol ) - { - SelectCell( topRow, leftCol, kbd, sendEvent ); - } - - size_t count, n; - - if ( m_selectionMode == wxGrid::wxGridSelectRows ) - { - // find out which rows are already selected: - wxArrayInt alreadyselected; - alreadyselected.Add(0,bottomRow-topRow+1); - for( n = 0; n < m_rowSelection.GetCount(); n++) - { - int row = m_rowSelection[n]; - if( (row >= topRow) && (row <= bottomRow) ) - { - alreadyselected[ row - topRow ]=1; - } - } - - // add the newly selected rows: - for ( int row = topRow; row <= bottomRow; row++ ) - { - if ( alreadyselected[ row - topRow ] == 0 ) - { - m_rowSelection.Add( row ); - } - } - } - else if ( m_selectionMode == wxGrid::wxGridSelectColumns ) - { - // find out which columns are already selected: - wxArrayInt alreadyselected; - alreadyselected.Add(0,rightCol-leftCol+1); - for( n = 0; n < m_colSelection.GetCount(); n++) - { - int col = m_colSelection[n]; - if( (col >= leftCol) && (col <= rightCol) ) - { - alreadyselected[ col - leftCol ]=1; - } - } - - // add the newly selected columns: - for ( int col = leftCol; col <= rightCol; col++ ) - { - if ( alreadyselected[ col - leftCol ] == 0 ) - { - m_colSelection.Add( col ); - } - } - } - else - { - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( topRow, leftCol, bottomRow, rightCol, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // If a block containing the selection is already selected, return, - // if a block contained in the selection is found, remove it. - - count = m_blockSelectionTopLeft.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - switch ( BlockContain( coords1.GetRow(), coords1.GetCol(), - coords2.GetRow(), coords2.GetCol(), - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - // If a row containing the selection is already selected, return, - // if a row contained in newly selected block is found, remove it. - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - switch ( BlockContain( m_rowSelection[n], 0, - m_rowSelection[n], m_grid->GetNumberCols() - 1, - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_rowSelection.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - // Same for columns. - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - switch ( BlockContain( 0, m_colSelection[n], - m_grid->GetNumberRows() - 1, m_colSelection[n], - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_colSelection.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - m_blockSelectionTopLeft.Add( wxGridCellCoords( topRow, leftCol ) ); - m_blockSelectionBottomRight.Add( wxGridCellCoords( bottomRow, rightCol ) ); - } - // Update View: - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(topRow, leftCol, bottomRow, rightCol); - } - - // Send Event, if not disabled. - if ( sendEvent ) - { - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( topRow, leftCol ), - wxGridCellCoords( bottomRow, rightCol ), - true, - kbd); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } -} - -void wxGridSelection::SelectCell( int row, int col, - const wxKeyboardState& kbd, - bool sendEvent ) -{ - if ( IsInSelection ( row, col ) ) + wxASSERT_MSG(allowed != -1, "unknown selection mode?"); + if ( !allowed ) return; - wxGridCellCoords selectedTopLeft, selectedBottomRight; - if ( m_selectionMode == wxGrid::wxGridSelectRows ) - { - m_rowSelection.Add( row ); - selectedTopLeft = wxGridCellCoords( row, 0 ); - selectedBottomRight = wxGridCellCoords( row, m_grid->GetNumberCols() - 1 ); - } - else if ( m_selectionMode == wxGrid::wxGridSelectColumns ) - { - m_colSelection.Add( col ); - selectedTopLeft = wxGridCellCoords( 0, col ); - selectedBottomRight = wxGridCellCoords( m_grid->GetNumberRows() - 1, col ); - } - else - { - m_cellSelection.Add( wxGridCellCoords( row, col ) ); - selectedTopLeft = wxGridCellCoords( row, col ); - selectedBottomRight = wxGridCellCoords( row, col ); - } - - // Update View: - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(selectedTopLeft, selectedBottomRight); - } - - // Send event - if (sendEvent) - { - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - selectedTopLeft, - selectedBottomRight, - true, - kbd); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } + Select(wxGridBlockCoords(topRow, leftCol, bottomRow, rightCol).Canonicalize(), + kbd, sendEvent); } void @@ -639,239 +190,146 @@ wxGridSelection::ToggleCellSelection(int row, int col, // if the cell is not selected, select it if ( !IsInSelection ( row, col ) ) { - SelectCell(row, col, kbd); + SelectBlock(row, col, row, col, kbd); return; } - // otherwise deselect it. This can be simple or more or - // less difficult, depending on how the cell is selected. + // otherwise deselect it. + DeselectBlock(wxGridBlockCoords(row, col, row, col), kbd); +} + + +void +wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, + bool sendEvent) +{ + const wxGridBlockCoords canonicalizedBlock = block.Canonicalize(); + size_t count, n; - // The simplest case: The cell is contained in m_cellSelection - // Then it can't be contained in rows/cols/block (since those - // would remove the cell from m_cellSelection on creation), so - // we just have to remove it from m_cellSelection. - - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - const wxGridCellCoords& sel = m_cellSelection[n]; - if ( row == sel.GetRow() && col == sel.GetCol() ) - { - wxGridCellCoords coords = m_cellSelection[n]; - m_cellSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(coords, coords); - } - - // Send event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, col ), - wxGridCellCoords( row, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - - return; - } - } - } - - // The most difficult case: The cell is member of one or even several - // blocks. Split each such block in up to 4 new parts, that don't - // contain the cell to be selected, like this: + // If the selected block intersects with the deselection block, split it + // in up to 4 new parts, that don't contain the block to be selected, like + // this (for rows): // |---------------------------| // | | // | part 1 | // | | // |---------------------------| - // | part 3 |x| part 4 | + // | part 3 | x | part 4 | // |---------------------------| // | | // | part 2 | // | | // |---------------------------| - // (The x marks the newly deselected cell). - // Note: in row selection mode, we only need part1 and part2; - // in column selection mode, we only need part 3 and part4, - // which are expanded to whole columns automatically! + // And for columns: + // |---------------------------| + // | | | | + // | | part 3 | | + // | | | | + // | |---------| | + // | part 1 | x | part 2 | + // | |---------| | + // | | | | + // | | part 4 | | + // | | | | + // |---------------------------| + // (The x marks the newly deselected block). + // Note: in row/column selection mode, we only need part1 and part2 - count = m_blockSelectionTopLeft.GetCount(); + // Blocks to refresh. + wxVectorGridBlockCoords refreshBlocks; + // Add the deselected block. + refreshBlocks.push_back(canonicalizedBlock); + + count = m_selection.size(); for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - int topRow = coords1.GetRow(); - int leftCol = coords1.GetCol(); - int bottomRow = coords2.GetRow(); - int rightCol = coords2.GetCol(); + const wxGridBlockCoords& selBlock = m_selection[n]; - if ( BlockContainsCell( topRow, leftCol, bottomRow, rightCol, row, col ) ) + // Whether blocks intersect. + if ( !m_selection[n].Intersects(canonicalizedBlock) ) + continue; + + int splitOrientation = wxHORIZONTAL; + switch ( m_selectionMode ) { - // remove the block - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; + case wxGrid::wxGridSelectCells: + if ( selBlock.GetLeftCol() == 0 && + selBlock.GetRightCol() == m_grid->GetNumberCols() - 1 ) + break; - // add up to 4 smaller blocks and set update region - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - if ( topRow < row ) - SelectBlockNoEvent(topRow, leftCol, row - 1, rightCol); - if ( bottomRow > row ) - SelectBlockNoEvent(row + 1, leftCol, bottomRow, rightCol); - } + if ( selBlock.GetTopRow() == 0 && + selBlock.GetBottomRow() == m_grid->GetNumberRows() - 1 ) + splitOrientation = wxVERTICAL; - if ( m_selectionMode != wxGrid::wxGridSelectRows ) + break; + + case wxGrid::wxGridSelectColumns: + splitOrientation = wxVERTICAL; + break; + + case wxGrid::wxGridSelectRowsOrColumns: + if ( selBlock.GetLeftCol() == 0 && + selBlock.GetRightCol() == m_grid->GetNumberCols() - 1 ) + break; + + splitOrientation = wxVERTICAL; + break; + } + + // remove the block + m_selection.erase(m_selection.begin() + n); + n--; + count--; + + wxGridBlockDiffResult result = + selBlock.Difference(canonicalizedBlock, splitOrientation); + + for ( int i = 0; i < 2; ++i ) + { + const wxGridBlockCoords& part = result.m_parts[i]; + if ( part != wxGridNoBlockCoords ) + SelectBlockNoEvent(part); + } + + for ( int i = 2; i < 4; ++i ) + { + const wxGridBlockCoords& part = result.m_parts[i]; + if ( part != wxGridNoBlockCoords ) { - if ( leftCol < col ) - SelectBlockNoEvent(row, leftCol, row, col - 1); - if ( rightCol > col ) - SelectBlockNoEvent(row, col + 1, row, rightCol); + // Add part[2] and part[3] only in the cells selection mode. + if ( m_selectionMode == wxGrid::wxGridSelectCells ) + SelectBlockNoEvent(part); + else + MergeOrAddBlock(refreshBlocks, part); } } } - bool rowSelectionWasChanged = false; - // remove a cell from a row, adding up to two new blocks - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) + // Refresh the screen and send events. + count = refreshBlocks.size(); + for ( n = 0; n < count; n++ ) { - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( m_rowSelection[n] == row ) - { - m_rowSelection.RemoveAt(n); - n--; - count--; + const wxGridBlockCoords& refBlock = refreshBlocks[n]; - rowSelectionWasChanged = true; - - if (m_selectionMode == wxGrid::wxGridSelectCells) - { - if ( col > 0 ) - SelectBlockNoEvent(row, 0, row, col - 1); - if ( col < m_grid->GetNumberCols() - 1 ) - SelectBlockNoEvent( row, col + 1, - row, m_grid->GetNumberCols() - 1); - } - } - } - } - - bool colSelectionWasChanged = false; - // remove a cell from a column, adding up to two new blocks - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( m_colSelection[n] == col ) - { - m_colSelection.RemoveAt(n); - n--; - count--; - - colSelectionWasChanged = true; - - if (m_selectionMode == wxGrid::wxGridSelectCells) - { - if ( row > 0 ) - SelectBlockNoEvent(0, col, row - 1, col); - if ( row < m_grid->GetNumberRows() - 1 ) - SelectBlockNoEvent(row + 1, col, - m_grid->GetNumberRows() - 1, col); - } - } - } - } - - // Refresh the screen and send the event; according to m_selectionMode, - // we need to either update only the cell, or the whole row/column. - wxRect r; - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { if ( !m_grid->GetBatchCount() ) { - m_grid->RefreshBlock(row, col, row, col); + m_grid->RefreshBlock(refBlock.GetTopRow(), refBlock.GetLeftCol(), + refBlock.GetBottomRow(), refBlock.GetRightCol()); } - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, col ), - wxGridCellCoords( row, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - else // rows/columns selection mode - { - if ( m_selectionMode != wxGrid::wxGridSelectColumns && - rowSelectionWasChanged ) + if ( sendEvent ) { - int numCols = m_grid->GetNumberCols(); - for ( int colFrom = 0, colTo = 0; colTo <= numCols; ++colTo ) - { - if ( m_colSelection.Index(colTo) >= 0 || colTo == numCols ) - { - if ( colFrom < colTo ) - { - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(row, colFrom, row, colTo - 1); - } - - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, colFrom ), - wxGridCellCoords( row, colTo - 1 ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - - colFrom = colTo + 1; - } - } - } - - if ( m_selectionMode != wxGrid::wxGridSelectRows && - colSelectionWasChanged ) - { - int numRows = m_grid->GetNumberRows(); - for ( int rowFrom = 0, rowTo = 0; rowTo <= numRows; ++rowTo ) - { - if ( m_rowSelection.Index(rowTo) >= 0 || rowTo == numRows ) - { - if (rowFrom < rowTo) - { - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(rowFrom, col, rowTo - 1, col); - } - - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( rowFrom, col ), - wxGridCellCoords( rowTo - 1, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - - rowFrom = rowTo + 1; - } - } + wxGridRangeSelectEvent gridEvt(m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + refBlock.GetTopLeft(), + refBlock.GetBottomRight(), + false, + kbd); + m_grid->GetEventHandler()->ProcessEvent(gridEvt); } } } @@ -882,33 +340,14 @@ void wxGridSelection::ClearSelection() wxRect r; wxGridCellCoords coords1, coords2; - // deselect all individual cells and update the screen - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - while ( ( n = m_cellSelection.GetCount() ) > 0) - { - n--; - coords1 = m_cellSelection[n]; - m_cellSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(coords1, coords1); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - // deselect all blocks and update the screen - while ( ( n = m_blockSelectionTopLeft.GetCount() ) > 0) + while ( ( n = m_selection.size() ) > 0) { n--; - coords1 = m_blockSelectionTopLeft[n]; - coords2 = m_blockSelectionBottomRight[n]; - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + const wxGridBlockCoords& block = m_selection[n]; + coords1 = block.GetTopLeft(); + coords2 = block.GetBottomRight(); + m_selection.erase(m_selection.begin() + n); if ( !m_grid->GetBatchCount() ) { m_grid->RefreshBlock(coords1, coords2); @@ -919,44 +358,6 @@ void wxGridSelection::ClearSelection() } } - // deselect all rows and update the screen - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - while ( ( n = m_rowSelection.GetCount() ) > 0) - { - n--; - int row = m_rowSelection[n]; - m_rowSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(row, 0, row, m_grid->GetNumberCols() - 1); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - - // deselect all columns and update the screen - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - while ( ( n = m_colSelection.GetCount() ) > 0) - { - n--; - int col = m_colSelection[n]; - m_colSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(0, col, m_grid->GetNumberRows() - 1, col); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - // One deselection event, indicating deselection of _all_ cells. // (No finer grained events for each of the smaller regions // deselected above!) @@ -975,54 +376,23 @@ void wxGridSelection::ClearSelection() void wxGridSelection::UpdateRows( size_t pos, int numRows ) { - size_t count = m_cellSelection.GetCount(); + size_t count = m_selection.size(); size_t n; - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - wxCoord row = coords.GetRow(); - if ((size_t)row >= pos) - { - if (numRows > 0) - { - // If rows inserted, increase row counter where necessary - coords.SetRow(row + numRows); - } - else if (numRows < 0) - { - // If rows deleted ... - if ((size_t)row >= pos - numRows) - { - // ...either decrement row counter (if row still exists)... - coords.SetRow(row + numRows); - } - else - { - // ...or remove the attribute - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - } - count = m_blockSelectionTopLeft.GetCount(); for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - wxCoord row1 = coords1.GetRow(); - wxCoord row2 = coords2.GetRow(); + wxGridBlockCoords& block = m_selection[n]; + wxCoord row1 = block.GetTopRow(); + wxCoord row2 = block.GetBottomRow(); if ((size_t)row2 >= pos) { if (numRows > 0) { // If rows inserted, increase row counter where necessary - coords2.SetRow( row2 + numRows ); + block.SetBottomRow( row2 + numRows ); if ((size_t)row1 >= pos) - coords1.SetRow( row1 + numRows ); + block.SetTopRow( row1 + numRows ); } else if (numRows < 0) { @@ -1030,9 +400,9 @@ void wxGridSelection::UpdateRows( size_t pos, int numRows ) if ((size_t)row2 >= pos - numRows) { // ...either decrement row counter (if row still exists)... - coords2.SetRow( row2 + numRows ); + block.SetBottomRow( row2 + numRows ); if ((size_t)row1 >= pos) - coords1.SetRow( wxMax(row1 + numRows, (int)pos) ); + block.SetTopRow( wxMax(row1 + numRows, (int)pos) ); } else @@ -1040,101 +410,38 @@ void wxGridSelection::UpdateRows( size_t pos, int numRows ) if ((size_t)row1 >= pos) { // ...or remove the attribute - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + m_selection.erase(m_selection.begin() + n); n--; count--; } else - coords2.SetRow( pos ); + block.SetBottomRow( pos ); } } } } - - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - int rowOrCol_ = m_rowSelection[n]; - - if ((size_t) rowOrCol_ >= pos) - { - if ( numRows > 0 ) - { - m_rowSelection[n] += numRows; - } - else if ( numRows < 0 ) - { - if ((size_t)rowOrCol_ >= (pos - numRows)) - m_rowSelection[n] += numRows; - else - { - m_rowSelection.RemoveAt( n ); - n--; - count--; - } - } - } - } - // No need to touch selected columns, unless we removed _all_ - // rows, in this case, we remove all columns from the selection. - - if ( !m_grid->GetNumberRows() ) - m_colSelection.Clear(); } void wxGridSelection::UpdateCols( size_t pos, int numCols ) { - size_t count = m_cellSelection.GetCount(); + size_t count = m_selection.size(); size_t n; for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords = m_cellSelection[n]; - wxCoord col = coords.GetCol(); - if ((size_t)col >= pos) - { - if (numCols > 0) - { - // If rows inserted, increase row counter where necessary - coords.SetCol(col + numCols); - } - else if (numCols < 0) - { - // If rows deleted ... - if ((size_t)col >= pos - numCols) - { - // ...either decrement row counter (if row still exists)... - coords.SetCol(col + numCols); - } - else - { - // ...or remove the attribute - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - } - - count = m_blockSelectionTopLeft.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - wxCoord col1 = coords1.GetCol(); - wxCoord col2 = coords2.GetCol(); + wxGridBlockCoords& block = m_selection[n]; + wxCoord col1 = block.GetLeftCol(); + wxCoord col2 = block.GetRightCol(); if ((size_t)col2 >= pos) { if (numCols > 0) { // If rows inserted, increase row counter where necessary - coords2.SetCol(col2 + numCols); + block.SetRightCol(col2 + numCols); if ((size_t)col1 >= pos) - coords1.SetCol(col1 + numCols); + block.SetLeftCol(col1 + numCols); } else if (numCols < 0) { @@ -1142,9 +449,9 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) if ((size_t)col2 >= pos - numCols) { // ...either decrement col counter (if col still exists)... - coords2.SetCol(col2 + numCols); + block.SetRightCol(col2 + numCols); if ( (size_t) col1 >= pos) - coords1.SetCol( wxMax(col1 + numCols, (int)pos) ); + block.SetLeftCol( wxMax(col1 + numCols, (int)pos) ); } else @@ -1152,45 +459,133 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) if ((size_t)col1 >= pos) { // ...or remove the attribute - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + m_selection.erase(m_selection.begin() + n); n--; count--; } else - coords2.SetCol(pos); + block.SetRightCol(pos); } } } } +} - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) +wxGridCellCoordsArray wxGridSelection::GetCellSelection() const +{ + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray cells; + const size_t count = m_selection.size(); + cells.reserve(count); + for ( size_t n = 0; n < count; n++ ) { - int rowOrCol = m_colSelection[n]; - - if ((size_t)rowOrCol >= pos) + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetTopRow() == block.GetBottomRow() && + block.GetLeftCol() == block.GetRightCol() ) { - if ( numCols > 0 ) - m_colSelection[n] += numCols; - else if ( numCols < 0 ) + cells.push_back(block.GetTopLeft()); + } + } + return cells; +} + +wxGridCellCoordsArray wxGridSelection::GetBlockSelectionTopLeft() const +{ + // return blocks only in wxGridSelectCells selection mode + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray coords; + const size_t count = m_selection.size(); + coords.reserve(count); + for ( size_t n = 0; n < count; n++ ) + { + coords.push_back(m_selection[n].GetTopLeft()); + } + return coords; +} + +wxGridCellCoordsArray wxGridSelection::GetBlockSelectionBottomRight() const +{ + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray coords; + const size_t count = m_selection.size(); + coords.reserve(count); + for ( size_t n = 0; n < count; n++ ) + { + coords.push_back(m_selection[n].GetBottomRight()); + } + return coords; +} + +// For compatibility with the existing code, try reconstructing the selected +// rows/columns from the selected blocks we store internally. Of course, this +// only works well in the corresponding selection mode in which the user can +// only select the entire lines in the first place, as otherwise it's difficult to +// efficiently determine that a line is selected because all of its cells +// were selected one by one. But this should work well enough in practice and +// is, anyhow, the best we can do. +wxArrayInt wxGridSelection::GetRowSelection() const +{ + if ( m_selectionMode == wxGrid::wxGridSelectColumns ) + return wxArrayInt(); + + wxIntSortedArray uniqueRows(&CompareInts); + const size_t count = m_selection.size(); + for ( size_t n = 0; n < count; ++n ) + { + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetLeftCol() == 0 && + block.GetRightCol() == m_grid->GetNumberCols() - 1 ) + { + for ( int r = block.GetTopRow(); r <= block.GetBottomRow(); ++r ) { - if ((size_t)rowOrCol >= (pos - numCols)) - m_colSelection[n] += numCols; - else - { - m_colSelection.RemoveAt( n ); - n--; - count--; - } + uniqueRows.Add(r); } } } - // No need to touch selected rows, unless we removed _all_ - // columns, in this case, we remove all rows from the selection. - if ( !m_grid->GetNumberCols() ) - m_rowSelection.Clear(); + wxArrayInt result; + result.reserve(uniqueRows.size()); + for( size_t i = 0; i < uniqueRows.size(); ++i ) + { + result.push_back(uniqueRows[i]); + } + return result; +} + +// See comments for GetRowSelection(). +wxArrayInt wxGridSelection::GetColSelection() const +{ + if ( m_selectionMode == wxGrid::wxGridSelectRows ) + return wxArrayInt(); + + wxIntSortedArray uniqueRows(&CompareInts); + const size_t count = m_selection.size(); + for ( size_t n = 0; n < count; ++n ) + { + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetTopRow() == 0 && + block.GetBottomRow() == m_grid->GetNumberRows() - 1 ) + { + for ( int c = block.GetLeftCol(); c <= block.GetRightCol(); ++c ) + { + uniqueRows.Add(c); + } + } + } + + wxArrayInt result; + result.reserve(uniqueRows.size()); + for( size_t i = 0; i < uniqueRows.size(); ++i ) + { + result.push_back(uniqueRows[i]); + } + return result; } int wxGridSelection::BlockContain( int topRow1, int leftCol1, @@ -1211,4 +606,66 @@ int wxGridSelection::BlockContain( int topRow1, int leftCol1, return 0; } +void +wxGridSelection::Select(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, bool sendEvent) +{ + if (m_grid->GetNumberRows() == 0 || m_grid->GetNumberCols() == 0) + return; + + MergeOrAddBlock(m_selection, block); + + // Update View: + if ( !m_grid->GetBatchCount() ) + { + m_grid->RefreshBlock(block.GetTopLeft(), block.GetBottomRight()); + } + + // Send Event, if not disabled. + if ( sendEvent ) + { + wxGridRangeSelectEvent gridEvt( m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + block.GetTopLeft(), + block.GetBottomRight(), + true, + kbd); + m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + } +} + +void wxGridSelection::MergeOrAddBlock(wxVectorGridBlockCoords& blocks, + const wxGridBlockCoords& newBlock) +{ + // If a block containing the selection is already selected, return, + // if a block contained in the selection is found, remove it. + + size_t count = blocks.size(); + for ( size_t n = 0; n < count; n++ ) + { + const wxGridBlockCoords& block = blocks[n]; + + switch ( BlockContain(block.GetTopRow(), block.GetLeftCol(), + block.GetBottomRow(), block.GetRightCol(), + newBlock.GetTopRow(), newBlock.GetLeftCol(), + newBlock.GetBottomRow(), newBlock.GetRightCol()) ) + { + case 1: + return; + + case -1: + blocks.erase(blocks.begin() + n); + n--; + count--; + break; + + default: + break; + } + } + + blocks.push_back(newBlock); +} + #endif From f1c3dfdca6d097fa9e27f3e6028416395c895601 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Fri, 27 Mar 2020 23:08:31 +0700 Subject: [PATCH 06/65] Do not merge wxGrid selection blocks Remove blocks merging because it complicates the behavior at the user point of view and the code. --- src/generic/gridsel.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 20b24f1c52..b7f27405ee 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -613,7 +613,7 @@ wxGridSelection::Select(const wxGridBlockCoords& block, if (m_grid->GetNumberRows() == 0 || m_grid->GetNumberCols() == 0) return; - MergeOrAddBlock(m_selection, block); + m_selection.push_back(block); // Update View: if ( !m_grid->GetBatchCount() ) @@ -638,18 +638,12 @@ wxGridSelection::Select(const wxGridBlockCoords& block, void wxGridSelection::MergeOrAddBlock(wxVectorGridBlockCoords& blocks, const wxGridBlockCoords& newBlock) { - // If a block containing the selection is already selected, return, - // if a block contained in the selection is found, remove it. - size_t count = blocks.size(); for ( size_t n = 0; n < count; n++ ) { const wxGridBlockCoords& block = blocks[n]; - switch ( BlockContain(block.GetTopRow(), block.GetLeftCol(), - block.GetBottomRow(), block.GetRightCol(), - newBlock.GetTopRow(), newBlock.GetLeftCol(), - newBlock.GetBottomRow(), newBlock.GetRightCol()) ) + switch ( block.ContainBlock(newBlock) ) { case 1: return; From cdf3187fe51de08ba02fc85813748fb1a1f5cf8a Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Thu, 26 Mar 2020 05:50:25 +0700 Subject: [PATCH 07/65] Improve rows, columns and cells deselection in wxGrid Use DeselectBlock() instead of ToggleCellSelection() to improve execution speed and make the code more clean. --- include/wx/generic/grid.h | 1 - src/generic/grid.cpp | 45 ++++++++++++--------------------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index f90fff3954..f8b524e6f4 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2687,7 +2687,6 @@ private: void DoGridProcessTab(wxKeyboardState& kbdState); // common implementations of methods defined for both rows and columns - void DeselectLine(int line, const wxGridOperations& oper); bool DoEndDragResizeLine(const wxGridOperations& oper, wxGridWindow *gridWindow); int PosToLinePos(int pos, bool clipToMinMax, const wxGridOperations& oper, diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 1fed7a841d..6d56e2b556 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10198,47 +10198,30 @@ void wxGrid::SelectAll() // cell, row and col deselection // ---------------------------------------------------------------------------- -void wxGrid::DeselectLine(int line, const wxGridOperations& oper) -{ - if ( !m_selection ) - return; - - const wxGridSelectionModes mode = m_selection->GetSelectionMode(); - if ( mode == oper.GetSelectionMode() || - mode == wxGrid::wxGridSelectRowsOrColumns ) - { - const wxGridCellCoords c(oper.MakeCoords(line, 0)); - if ( m_selection->IsInSelection(c) ) - m_selection->ToggleCellSelection(c); - } - else if ( mode != oper.Dual().GetSelectionMode() ) - { - const int nOther = oper.Dual().GetNumberOfLines(this, NULL); - for ( int i = 0; i < nOther; i++ ) - { - const wxGridCellCoords c(oper.MakeCoords(line, i)); - if ( m_selection->IsInSelection(c) ) - m_selection->ToggleCellSelection(c); - } - } - //else: can only select orthogonal lines so no lines in this direction - // could have been selected anyhow -} - void wxGrid::DeselectRow(int row) { - DeselectLine(row, wxGridRowOperations()); + wxCHECK_RET( row >= 0 && row < m_numRows, wxT("invalid row index") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(row, 0, row, m_numRows - 1)); } void wxGrid::DeselectCol(int col) { - DeselectLine(col, wxGridColumnOperations()); + wxCHECK_RET( col >= 0 && col < m_numCols, wxT("invalid column index") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(0, col, m_numCols - 1, col)); } void wxGrid::DeselectCell( int row, int col ) { - if ( m_selection && m_selection->IsInSelection(row, col) ) - m_selection->ToggleCellSelection(row, col); + wxCHECK_RET( row >= 0 && row < m_numRows && + col >= 0 && col < m_numCols, + wxT("invalid cell coords") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(row, col, row, col)); } bool wxGrid::IsSelection() const From d4919d3334d9200fb56b120f2aa0910050a8cc5d Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Fri, 27 Mar 2020 22:40:20 +0700 Subject: [PATCH 08/65] Remove wxGridSelection::ToggleCellSelection() Remove the function because it's not usefull anymore and used only in one place. --- include/wx/generic/gridsel.h | 8 -------- src/generic/grid.cpp | 16 +++++++++++++++- src/generic/gridsel.cpp | 17 ----------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 18016adfcd..dab94952fb 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -52,14 +52,6 @@ public: kbd, sendEvent); } - void ToggleCellSelection(int row, int col, - const wxKeyboardState& kbd = wxKeyboardState()); - void ToggleCellSelection(const wxGridCellCoords& coords, - const wxKeyboardState& kbd = wxKeyboardState()) - { - ToggleCellSelection(coords.GetRow(), coords.GetCol(), kbd); - } - void DeselectBlock(const wxGridBlockCoords& block, const wxKeyboardState& kbd = wxKeyboardState(), bool sendEvent = true ); diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 6d56e2b556..f0b4863f98 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4577,7 +4577,21 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, { if ( m_selection ) { - m_selection->ToggleCellSelection(coords, event); + if ( !m_selection->IsInSelection(coords) ) + { + // If the cell is not selected, select it. + m_selection->SelectBlock(coords.GetRow(), coords.GetCol(), + coords.GetRow(), coords.GetCol(), + event); + } + else + { + // Otherwise deselect it. + m_selection->DeselectBlock( + wxGridBlockCoords(coords.GetRow(), coords.GetCol(), + coords.GetRow(), coords.GetCol()), + event); + } } m_selectedBlockTopLeft = wxGridNoCellCoords; diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index b7f27405ee..e8530388a8 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -183,23 +183,6 @@ void wxGridSelection::SelectBlock( int topRow, int leftCol, kbd, sendEvent); } -void -wxGridSelection::ToggleCellSelection(int row, int col, - const wxKeyboardState& kbd) -{ - // if the cell is not selected, select it - if ( !IsInSelection ( row, col ) ) - { - SelectBlock(row, col, row, col, kbd); - - return; - } - - // otherwise deselect it. - DeselectBlock(wxGridBlockCoords(row, col, row, col), kbd); -} - - void wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, const wxKeyboardState& kbd, From 779d3f7f1772f6cdef06086e6b1eb9a101191b8f Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Tue, 25 Feb 2020 23:45:42 +0700 Subject: [PATCH 09/65] Clear the selection in wxGrid::SetTable The selection in m_selection was not considered at all so it was worked not correctly any way. --- src/generic/grid.cpp | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index f0b4863f98..2d5ab5dc1b 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -2753,7 +2753,6 @@ wxGrid::SetTable(wxGridTableBase *table, bool takeOwnership, wxGrid::wxGridSelectionModes selmode ) { - bool checkSelection = false; if ( m_created ) { // stop all processing @@ -2785,7 +2784,6 @@ wxGrid::SetTable(wxGridTableBase *table, m_numCols = 0; m_numFrozenRows = 0; m_numFrozenCols = 0; - checkSelection = true; // kill row and column size arrays m_colWidths.Empty(); @@ -2809,28 +2807,6 @@ wxGrid::SetTable(wxGridTableBase *table, SetNativeHeaderColCount(); m_selection = new wxGridSelection( this, selmode ); - if (checkSelection) - { - // If the newly set table is smaller than the - // original one current cell and selection regions - // might be invalid, - m_selectedBlockCorner = wxGridNoCellCoords; - m_currentCellCoords = - wxGridCellCoords(wxMin(m_numRows, m_currentCellCoords.GetRow()), - wxMin(m_numCols, m_currentCellCoords.GetCol())); - if (m_selectedBlockTopLeft.GetRow() >= m_numRows || - m_selectedBlockTopLeft.GetCol() >= m_numCols) - { - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - } - else - m_selectedBlockBottomRight = - wxGridCellCoords(wxMin(m_numRows, - m_selectedBlockBottomRight.GetRow()), - wxMin(m_numCols, - m_selectedBlockBottomRight.GetCol())); - } CalcDimensions(); m_created = true; From 206bad9ba088314a6afae8486de3e561eb13120c Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Wed, 1 Apr 2020 23:29:48 +0700 Subject: [PATCH 10/65] Remove wxEVT_GRID_RANGE_SELECT handling by wxGrid::SendEvent() VZ: [...] this is not used by wxGrid itself and SendEvent() is not part of the public API, so we never made any promises about being able to call it with wxEVT_GRID_RANGE_SELECT. --- src/generic/grid.cpp | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 2d5ab5dc1b..73a34ec31a 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5287,24 +5287,10 @@ wxGrid::SendEvent(wxEventType type, { bool claimed, vetoed; - if ( type == wxEVT_GRID_RANGE_SELECT ) - { - // Right now, it should _never_ end up here! - wxGridRangeSelectEvent gridEvt( GetId(), - type, - this, - m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - true, - mouseEv); - - claimed = GetEventHandler()->ProcessEvent(gridEvt); - vetoed = !gridEvt.IsAllowed(); - } - else if ( type == wxEVT_GRID_LABEL_LEFT_CLICK || - type == wxEVT_GRID_LABEL_LEFT_DCLICK || - type == wxEVT_GRID_LABEL_RIGHT_CLICK || - type == wxEVT_GRID_LABEL_RIGHT_DCLICK ) + if ( type == wxEVT_GRID_LABEL_LEFT_CLICK || + type == wxEVT_GRID_LABEL_LEFT_DCLICK || + type == wxEVT_GRID_LABEL_RIGHT_CLICK || + type == wxEVT_GRID_LABEL_RIGHT_DCLICK ) { wxPoint pos = mouseEv.GetPosition(); From 72e7bde306c20af445b56bab5582fef227d386f2 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Thu, 26 Mar 2020 04:11:59 +0700 Subject: [PATCH 11/65] Refactor wxGrid::MakeCellVisible() to scroll in only one direction Allow -1 for a row or a column single parameter. --- src/generic/grid.cpp | 87 +++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 73a34ec31a..b142ce6cc0 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -7829,22 +7829,54 @@ void wxGrid::MakeCellVisible( int row, int col ) { int xpos = -1, ypos = -1; - if ( row >= 0 && row < m_numRows && - col >= 0 && col < m_numCols ) + if ( row < -1 || row >= m_numRows || + col < -1 || col >= m_numCols ) + return; + + const bool processRow = row != -1; + const bool processCol = col != -1; + + // Get the cell rectangle in logical coords. + wxRect r; + wxGridWindow *gridWindow; + + if ( processRow && processCol ) { - // get the cell rectangle in logical coords - wxRect r( CellToRect( row, col ) ); + r = CellToRect(row, col); + gridWindow = CellToGridWindow(row, col); + } + else if ( processRow ) + { + r.SetTop(GetRowTop(row)); + r.SetHeight(GetRowHeight(row)); + gridWindow = row < m_numFrozenRows + ? m_frozenRowGridWin + : m_gridWin; + } + else if ( processCol ) + { + r.SetLeft(GetColLeft(col)); + r.SetWidth(GetColWidth(col)); + gridWindow = col < m_numFrozenCols + ? m_frozenColGridWin + : m_gridWin; + } + else + { + return; + } - wxGridWindow *gridWindow = CellToGridWindow(row, col); - wxPoint gridOffset = GetGridWindowOffset(gridWindow); + wxPoint gridOffset = GetGridWindowOffset(gridWindow); - // convert to device coords - int left, top, right, bottom; - CalcGridWindowScrolledPosition( r.GetLeft(), r.GetTop(), &left, &top, gridWindow ); - CalcGridWindowScrolledPosition( r.GetRight(), r.GetBottom(), &right, &bottom, gridWindow ); + if ( processRow ) + { + // Convert to device coords. + int top, bottom; + CalcGridWindowScrolledPosition(0, r.GetTop(), NULL, &top, gridWindow); + CalcGridWindowScrolledPosition(0, r.GetBottom(), NULL, &bottom, gridWindow); - int cw, ch; - gridWindow->GetClientSize( &cw, &ch ); + int ch; + gridWindow->GetClientSize(NULL, &ch); if ( top < gridOffset.y ) { @@ -7875,6 +7907,17 @@ void wxGrid::MakeCellVisible( int row, int col ) // so just add a full scroll unit... ypos += m_yScrollPixelsPerLine; } + } + + if ( processCol ) + { + // Convert to device coords. + int left, right; + CalcGridWindowScrolledPosition(r.GetLeft(), 0, &left, NULL, gridWindow); + CalcGridWindowScrolledPosition(r.GetRight(), 0, &right, NULL, gridWindow); + + int cw; + gridWindow->GetClientSize(&cw, NULL); // special handling for wide cells - show always left part of the cell! // Otherwise, e.g. when stepping from row to row, it would jump between @@ -7894,17 +7937,17 @@ void wxGrid::MakeCellVisible( int row, int col ) // see comment for ypos above xpos += m_xScrollPixelsPerLine; } - - if ( xpos != -1 || ypos != -1 ) - { - if ( xpos != -1 ) - xpos /= m_xScrollPixelsPerLine; - if ( ypos != -1 ) - ypos /= m_yScrollPixelsPerLine; - Scroll( xpos, ypos ); - AdjustScrollbars(); - } } + + if ( xpos == -1 && ypos == -1 ) + return; + + if ( xpos != -1 ) + xpos /= m_xScrollPixelsPerLine; + if ( ypos != -1 ) + ypos /= m_yScrollPixelsPerLine; + Scroll(xpos, ypos); + AdjustScrollbars(); } // From e1b9ece9a499393eb0616afd47bd80ebd66b01b5 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Wed, 4 Mar 2020 20:12:09 +0700 Subject: [PATCH 12/65] Edit the current wxGrid selection block Really edit the current selection block instead of storing the temporary information about the current selection and applying it on releasing Shift key or LKM. --- include/wx/generic/grid.h | 10 -- include/wx/generic/gridsel.h | 22 +++ include/wx/generic/private/grid.h | 13 ++ src/generic/grid.cpp | 289 ++++++++---------------------- src/generic/gridsel.cpp | 115 ++++++++++++ 5 files changed, 227 insertions(+), 222 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index f8b524e6f4..33e656fa39 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2508,16 +2508,6 @@ protected: { return SetCurrentCell( wxGridCellCoords(row, col) ); } - // this function is called to extend the block being currently selected - // from mouse and keyboard event handlers - void UpdateBlockBeingSelected(int blockStartRow, int blockStartCol, - int blockEndRow, int blockEndCol); - - void UpdateBlockBeingSelected(const wxGridCellCoords& blockStart, - const wxGridCellCoords& blockEnd) - { UpdateBlockBeingSelected(blockStart.GetRow(), blockStart.GetCol(), - blockEnd.GetRow(), blockEnd.GetCol()); } - virtual bool ShouldScrollToChildOnFocus(wxWindow* WXUNUSED(win)) wxOVERRIDE { return false; } diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index dab94952fb..c5e5df7812 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -61,6 +61,28 @@ public: void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); + // Extend (or shrink) the current selection block or select a new one. + // blockStart and blockEnd specifies the opposite corners of the currently + // edited selection block. In almost all cases blockStart equals to + // wxGrid::m_currentCellCoords (the exception is when we scrolled out from + // the top of the grid and select a column or scrolled right and select + // a row: in this case the lowest visible row/column will be set as + // current, not the first one). If the row or the column component of + // blockEnd parametr has value of -1, it means that the corresponding + // component of the current block should not be changed. + // Return true if the current block was actually changed or created. + bool ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd); + + + // Return the row of the current selection block if it exists and we can + // edit the block vertically. Otherwise return -1. + int GetCurrentBlockCornerRow() const; + // Return the column of the current selection block if it exists and we can + // edit the block horizontally. Otherwise return -1. + int GetCurrentBlockCornerCol() const; + wxGridCellCoordsArray GetCellSelection() const; wxGridCellCoordsArray GetBlockSelectionTopLeft() const; wxGridCellCoordsArray GetBlockSelectionBottomRight() const; diff --git a/include/wx/generic/private/grid.h b/include/wx/generic/private/grid.h index 216491fa89..82703c4b9d 100644 --- a/include/wx/generic/private/grid.h +++ b/include/wx/generic/private/grid.h @@ -793,6 +793,19 @@ public: // boundary, i.e. is the first/last row/column virtual bool IsAtBoundary(const wxGridCellCoords& coords) const = 0; + // Check if the component of this point in our direction is + // valid, i.e. not -1 + bool IsValid(const wxGridCellCoords& coords) const + { + return m_oper.Select(coords) != -1; + } + + // Make the coordinates with the other component value of -1. + wxGridCellCoords MakeWholeLineCoords(const wxGridCellCoords& coords) const + { + return m_oper.MakeCoords(m_oper.Select(coords), -1); + } + // Increment the component of this point in our direction virtual void Advance(wxGridCellCoords& coords) const = 0; diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index b142ce6cc0..22d9ec2ae0 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -152,23 +152,6 @@ wxDEFINE_EVENT( wxEVT_GRID_EDITOR_HIDDEN, wxGridEvent ); wxDEFINE_EVENT( wxEVT_GRID_EDITOR_CREATED, wxGridEditorCreatedEvent ); wxDEFINE_EVENT( wxEVT_GRID_TABBING, wxGridEvent ); -// ---------------------------------------------------------------------------- -// private helpers -// ---------------------------------------------------------------------------- - -namespace -{ - - // ensure that first is less or equal to second, swapping the values if - // necessary - void EnsureFirstLessThanSecond(int& first, int& second) - { - if ( first > second ) - wxSwap(first, second); - } - -} // anonymous namespace - // ============================================================================ // implementation // ============================================================================ @@ -2916,10 +2899,6 @@ void wxGrid::Init() m_currentCellCoords = wxGridNoCellCoords; - m_selectedBlockTopLeft = - m_selectedBlockBottomRight = - m_selectedBlockCorner = wxGridNoCellCoords; - m_selectionBackground = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); m_selectionForeground = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); @@ -3763,12 +3742,13 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo { if ( event.ShiftDown() ) { - m_selection->SelectBlock + m_selection->ExtendOrCreateCurrentBlock ( - m_currentCellCoords.GetRow(), 0, - row, GetNumberCols() - 1, + wxGridCellCoords(m_currentCellCoords.GetRow(), 0), + wxGridCellCoords(row, GetNumberCols() - 1), event ); + MakeCellVisible(row, -1); } else { @@ -4137,12 +4117,13 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo { if ( event.ShiftDown() ) { - m_selection->SelectBlock + m_selection->ExtendOrCreateCurrentBlock ( - 0, m_currentCellCoords.GetCol(), - GetNumberRows() - 1, col, + wxGridCellCoords(0, m_currentCellCoords.GetCol()), + wxGridCellCoords(GetNumberRows() - 1, col), event ); + MakeCellVisible(-1, col); } else { @@ -4463,11 +4444,15 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, switch ( event.GetModifiers() ) { case wxMOD_CONTROL: - if ( m_selectedBlockCorner == wxGridNoCellCoords) - m_selectedBlockCorner = coords; if ( isFirstDrag ) SetGridCursor(coords); - UpdateBlockBeingSelected(m_currentCellCoords, coords); + if ( m_selection ) + { + m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, + coords, + event); + MakeCellVisible(coords); + } break; case wxMOD_NONE: @@ -4475,9 +4460,6 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, { if ( isFirstDrag ) { - if ( m_selectedBlockCorner == wxGridNoCellCoords) - m_selectedBlockCorner = coords; - // if event is handled by user code, no further processing if ( SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) != 0 ) performDefault = false; @@ -4485,8 +4467,13 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, return performDefault; } } - - UpdateBlockBeingSelected(m_currentCellCoords, coords); + if ( m_selection ) + { + m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, + coords, + event); + MakeCellVisible(coords); + } break; default: @@ -4540,8 +4527,10 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, { if ( m_selection ) { - m_selection->SelectBlock(m_currentCellCoords, coords, event); - m_selectedBlockCorner = coords; + m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, + coords, + event); + MakeCellVisible(coords); } } else if ( XToEdgeOfCol(pos.x) < 0 && YToEdgeOfRow(pos.y) < 0 ) @@ -4569,10 +4558,6 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, event); } } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - m_selectedBlockCorner = coords; } else { @@ -4640,19 +4625,8 @@ wxGrid::DoGridCellLeftUp(wxMouseEvent& event, m_waitForSlowClick = false; } - else if ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords ) + else if ( m_selection && m_selection->IsSelection() ) { - if ( m_selection ) - { - m_selection->SelectBlock( m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - event ); - } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - // Show the edit control, if it has been hidden for // drag-shrinking. ShowCellEditControl(); @@ -5747,13 +5721,16 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) } else { + int currentBlockRow = -1; + if ( m_selection ) + currentBlockRow = m_selection->GetCurrentBlockCornerRow(); + // If we're selecting, continue in the same row, which // may well be different from the one in which we // started selecting. - if ( event.ShiftDown() && - m_selectedBlockCorner != wxGridNoCellCoords ) + if ( event.ShiftDown() && currentBlockRow != -1 ) { - row = m_selectedBlockCorner.GetRow(); + row = currentBlockRow; } else // Just use the current row. { @@ -5782,8 +5759,11 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) if ( event.ShiftDown() ) { - UpdateBlockBeingSelected(m_currentCellCoords, - wxGridCellCoords(row, col)); + if ( m_selection ) + m_selection->ExtendOrCreateCurrentBlock( + m_currentCellCoords, + wxGridCellCoords(row, col), + event); MakeCellVisible(row, col); } else @@ -5842,28 +5822,9 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) m_inOnKeyDown = false; } -void wxGrid::OnKeyUp( wxKeyEvent& event ) +void wxGrid::OnKeyUp( wxKeyEvent& WXUNUSED(event) ) { // try local handlers - // - if ( event.GetKeyCode() == WXK_SHIFT ) - { - if ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords ) - { - if ( m_selection ) - { - m_selection->SelectBlock( - m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - event); - } - } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - m_selectedBlockCorner = wxGridNoCellCoords; - } } void wxGrid::OnChar( wxKeyEvent& event ) @@ -6033,122 +5994,6 @@ bool wxGrid::SetCurrentCell( const wxGridCellCoords& coords ) return true; } -void -wxGrid::UpdateBlockBeingSelected(int blockStartRow, int blockStartCol, - int blockEndRow, int blockEndCol) -{ - m_selectedBlockCorner = wxGridCellCoords(blockEndRow, blockEndCol); - MakeCellVisible(m_selectedBlockCorner); - - int topRow = wxMin(blockStartRow, blockEndRow); - int leftCol = wxMin(blockStartCol, blockEndCol); - int bottomRow = wxMax(blockStartRow, blockEndRow); - int rightCol = wxMax(blockStartCol, blockEndCol); - - if ( m_selection ) - { - switch ( m_selection->GetSelectionMode() ) - { - default: - wxFAIL_MSG( "unknown selection mode" ); - wxFALLTHROUGH; - - case wxGridSelectCells: - // arbitrary blocks selection allowed so just use the cell - // coordinates as is - break; - - case wxGridSelectRows: - // only full rows selection allowed, ensure that we do select - // full rows - leftCol = 0; - rightCol = GetNumberCols() - 1; - break; - - case wxGridSelectColumns: - // same as above but for columns - topRow = 0; - bottomRow = GetNumberRows() - 1; - break; - - case wxGridSelectRowsOrColumns: - // in this mode we can select only full rows or full columns so - // it doesn't make sense to select blocks at all (and we can't - // extend the block because there is no preferred direction, we - // could only extend it to cover the entire grid but this is - // not useful) - return; - } - } - - wxGridCellCoords updateTopLeft = wxGridCellCoords(topRow, leftCol), - updateBottomRight = wxGridCellCoords(bottomRow, rightCol); - - // First the case that we selected a completely new area - if ( m_selectedBlockTopLeft == wxGridNoCellCoords || - m_selectedBlockBottomRight == wxGridNoCellCoords ) - { - RefreshBlock(topRow, leftCol, bottomRow, rightCol); - } - - // Now handle changing an existing selection area. - else if ( m_selectedBlockTopLeft != updateTopLeft || - m_selectedBlockBottomRight != updateBottomRight ) - { - // Compute two optimal update rectangles: - // Either one rectangle is a real subset of the - // other, or they are (almost) disjoint! - - // Store intermediate values - wxCoord oldLeft = m_selectedBlockTopLeft.GetCol(); - wxCoord oldTop = m_selectedBlockTopLeft.GetRow(); - wxCoord oldRight = m_selectedBlockBottomRight.GetCol(); - wxCoord oldBottom = m_selectedBlockBottomRight.GetRow(); - - // Determine the outer/inner coordinates. - EnsureFirstLessThanSecond(oldLeft, leftCol); - EnsureFirstLessThanSecond(oldTop, topRow); - EnsureFirstLessThanSecond(rightCol, oldRight); - EnsureFirstLessThanSecond(bottomRow, oldBottom); - - // Now, either the stuff marked old is the outer - // rectangle or we don't have a situation where one - // is contained in the other. - - if ( oldLeft < leftCol ) - { - // Refresh the newly selected or deselected - // area to the left of the old or new selection. - RefreshBlock(oldTop, oldLeft, oldBottom, leftCol - 1); - } - - if ( oldTop < topRow ) - { - // Refresh the newly selected or deselected - // area above the old or new selection. - RefreshBlock(oldTop, leftCol, topRow - 1, rightCol); - } - - if ( oldRight > rightCol ) - { - // Refresh the newly selected or deselected - // area to the right of the old or new selection. - RefreshBlock(oldTop, rightCol + 1, oldBottom, oldRight); - } - - if ( oldBottom > bottomRow ) - { - // Refresh the newly selected or deselected - // area below the old or new selection. - RefreshBlock(bottomRow + 1, leftCol, oldBottom, rightCol); - } - } - - // change selection - m_selectedBlockTopLeft = updateTopLeft; - m_selectedBlockBottomRight = updateBottomRight; -} - // Note - this function only draws cells that are in the list of // exposed cells (usually set from the update region by // CalcExposedCells) @@ -7963,16 +7808,36 @@ wxGrid::DoMoveCursor(bool expandSelection, if ( expandSelection ) { - wxGridCellCoords coords = m_selectedBlockCorner; + if ( !m_selection ) + return false; + + wxGridCellCoords coords(m_selection->GetCurrentBlockCornerRow(), + m_selection->GetCurrentBlockCornerCol()); + if ( coords == wxGridNoCellCoords ) coords = m_currentCellCoords; + else if ( !diroper.IsValid(coords) ) + { + // The component of the current block corner in our direction + // is not valid. This means we can't change the selection block + // in this direction. + return false; + } if ( diroper.IsAtBoundary(coords) ) return false; diroper.Advance(coords); - UpdateBlockBeingSelected(m_currentCellCoords, coords); + if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, + coords, + wxKeyboardState()) ) + { + // We want to show a line (a row or a column), not the end of + // the selection block. And do it only if the selection block + // was actually changed. + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); + } } else // don't expand selection { @@ -8104,7 +7969,20 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, if ( expandSelection ) { - UpdateBlockBeingSelected(m_currentCellCoords, coords); + // TODO: Select the next block every time (not the same as now). + // And provide the keyboard state. + if ( m_selection ) + { + if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, + coords, + wxKeyboardState()) ) + { + // We want to show a line (a row or a column), not the end of + // the selection block. And do it only if the selection block + // was actually changed. + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); + } + } } else { @@ -10245,18 +10123,12 @@ void wxGrid::DeselectCell( int row, int col ) bool wxGrid::IsSelection() const { - return ( m_selection && (m_selection->IsSelection() || - ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords) ) ); + return m_selection && m_selection->IsSelection(); } bool wxGrid::IsInSelection( int row, int col ) const { - return ( m_selection && (m_selection->IsInSelection( row, col ) || - ( row >= m_selectedBlockTopLeft.GetRow() && - col >= m_selectedBlockTopLeft.GetCol() && - row <= m_selectedBlockBottomRight.GetRow() && - col <= m_selectedBlockBottomRight.GetCol() )) ); + return m_selection && m_selection->IsInSelection(row, col); } wxGridCellCoordsArray wxGrid::GetSelectedCells() const @@ -10316,13 +10188,6 @@ wxArrayInt wxGrid::GetSelectedCols() const void wxGrid::ClearSelection() { - RefreshBlock(m_selectedBlockTopLeft, m_selectedBlockBottomRight); - RefreshBlock(m_currentCellCoords, m_selectedBlockCorner); - - m_selectedBlockTopLeft = - m_selectedBlockBottomRight = - m_selectedBlockCorner = wxGridNoCellCoords; - if ( m_selection ) m_selection->ClearSelection(); } diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index e8530388a8..6ab871bf9a 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -454,6 +454,121 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) } } +bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd) +{ + if ( m_selection.empty() ) + { + SelectBlock(blockStart, blockEnd); + return true; + } + + wxGridBlockCoords& block = *m_selection.rbegin(); + wxGridBlockCoords newBlock = block; + + bool editBlock = false; + + if ( blockEnd.GetRow() != -1 ) + { + if ( newBlock.GetTopRow() == blockStart.GetRow() ) + { + newBlock.SetBottomRow(blockEnd.GetRow()); + editBlock = true; + } + else if ( newBlock.GetBottomRow() == blockStart.GetRow() ) + { + newBlock.SetTopRow(blockEnd.GetRow()); + editBlock = true; + } + } + if ( blockEnd.GetCol() != -1 ) + { + if ( newBlock.GetLeftCol() == blockStart.GetCol() ) + { + newBlock.SetRightCol(blockEnd.GetCol()); + editBlock = true; + } + else if ( newBlock.GetRightCol() == blockStart.GetCol() ) + { + newBlock.SetLeftCol(blockEnd.GetCol()); + editBlock = true; + } + } + + newBlock = newBlock.Canonicalize(); + + const bool endCoordsSet = + blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1; + + if ( editBlock ) + { + if ( newBlock == block ) + return false; + + // Update View. + if ( !m_grid->GetBatchCount() ) + { + wxGridBlockDiffResult refreshBlocks = block.SymDifference(newBlock); + for ( int i = 0; i < 4; ++i ) + { + const wxGridBlockCoords& refreshBlock = refreshBlocks.m_parts[i]; + m_grid->RefreshBlock(refreshBlock.GetTopLeft(), + refreshBlock.GetBottomRight()); + } + } + + // Edit the current block. + block = newBlock; + + // Send Event. + wxGridRangeSelectEvent gridEvt(m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + newBlock.GetTopLeft(), + newBlock.GetBottomRight(), + true, + kbd); + m_grid->GetEventHandler()->ProcessEvent(gridEvt); + return true; + } + else if ( endCoordsSet ) + { + // Select the new one. + SelectBlock(newBlock.GetTopLeft(), newBlock.GetBottomRight(), kbd); + return true; + } + return false; +} + +int wxGridSelection::GetCurrentBlockCornerRow() const +{ + if ( m_selection.empty() ) + return -1; + + const wxGridBlockCoords& block = *m_selection.rbegin(); + if ( block.GetTopRow() == m_grid->m_currentCellCoords.GetRow() ) + return block.GetBottomRow(); + if ( block.GetBottomRow() == m_grid->m_currentCellCoords.GetRow() ) + return block.GetTopRow(); + + return -1; +} + +int wxGridSelection::GetCurrentBlockCornerCol() const +{ + if ( m_selection.empty() ) + return -1; + + const wxGridBlockCoords& block = *m_selection.rbegin(); + if ( block.GetLeftCol() == m_grid->m_currentCellCoords.GetCol() ) + return block.GetRightCol(); + if ( block.GetRightCol() == m_grid->m_currentCellCoords.GetCol() ) + return block.GetLeftCol(); + + return -1; +} + wxGridCellCoordsArray wxGridSelection::GetCellSelection() const { if ( m_selectionMode != wxGrid::wxGridSelectCells ) From 89dd47edeed74bcecb1b651f3c2fc4ab494705ca Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Wed, 4 Mar 2020 23:00:10 +0700 Subject: [PATCH 13/65] Make wxGrid column selecting more user friendly --- include/wx/generic/grid.h | 3 ++ interface/wx/grid.h | 6 ++++ src/generic/grid.cpp | 70 +++++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 33e656fa39..1b1b0b4e09 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -1466,6 +1466,9 @@ public: void MakeCellVisible( const wxGridCellCoords& coords ) { MakeCellVisible( coords.GetRow(), coords.GetCol() ); } + // Returns the topmost row of the current visible area. + int GetFirstFullyVisibleRow() const; + // ------ grid cursor movement functions // diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 060f913663..74a8812580 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -4830,6 +4830,12 @@ public: */ void MakeCellVisible(const wxGridCellCoords& coords); + /** + Returns the topmost row of the current visible area. + Returns -1 if the grid doesn't have any rows. + */ + int GetFirstFullyVisibleRow() const; + /** Sets the number of pixels per horizontal scroll increment. diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 22d9ec2ae0..8aea66a5b8 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4001,8 +4001,13 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo { if ( col != -1 ) { - if ( m_selection ) - m_selection->SelectCol(col, event); + if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) + break; + + m_selection->ExtendOrCreateCurrentBlock( + wxGridCellCoords(0, m_currentCellCoords.GetCol()), + wxGridCellCoords(GetNumberRows() - 1, col), + event); } } break; @@ -4111,12 +4116,14 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo } else { - if ( !event.ShiftDown() && !event.CmdDown() ) - ClearSelection(); - if ( m_selection ) + if ( m_selection && m_numRows > 0 && m_numCols > 0 ) { - if ( event.ShiftDown() ) + bool selectNewCol = false; + + if ( event.ShiftDown() && !event.CmdDown() ) { + // Continue editing the current selection and don't + // move the grid cursor. m_selection->ExtendOrCreateCurrentBlock ( wxGridCellCoords(0, m_currentCellCoords.GetCol()), @@ -4125,9 +4132,25 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo ); MakeCellVisible(-1, col); } + else if ( event.CmdDown() && !event.ShiftDown() ) + { + if ( GetSelectedCols().Index(col) != wxNOT_FOUND ) + DeselectCol(col); + else + selectNewCol = true; + } else { + ClearSelection(); + selectNewCol = true; + } + + if (selectNewCol) + { + // Select the new column. m_selection->SelectCol(col, event); + + SetCurrentCell(GetFirstFullyVisibleRow(), col); } } @@ -7795,6 +7818,41 @@ void wxGrid::MakeCellVisible( int row, int col ) AdjustScrollbars(); } +int wxGrid::GetFirstFullyVisibleRow() const +{ + if ( m_numRows == 0 ) + return -1; + + int row; + if ( GetNumberFrozenRows() > 0 ) + { + row = 0; + } + else + { + int y; + CalcGridWindowUnscrolledPosition(0, 0, + NULL, &y, + m_gridWin); + + row = YToRow(y, true, m_gridWin); + + // If the row is not fully visible (if only 2 pixels is hidden + // the row still looks fully visible). + if ( GetRowTop(row) - 2 < y ) + { + // Use the next visible row. + for ( ; row < m_numRows; ++row ) + { + if ( IsRowShown(row) ) + break; + } + } + } + + return row; +} + // // ------ Grid cursor movement functions // From 0920a1646b52b1d451898fd1d6d78c65bed0eec7 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Wed, 4 Mar 2020 23:18:45 +0700 Subject: [PATCH 14/65] Make wxGrid row selecting more user friendly --- include/wx/generic/grid.h | 3 +- interface/wx/grid.h | 5 +++ src/generic/grid.cpp | 69 +++++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 1b1b0b4e09..df5b0c69b1 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -1468,7 +1468,8 @@ public: // Returns the topmost row of the current visible area. int GetFirstFullyVisibleRow() const; - + // Returns the leftmost column of the current visible area. + int GetFirstFullyVisibleColumn() const; // ------ grid cursor movement functions // diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 74a8812580..52c966122b 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -4835,6 +4835,11 @@ public: Returns -1 if the grid doesn't have any rows. */ int GetFirstFullyVisibleRow() const; + /** + Returns the leftmost column of the current visible area. + Returns -1 if the grid doesn't have any columns. + */ + int GetFirstFullyVisibleColumn() const; /** Sets the number of pixels per horizontal scroll increment. diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 8aea66a5b8..f35a8f79a7 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3691,10 +3691,15 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo case WXGRID_CURSOR_SELECT_ROW: { + if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) + break; + if ( (row = YToRow( pos.y )) >= 0 ) { - if ( m_selection ) - m_selection->SelectRow(row, event); + m_selection->ExtendOrCreateCurrentBlock( + wxGridCellCoords(m_currentCellCoords.GetRow(), 0), + wxGridCellCoords(row, GetNumberCols() - 1), + event); } } break; @@ -3736,12 +3741,14 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( row >= 0 && !SendEvent( wxEVT_GRID_LABEL_LEFT_CLICK, row, -1, event ) ) { - if ( !event.ShiftDown() && !event.CmdDown() ) - ClearSelection(); - if ( m_selection ) + if ( m_selection && m_numRows > 0 && m_numCols > 0 ) { - if ( event.ShiftDown() ) + bool selectNewRow = false; + + if ( event.ShiftDown() && !event.CmdDown() ) { + // Continue editing the current selection and don't + // move the grid cursor. m_selection->ExtendOrCreateCurrentBlock ( wxGridCellCoords(m_currentCellCoords.GetRow(), 0), @@ -3750,9 +3757,25 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo ); MakeCellVisible(row, -1); } + else if ( event.CmdDown() && !event.ShiftDown() ) + { + if ( GetSelectedRows().Index(row) != wxNOT_FOUND ) + DeselectRow(row); + else + selectNewRow = true; + } else { + ClearSelection(); + selectNewRow = true; + } + + if ( selectNewRow ) + { + // Select the new row. m_selection->SelectRow(row, event); + + SetCurrentCell(row, GetFirstFullyVisibleColumn()); } } @@ -7853,6 +7876,40 @@ int wxGrid::GetFirstFullyVisibleRow() const return row; } +int wxGrid::GetFirstFullyVisibleColumn() const +{ + if ( m_numCols == 0 ) + return -1; + + int col; + if ( GetNumberFrozenCols() > 0 ) + { + col = 0; + } + else + { + int x; + CalcGridWindowUnscrolledPosition(0, 0, + &x, NULL, + m_gridWin); + + col = XToCol(x, true, m_gridWin); + + // If the column is not fully visible. + if ( GetColLeft(col) < x ) + { + // Use the next visible column. + for ( ; col < m_numCols; ++col ) + { + if ( IsColShown(GetColAt(col)) ) + break; + } + } + } + + return col; +} + // // ------ Grid cursor movement functions // From 8ebdf101b87f81df8affa18116a304566a20f6af Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Wed, 4 Mar 2020 23:32:23 +0700 Subject: [PATCH 15/65] Make wxGrid cells selecting by mouse more user friendly --- src/generic/grid.cpp | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index f35a8f79a7..19c0bc4c2c 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4492,13 +4492,6 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, case wxMOD_CONTROL: if ( isFirstDrag ) SetGridCursor(coords); - if ( m_selection ) - { - m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, - coords, - event); - MakeCellVisible(coords); - } break; case wxMOD_NONE: @@ -4513,20 +4506,13 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, return performDefault; } } - if ( m_selection ) - { - m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, - coords, - event); - MakeCellVisible(coords); - } break; - - default: - // we don't handle the other key modifiers - event.Skip(); } + // Edit the current selection block independently of the modifiers state. + if ( m_selection ) + m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, coords, event); + return performDefault; } @@ -4566,10 +4552,7 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, return; } - if ( !event.CmdDown() ) - ClearSelection(); - - if ( event.ShiftDown() ) + if ( event.ShiftDown() && !event.CmdDown() ) { if ( m_selection ) { @@ -4584,7 +4567,7 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, DisableCellEditControl(); MakeCellVisible( coords ); - if ( event.CmdDown() ) + if ( event.CmdDown() && !event.ShiftDown() ) { if ( m_selection ) { @@ -4607,6 +4590,8 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, } else { + ClearSelection(); + if ( m_selection ) { // In row or column selection mode just clicking on the cell @@ -4632,8 +4617,8 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, m_waitForSlowClick = m_currentCellCoords == coords && coords != wxGridNoCellCoords; - SetCurrentCell( coords ); } + SetCurrentCell(coords); } } From f8015b13b1a6d8b9dc096f1d159654468238a4e3 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Fri, 27 Mar 2020 03:23:45 +0700 Subject: [PATCH 16/65] Implement wxGrid selection blocks iterating interface --- include/wx/generic/grid.h | 61 ++++++++++++++++++++++++++++++++++++ include/wx/generic/gridsel.h | 2 ++ interface/wx/grid.h | 61 ++++++++++++++++++++++++++++++++++++ src/generic/grid.cpp | 9 ++++++ tests/controls/gridtest.cpp | 35 +++++++++++++++++++++ 5 files changed, 168 insertions(+) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index df5b0c69b1..adb1e7b345 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -868,6 +868,66 @@ struct wxGridBlockDiffResult wxGridBlockCoords m_parts[4]; }; +// ---------------------------------------------------------------------------- +// wxGridSelectionRange: the range of grid selection blocks +// ---------------------------------------------------------------------------- + +class wxGridSelectionRange +{ +public: + typedef wxGridBlockCoords* iterator; + + wxGridSelectionRange() : + m_begin(NULL), + m_end(NULL), + m_it(NULL) + { + } + + // Get the current selection block coordinates. + const wxGridBlockCoords& GetBlockCoords() const + { + static wxGridBlockCoords empty; + return Valid() ? *m_it : empty; + } + + // Iterate to the next block. + void Next() + { + if ( Valid() ) + ++m_it; + } + + // Whether the iterator is valid. + bool Valid() const + { + return m_it != m_end; + } + + iterator begin() const + { + return m_begin; + } + + iterator end() const + { + return m_end; + } + +private: + wxGridSelectionRange(iterator begin, iterator end) : + m_begin(begin), + m_end(end), + m_it(begin) + { + } + + const iterator m_begin; + const iterator m_end; + iterator m_it; + + friend class wxGrid; +}; // For comparisons... // @@ -1918,6 +1978,7 @@ public: bool IsInSelection( const wxGridCellCoords& coords ) const { return IsInSelection( coords.GetRow(), coords.GetCol() ); } + wxGridSelectionRange GetSelectionRange() const; wxGridCellCoordsArray GetSelectedCells() const; wxGridCellCoordsArray GetSelectionBlockTopLeft() const; wxGridCellCoordsArray GetSelectionBlockBottomRight() const; diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index c5e5df7812..5d57e5e447 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -89,6 +89,8 @@ public: wxArrayInt GetRowSelection() const; wxArrayInt GetColSelection() const; + wxVectorGridBlockCoords& GetBlocks() { return m_selection; } + private: int BlockContain( int topRow1, int leftCol1, int bottomRow1, int rightCol1, diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 52c966122b..dc17b2b9ae 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -2005,6 +2005,45 @@ struct wxGridBlockDiffResult wxGridBlockCoords m_parts[4]; }; +/** + The range of grid selection blocks. + + @since 3.1.4 + */ +class wxGridSelectionRange +{ +public: + /** + Default constructor initializes the iterator. + */ + wxGridSelectionRange(); + + /** + * Get the current selection block coordinates. + */ + const wxGridBlockCoords& GetBlockCoords() const; + + /** + * Iterate to the next block. + */ + void Next(); + + /** + * Whether the iterator is valid. + */ + bool Valid(); + + /** + * The function to allow using range-based for. + */ + wxGridBlockCoords *begin() const; + + /** + * The function to allow using range-based for. + */ + wxGridBlockCoords *end() const; +}; + /** @class wxGridTableBase @@ -4608,6 +4647,13 @@ public: */ void DeselectCell( int row, int col ); + /** + Returns an range of grid selection blocks. + + @since 3.1.4 + */ + wxGridSelectionRange GetSelectionRange() const; + /** Returns an array of individually selected cells. @@ -4623,6 +4669,9 @@ public: a grid with a million of columns, we don't want to create an array with a million of entries in this function, instead it returns an empty array and GetSelectedCols() returns an array containing one element). + + The function can be slow for the big grids, use GetSelectionRange() + in the new code. */ wxGridCellCoordsArray GetSelectedCells() const; @@ -4634,6 +4683,9 @@ public: individually selected but not those being part of the block selection or being selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. + + The function can be slow for the big grids, use GetSelectionRange() + in the new code. */ wxArrayInt GetSelectedCols() const; @@ -4645,6 +4697,9 @@ public: selected but not those being part of the block selection or being selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. + + The function can be slow for the big grids, use GetSelectionRange() + in the new code. */ wxArrayInt GetSelectedRows() const; @@ -4660,6 +4715,9 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. + The function can be slow for the big grids, use GetSelectionRange() + in the new code. + @see GetSelectionBlockTopLeft() */ wxGridCellCoordsArray GetSelectionBlockBottomRight() const; @@ -4670,6 +4728,9 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. + The function can be slow for the big grids, use GetSelectionRange() + in the new code. + @see GetSelectionBlockBottomRight() */ wxGridCellCoordsArray GetSelectionBlockTopLeft() const; diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 19c0bc4c2c..f24c68499e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10231,6 +10231,15 @@ bool wxGrid::IsInSelection( int row, int col ) const return m_selection && m_selection->IsInSelection(row, col); } +wxGridSelectionRange wxGrid::GetSelectionRange() const +{ + if ( !m_selection ) + return wxGridSelectionRange(); + + wxVectorGridBlockCoords& blocks = m_selection->GetBlocks(); + return wxGridSelectionRange(blocks.begin(), blocks.end()); +} + wxGridCellCoordsArray wxGrid::GetSelectedCells() const { if (!m_selection) diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 6e57e1bc18..5159c12e50 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -524,6 +524,41 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") CHECK(!m_grid->IsInSelection(3, 0)); } +TEST_CASE_METHOD(GridTestCase, "Grid::SelectionIterator", "[grid]") +{ + CHECK(!m_grid->GetSelectionRange().Valid()); + + m_grid->SelectBlock(1, 0, 3, 1); + + wxGridSelectionRange sel = m_grid->GetSelectionRange(); + CHECK(sel.Valid()); + CHECK(sel.GetBlockCoords() == wxGridBlockCoords(1, 0, 3, 1)); + + sel.Next(); + CHECK(!sel.Valid()); + +#if __cplusplus >= 201103L || wxCHECK_VISUALC_VERSION(10) + m_grid->SelectBlock(4, 0, 7, 1, true); + int index = 0; + for ( const wxGridBlockCoords& block : m_grid->GetSelectionRange() ) + { + switch ( index ) + { + case 0: + CHECK(block == wxGridBlockCoords(1, 0, 3, 1)); + break; + case 1: + CHECK(block == wxGridBlockCoords(4, 0, 7, 1)); + break; + default: + FAIL("Unexpected iterations count"); + break; + } + ++index; + } +#endif +} + TEST_CASE_METHOD(GridTestCase, "Grid::SelectEmptyGrid", "[grid]") { for ( int i = 0; i < 2; ++i ) From bfca68c74ad581dbc10513521e90625e615bc778 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Sat, 28 Mar 2020 00:49:45 +0700 Subject: [PATCH 17/65] Move the wxGrid cursor to the corner of the last selection Move the wxGrid cursor to the corner of the last selection when splitting the selection block on click with Ctrl. --- src/generic/grid.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index f24c68499e..f67bd960b8 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4567,6 +4567,10 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, DisableCellEditControl(); MakeCellVisible( coords ); + // Whether we should move the current grid cell to the corrner of the + // last selected block. + bool goToLastBlock = false; + if ( event.CmdDown() && !event.ShiftDown() ) { if ( m_selection ) @@ -4585,6 +4589,8 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, wxGridBlockCoords(coords.GetRow(), coords.GetCol(), coords.GetRow(), coords.GetCol()), event); + + goToLastBlock = true; } } } @@ -4618,7 +4624,16 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, m_waitForSlowClick = m_currentCellCoords == coords && coords != wxGridNoCellCoords; } - SetCurrentCell(coords); + + if ( goToLastBlock && m_selection->IsSelection() ) + { + wxGridBlockCoords& lastBlock = m_selection->GetBlocks().back(); + SetCurrentCell(lastBlock.GetTopLeft()); + } + else + { + SetCurrentCell(coords); + } } } From a469d36783057b80bc3ae8003db734b0e4dbee30 Mon Sep 17 00:00:00 2001 From: Ilya Sinitsyn Date: Thu, 2 Apr 2020 03:02:48 +0700 Subject: [PATCH 18/65] Fix the test where wxGrid scrolling when selecting cells --- tests/controls/gridtest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 5159c12e50..82fac33040 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -619,12 +619,12 @@ TEST_CASE_METHOD(GridTestCase, "Grid::ScrollWhenSelect", "[grid]") CHECK( m_grid->IsVisible(0, 4) ); m_grid->ClearSelection(); - m_grid->SetGridCursor(1, 1); - for ( int i = 0; i < 5; ++i ) + m_grid->GoToCell(1, 1); + for ( int i = 0; i < 8; ++i ) { m_grid->MoveCursorDown(true); } - CHECK( m_grid->IsVisible(6, 1) ); + CHECK( m_grid->IsVisible(9, 1) ); } TEST_CASE_METHOD(GridTestCase, "Grid::MoveGridCursorUsingEndKey", "[grid]") From 0f8e985252d4c6b18f6ee96f751e02640e827dc7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Apr 2020 19:03:01 +0200 Subject: [PATCH 19/65] Define wxGridSelectionRange::iterator in terms of vector iterator This fixes compilation when using std::vector<> implementation for wxVector (i.e. with wxUSE_STD_CONTAINERS==1). --- include/wx/generic/grid.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index adb1e7b345..b41b8a9829 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -875,12 +875,12 @@ struct wxGridBlockDiffResult class wxGridSelectionRange { public: - typedef wxGridBlockCoords* iterator; + typedef wxVector::const_iterator iterator; wxGridSelectionRange() : - m_begin(NULL), - m_end(NULL), - m_it(NULL) + m_begin(), + m_end(), + m_it() { } From 7231a6a8552683e16770173ba8aab853019578b8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Apr 2020 19:04:33 +0200 Subject: [PATCH 20/65] Make temporary variable in GetSelectionRange() const We don't need to modify the selection blocks here. No real changes. --- src/generic/grid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index f67bd960b8..1dca09bd95 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10251,7 +10251,7 @@ wxGridSelectionRange wxGrid::GetSelectionRange() const if ( !m_selection ) return wxGridSelectionRange(); - wxVectorGridBlockCoords& blocks = m_selection->GetBlocks(); + const wxVectorGridBlockCoords& blocks = m_selection->GetBlocks(); return wxGridSelectionRange(blocks.begin(), blocks.end()); } From 0a5a904d8dfa4e7c2fc3e3d0d75ad141a1e21302 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Apr 2020 19:22:02 +0200 Subject: [PATCH 21/65] Simplify wxGridSelectionRange to provide only iterators This class was a strange hybrid of a container/view/range and iterator, as it both provided begin()/end() container-like methods and iterator-like methods for dereferencing/advancing. Simplify this by removing the latter part and making this class really just a range, with its own iterator class in order to avoid leaking the exact type of the iterator used in the API. Note that while it's now completely trivial, it is still useful as it isolates the application code from the vector used to store the selected blocks currently and will allow to change internal representation in the future without breaking the existing code. --- include/wx/generic/grid.h | 71 ++++++++++++++++++++--------------- interface/wx/grid.h | 74 +++++++++++++++++++++++++------------ tests/controls/gridtest.cpp | 12 +++--- 3 files changed, 97 insertions(+), 60 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index b41b8a9829..762c4e099d 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -19,6 +19,10 @@ #include "wx/scrolwin.h" +#if wxUSE_STD_CONTAINERS_COMPATIBLY + #include +#endif + // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- @@ -869,40 +873,45 @@ struct wxGridBlockDiffResult }; // ---------------------------------------------------------------------------- -// wxGridSelectionRange: the range of grid selection blocks +// wxGridSelectionRange: a range of grid blocks that can be iterated over // ---------------------------------------------------------------------------- class wxGridSelectionRange { + typedef wxVector::const_iterator iterator_impl; + public: - typedef wxVector::const_iterator iterator; - - wxGridSelectionRange() : - m_begin(), - m_end(), - m_it() + class iterator { - } + public: +#if wxUSE_STD_CONTAINERS_COMPATIBLY + typedef std::forward_iterator_tag iterator_category; +#endif + typedef ptrdiff_t difference_type; + typedef wxGridBlockCoords value_type; + typedef const value_type& reference; - // Get the current selection block coordinates. - const wxGridBlockCoords& GetBlockCoords() const - { - static wxGridBlockCoords empty; - return Valid() ? *m_it : empty; - } + iterator() : m_it() { } - // Iterate to the next block. - void Next() - { - if ( Valid() ) - ++m_it; - } + reference operator*() const { return *m_it; } - // Whether the iterator is valid. - bool Valid() const - { - return m_it != m_end; - } + iterator& operator++() + { ++m_it; return *this; } + iterator operator++(int) + { iterator tmp = *this; ++m_it; return tmp; } + + bool operator==(const iterator& it) const + { return m_it == it.m_it; } + bool operator!=(const iterator& it) const + { return m_it != it.m_it; } + + private: + explicit iterator(iterator_impl it) : m_it(it) { } + + iterator_impl m_it; + + friend class wxGridSelectionRange; + }; iterator begin() const { @@ -915,16 +924,20 @@ public: } private: - wxGridSelectionRange(iterator begin, iterator end) : + wxGridSelectionRange() : + m_begin(), + m_end() + { + } + + wxGridSelectionRange(iterator_impl begin, iterator_impl end) : m_begin(begin), - m_end(end), - m_it(begin) + m_end(end) { } const iterator m_begin; const iterator m_end; - iterator m_it; friend class wxGrid; }; diff --git a/interface/wx/grid.h b/interface/wx/grid.h index dc17b2b9ae..1976ff27da 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -2006,7 +2006,29 @@ struct wxGridBlockDiffResult }; /** - The range of grid selection blocks. + Represents a range of grid blocks that can be iterated over. + + This class provides read-only access to the blocks making up the grid + selection in the most general case. + + Note that objects of this class can only be returned by wxGrid, but not + constructed in the application code. + + The preferable way to iterate over it is using C++11 range-for loop: + @code + for ( const auto block: grid->GetSelectionRange() ) { + ... do something with block ... + } + @endcode + When not using C++11, iteration has to be done manually: + @code + wxGridSelectionRange range = grid->GetSelectionRange(); + for ( wxGridSelectionRange::iterator it = range.begin(); + it != range.end(); + ++it ) { + ... do something with *it ... + } + @endcode @since 3.1.4 */ @@ -2014,34 +2036,30 @@ class wxGridSelectionRange { public: /** - Default constructor initializes the iterator. - */ - wxGridSelectionRange(); + Read-only forward iterator type. - /** - * Get the current selection block coordinates. + This is an opaque type, which satisfies the forward iterator + requirements, i.e. provides all the expected operations, such as + comparison, dereference and pre- and post-increment. */ - const wxGridBlockCoords& GetBlockCoords() const; + class iterator + { + iterator(); - /** - * Iterate to the next block. - */ - void Next(); + const wxGridBlockCoords& operator*() const; - /** - * Whether the iterator is valid. - */ - bool Valid(); + iterator& operator++(); + iterator operator++(int); - /** - * The function to allow using range-based for. - */ - wxGridBlockCoords *begin() const; + bool operator==(const iterator& it) const; + bool operator!=(const iterator& it) const; + }; - /** - * The function to allow using range-based for. - */ - wxGridBlockCoords *end() const; + /// Return iterator corresponding to the beginning of the range. + iterator begin() const; + + /// Return iterator corresponding to the end of the range. + iterator end() const; }; /** @@ -4648,7 +4666,15 @@ public: void DeselectCell( int row, int col ); /** - Returns an range of grid selection blocks. + Returns a range of grid selection blocks. + + The returned range can be iterated over, e.g. with C++11 range-for loop: + @code + for ( const auto block: grid->GetSelectionRange() ) { + if ( block.Intersects(myBlock) ) + break; + } + @endcode @since 3.1.4 */ diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 82fac33040..d32e94e955 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -524,18 +524,16 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") CHECK(!m_grid->IsInSelection(3, 0)); } -TEST_CASE_METHOD(GridTestCase, "Grid::SelectionIterator", "[grid]") +TEST_CASE_METHOD(GridTestCase, "Grid::SelectionRange", "[grid]") { - CHECK(!m_grid->GetSelectionRange().Valid()); + const wxGridSelectionRange empty = m_grid->GetSelectionRange(); + CHECK( empty.begin() == empty.end() ); m_grid->SelectBlock(1, 0, 3, 1); wxGridSelectionRange sel = m_grid->GetSelectionRange(); - CHECK(sel.Valid()); - CHECK(sel.GetBlockCoords() == wxGridBlockCoords(1, 0, 3, 1)); - - sel.Next(); - CHECK(!sel.Valid()); + REQUIRE( sel.begin() != sel.end() ); + CHECK( *sel.begin() == wxGridBlockCoords(1, 0, 3, 1) ); #if __cplusplus >= 201103L || wxCHECK_VISUALC_VERSION(10) m_grid->SelectBlock(4, 0, 7, 1, true); From a5952ee08799e9d6412f1b1dfb7573df28eb5bbe Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Apr 2020 19:45:19 +0200 Subject: [PATCH 22/65] Rename wxGridBlockCoords::ContainsCell() and move it inline Make the function name more grammatically correct. No real changes. --- include/wx/generic/grid.h | 10 ++++++---- interface/wx/grid.h | 6 +++--- src/generic/grid.cpp | 6 ------ src/generic/gridsel.cpp | 2 +- tests/controls/gridtest.cpp | 6 +++--- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 762c4e099d..fcb2f8b4b8 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -816,10 +816,12 @@ public: m_leftCol <= other.m_rightCol && m_rightCol >= other.m_leftCol; } - // Whether the block contains the cell. - // returns @true, if the block contains the cell, - // @false, otherwise - bool ContainCell(const wxGridCellCoords& cell) const; + // Return whether this block contains the given cell. + bool ContainsCell(const wxGridCellCoords& cell) const + { + return m_topRow <= cell.GetRow() && cell.GetRow() <= m_bottomRow && + m_leftCol <= cell.GetCol() && cell.GetCol() <= m_rightCol; + } // Whether the blocks contain each other. // returns 1, if this block contains the other, diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 1976ff27da..6e263abb58 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1916,12 +1916,12 @@ public: } /** - Whether the block contains the cell. + Check whether this block contains the given cell. @return - @true, if the block contains the cell, @false, otherwise. + @true, if the block contains the cell, @false otherwise. */ - bool ContainCell(const wxGridCellCoords& cell) const; + bool ContainsCell(const wxGridCellCoords& cell) const; /** Whether the blocks contain each other. diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 1dca09bd95..ed5a8bc3d3 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -1140,12 +1140,6 @@ const wxGridCornerHeaderRenderer& wxGridCellAttrProvider::GetCornerRenderer() // wxGridBlockCoords // ---------------------------------------------------------------------------- -bool wxGridBlockCoords::ContainCell(const wxGridCellCoords& cell) const -{ - return m_topRow <= cell.GetRow() && cell.GetRow() <= m_bottomRow && - m_leftCol <= cell.GetCol() && cell.GetCol() <= m_rightCol; -} - int wxGridBlockCoords::ContainBlock(const wxGridBlockCoords& other) const { // returns 1, if this block contains the other, diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 6ab871bf9a..62ea7312d7 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -61,7 +61,7 @@ bool wxGridSelection::IsInSelection( int row, int col ) const size_t count = m_selection.size(); for ( size_t n = 0; n < count; n++ ) { - if ( m_selection[n].ContainCell(wxGridCellCoords(row, col)) ) + if ( m_selection[n].ContainsCell(wxGridCellCoords(row, col)) ) return true; } diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index d32e94e955..f1749634c9 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -1328,13 +1328,13 @@ TEST_CASE("GridBlockCoords::Intersects", "[grid]") CHECK(!wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(4, 4, 6, 6))); } -TEST_CASE("GridBlockCoords::ContainCell", "[grid]") +TEST_CASE("GridBlockCoords::ContainsCell", "[grid]") { // Inside. - CHECK(wxGridBlockCoords(1, 1, 3, 3).ContainCell(wxGridCellCoords(2, 2))); + CHECK(wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(2, 2))); // Outside. - CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainCell(wxGridCellCoords(5, 5))); + CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(5, 5))); } TEST_CASE("GridBlockCoords::ContainBlock", "[grid]") From 791a9e68ae31079b4b0e04358e44f5a2fb09fa2e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 01:37:56 +0200 Subject: [PATCH 23/65] Rename and simplify wxGridBlockCoords::ContainsBlock() Change the return type of this function to a simple and clear bool instead of 3-valued int requiring a special explanation. This is simpler and not any less efficient as checking for whether one block contains another or the other one contains this one are separate operations anyhow. Rename the function to a more grammatically correct name. Also move it inline as it's now trivial enough for this to be worth it. --- include/wx/generic/grid.h | 11 ++++++----- interface/wx/grid.h | 8 +++----- src/generic/grid.cpp | 15 --------------- src/generic/gridsel.cpp | 19 +++++++------------ tests/controls/gridtest.cpp | 12 +++++++----- 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index fcb2f8b4b8..96a3844f6b 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -823,11 +823,12 @@ public: m_leftCol <= cell.GetCol() && cell.GetCol() <= m_rightCol; } - // Whether the blocks contain each other. - // returns 1, if this block contains the other, - // -1, if the other block contains this one, - // 0, otherwise - int ContainBlock(const wxGridBlockCoords& other) const; + // Return whether this blocks fully contains another one. + bool ContainsBlock(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_topRow && other.m_bottomRow <= m_bottomRow && + m_leftCol <= other.m_leftCol && other.m_rightCol <= m_rightCol; + } // Calculates the result blocks by subtracting the other block from this // block. splitOrientation can be wxVERTICAL or wxHORIZONTAL. diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 6e263abb58..6122f6b3fa 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1924,14 +1924,12 @@ public: bool ContainsCell(const wxGridCellCoords& cell) const; /** - Whether the blocks contain each other. + Check whether this block contains another one. @return - 1, if this block contains the other, - -1, if the other block contains this one, - 0, otherwise. + @true if @a other is entirely contained within this block. */ - int ContainBlock(const wxGridBlockCoords& other) const; + int ContainsBlock(const wxGridBlockCoords& other) const; /** Calculates the result blocks by subtracting the other block from this diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index ed5a8bc3d3..70ee786943 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -1140,21 +1140,6 @@ const wxGridCornerHeaderRenderer& wxGridCellAttrProvider::GetCornerRenderer() // wxGridBlockCoords // ---------------------------------------------------------------------------- -int wxGridBlockCoords::ContainBlock(const wxGridBlockCoords& other) const -{ -// returns 1, if this block contains the other, -// -1, if the other block contains this one, -// 0, otherwise - if ( m_topRow <= other.m_topRow && other.m_bottomRow <= m_bottomRow && - m_leftCol <= other.m_leftCol && other.m_rightCol <= m_rightCol ) - return 1; - else if ( other.m_topRow <= m_topRow && m_bottomRow <= other.m_bottomRow && - other.m_leftCol <= m_leftCol && m_rightCol <= other.m_rightCol ) - return -1; - - return 0; -} - wxGridBlockDiffResult wxGridBlockCoords::Difference(const wxGridBlockCoords& other, int splitOrientation) const diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 62ea7312d7..6fcd64140f 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -741,19 +741,14 @@ void wxGridSelection::MergeOrAddBlock(wxVectorGridBlockCoords& blocks, { const wxGridBlockCoords& block = blocks[n]; - switch ( block.ContainBlock(newBlock) ) + if ( block.ContainsBlock(newBlock) ) + return; + + if ( newBlock.ContainsBlock(block) ) { - case 1: - return; - - case -1: - blocks.erase(blocks.begin() + n); - n--; - count--; - break; - - default: - break; + blocks.erase(blocks.begin() + n); + n--; + count--; } } diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index f1749634c9..708f42cd18 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -1337,17 +1337,19 @@ TEST_CASE("GridBlockCoords::ContainsCell", "[grid]") CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(5, 5))); } -TEST_CASE("GridBlockCoords::ContainBlock", "[grid]") +TEST_CASE("GridBlockCoords::ContainsBlock", "[grid]") { wxGridBlockCoords block1(1, 1, 5, 5); wxGridBlockCoords block2(1, 1, 3, 3); wxGridBlockCoords block3(2, 2, 7, 7); wxGridBlockCoords block4(10, 10, 12, 12); - CHECK(block1.ContainBlock(block2) == 1); - CHECK(block2.ContainBlock(block1) == -1); - CHECK(block1.ContainBlock(block3) == 0); - CHECK(block1.ContainBlock(block4) == 0); + CHECK( block1.ContainsBlock(block2)); + CHECK(!block2.ContainsBlock(block1)); + CHECK(!block1.ContainsBlock(block3)); + CHECK(!block1.ContainsBlock(block4)); + CHECK(!block3.ContainsBlock(block1)); + CHECK(!block4.ContainsBlock(block1)); } TEST_CASE("GridBlockCoords::Difference", "[grid]") From 0ab23a0072b851cdeebe42f533f2ff757517f506 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 01:42:40 +0200 Subject: [PATCH 24/65] Add a comment about wxGridSelection::IsInSelection() complexity No real changes. --- src/generic/gridsel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 6fcd64140f..707180fccd 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -58,6 +58,12 @@ bool wxGridSelection::IsSelection() bool wxGridSelection::IsInSelection( int row, int col ) { // Check whether the given cell is contained in one of the selected blocks. + // + // Note that this algorithm is O(N) in number of selected blocks, not in + // number of cells in the grid, so it should be reasonably efficient even + // for very large grids, as the user shouldn't be able to select too many + // blocks. If we still run into problems with this, we should find a more + // efficient way of storing the selection, e.g. using k-d trees. const size_t count = m_selection.size(); for ( size_t n = 0; n < count; n++ ) { From 13978a6626dbb13365f18dfdd91946d140bda826 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 01:52:35 +0200 Subject: [PATCH 25/65] Remove outdated comment from wxGridSelection code Don't mention variable which doesn't exist any more. --- src/generic/gridsel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 707180fccd..588fa1c586 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -94,7 +94,6 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) } else { - // Note that m_blockSelectionTopLeft's size may be changing! for ( size_t n = m_selection.size(); n > 0; ) { n--; From d031ef154a97effd7772007acbb3879eb90d7758 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 02:03:55 +0200 Subject: [PATCH 26/65] Simplify wxGrid::SetSelectionMode() selection updating logic Don't try to extend the existing selected blocks to rows/columns, this contradicts the documented behaviour which is to discard the selected blocks that become invalid in the new mode. Do handle switching to wxGridSelectRowsOrColumns mode, as there doesn't seem to be any reason not to. Update the tests to check for the expected selection update behaviour. --- src/generic/gridsel.cpp | 46 +++++++++++++++++++++---------------- tests/controls/gridtest.cpp | 13 ++++++++++- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 588fa1c586..a7957bd777 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -81,8 +81,6 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) if (selmode == m_selectionMode) return; - // TODO: wxGridSelectRowsOrColumns? - if ( m_selectionMode != wxGrid::wxGridSelectCells ) { // if changing form row to column selection @@ -94,6 +92,11 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) } else { + // Preserve only fully selected rows/columns when switching from cell + // selection mode and discard the selected blocks that are invalid in + // the new selection mode. + const int lastCol = m_grid->GetNumberCols() - 1; + const int lastRow = m_grid->GetNumberRows() - 1; for ( size_t n = m_selection.size(); n > 0; ) { n--; @@ -103,26 +106,29 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) const int bottomRow = block.GetBottomRow(); const int rightCol = block.GetRightCol(); - if (selmode == wxGrid::wxGridSelectRows) + bool valid = false; + switch ( selmode ) { - if (leftCol != 0 || rightCol != m_grid->GetNumberCols() - 1 ) - { - m_selection.erase(m_selection.begin() + n); - SelectBlockNoEvent( - wxGridBlockCoords(topRow, 0, - bottomRow, m_grid->GetNumberCols() - 1)); - } - } - else // selmode == wxGridSelectColumns) - { - if (topRow != 0 || bottomRow != m_grid->GetNumberRows() - 1 ) - { - m_selection.erase(m_selection.begin() + n); - SelectBlockNoEvent( - wxGridBlockCoords(0, leftCol, - m_grid->GetNumberRows() - 1, rightCol)); - } + case wxGrid::wxGridSelectCells: + wxFAIL_MSG("unreachable"); + break; + + case wxGrid::wxGridSelectRows: + valid = leftCol == 0 && rightCol == lastCol; + break; + + case wxGrid::wxGridSelectColumns: + valid = topRow == 0 && bottomRow == lastRow; + break; + + case wxGrid::wxGridSelectRowsOrColumns: + valid = (leftCol == 0 && rightCol == lastCol) || + (topRow == 0 && bottomRow == lastRow); + break; } + + if ( !valid ) + m_selection.erase(m_selection.begin() + n); } m_selectionMode = selmode; diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 708f42cd18..67892326eb 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -862,9 +862,20 @@ TEST_CASE_METHOD(GridTestCase, "Grid::SelectionMode", "[grid]") //We already test this mode in Select CHECK(m_grid->GetSelectionMode() == wxGrid::wxGridSelectCells); + // Select an individual cell and an entire row. + m_grid->SelectBlock(3, 1, 3, 1); + m_grid->SelectRow(5, true /* add to selection */); + + // Test that after switching to row selection mode only the row remains + // selected. + m_grid->SetSelectionMode(wxGrid::wxGridSelectRows); + CHECK( m_grid->IsInSelection(5, 0) ); + CHECK( m_grid->IsInSelection(5, 1) ); + CHECK( !m_grid->IsInSelection(3, 1) ); + //Test row selection be selecting a single cell and checking the whole //row is selected - m_grid->SetSelectionMode(wxGrid::wxGridSelectRows); + m_grid->ClearSelection(); m_grid->SelectBlock(3, 1, 3, 1); wxArrayInt selectedRows = m_grid->GetSelectedRows(); From ddc87d66e8bfbb674d1e850dd92732c632042022 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 15:51:35 +0200 Subject: [PATCH 27/65] Increase the size of the windows in the grid sample Make larger part of the grid visible initially and also show more lines in the log window. No real changes, this is purely cosmetic. --- samples/grid/griddemo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index 6de852ba6f..dd0536bbdb 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -432,7 +432,7 @@ GridFrame::GridFrame() grid = new wxGrid( this, wxID_ANY, wxPoint( 0, 0 ), - FromDIP(wxSize( 400, 300 )) ); + FromDIP(wxSize( 800, 450 )) ); #if wxUSE_LOG @@ -440,7 +440,7 @@ GridFrame::GridFrame() wxID_ANY, wxEmptyString, wxDefaultPosition, - wxDefaultSize, + wxSize(-1, 8*GetCharHeight()), wxTE_MULTILINE ); logger = new wxLogTextCtrl( logWin ); From 1d43ae7dc6144adf891acc2e59372fe36db0a54c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 15:53:30 +0200 Subject: [PATCH 28/65] Refactor selection expansion code to use actual wxKeyboardState Switch from using just "bool expandSelection" in the grid functions (possibly) extending the current selection to using the full wxKeyboardState. This allows to pass it to ExtendOrCreateCurrentBlock() and slightly simplify the code by using DoMoveCursorFromKeyboard(). --- include/wx/generic/grid.h | 6 ++- src/generic/grid.cpp | 93 ++++++++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 96a3844f6b..e21baacd6b 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2767,10 +2767,12 @@ private: wxGridWindow *gridWindow) const; int PosToEdgeOfLine(int pos, const wxGridOperations& oper) const; - bool DoMoveCursor(bool expandSelection, + void DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper); + bool DoMoveCursor(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); bool DoMoveCursorByPage(const wxGridDirectionOperations& diroper); - bool DoMoveCursorByBlock(bool expandSelection, + bool DoMoveCursorByBlock(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); void AdvanceToNextNonEmpty(wxGridCellCoords& coords, const wxGridDirectionOperations& diroper); diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 70ee786943..bff2bbfd27 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5650,31 +5650,35 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) switch ( event.GetKeyCode() ) { case WXK_UP: - if ( event.ControlDown() ) - MoveCursorUpBlock( event.ShiftDown() ); - else - MoveCursorUp( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridBackwardOperations(this, wxGridRowOperations()) + ); break; case WXK_DOWN: - if ( event.ControlDown() ) - MoveCursorDownBlock( event.ShiftDown() ); - else - MoveCursorDown( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridForwardOperations(this, wxGridRowOperations()) + ); break; case WXK_LEFT: - if ( event.ControlDown() ) - MoveCursorLeftBlock( event.ShiftDown() ); - else - MoveCursorLeft( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridBackwardOperations(this, wxGridColumnOperations()) + ); break; case WXK_RIGHT: - if ( event.ControlDown() ) - MoveCursorRightBlock( event.ShiftDown() ); - else - MoveCursorRight( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridForwardOperations(this, wxGridColumnOperations()) + ); break; case WXK_RETURN: @@ -7893,14 +7897,42 @@ int wxGrid::GetFirstFullyVisibleColumn() const // ------ Grid cursor movement functions // +namespace +{ + +// Helper function creating dummy wxKeyboardState object corresponding to the +// value of "expandSelection" flag specified to our public MoveCursorXXX(). +// This function only exists to avoid breaking compatibility in the public API, +// all internal code should use the real, not dummy, wxKeyboardState. +inline +wxKeyboardState DummyKeyboardState(bool expandSelection) +{ + // Normally "expandSelection" is set depending on whether Shift is pressed + // or not, but here we reconstruct the keyboard state from this flag. + return wxKeyboardState(false /* control */, expandSelection /* shift */); +} + +} // anonymous namespace + +void +wxGrid::DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) +{ + if ( kbdState.ControlDown() ) + DoMoveCursorByBlock(kbdState, diroper); + else + DoMoveCursor(kbdState, diroper); +} + bool -wxGrid::DoMoveCursor(bool expandSelection, +wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) { if ( m_currentCellCoords == wxGridNoCellCoords ) return false; - if ( expandSelection ) + // Expand selection if Shift is pressed. + if ( kbdState.ShiftDown() ) { if ( !m_selection ) return false; @@ -7925,7 +7957,7 @@ wxGrid::DoMoveCursor(bool expandSelection, if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, coords, - wxKeyboardState()) ) + kbdState) ) { // We want to show a line (a row or a column), not the end of // the selection block. And do it only if the selection block @@ -7951,25 +7983,25 @@ wxGrid::DoMoveCursor(bool expandSelection, bool wxGrid::MoveCursorUp(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridRowOperations())); } bool wxGrid::MoveCursorDown(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridRowOperations())); } bool wxGrid::MoveCursorLeft(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridColumnOperations())); } bool wxGrid::MoveCursorRight(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridColumnOperations())); } @@ -8022,7 +8054,7 @@ wxGrid::AdvanceToNextNonEmpty(wxGridCellCoords& coords, } bool -wxGrid::DoMoveCursorByBlock(bool expandSelection, +wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) { if ( !m_table || m_currentCellCoords == wxGridNoCellCoords ) @@ -8061,15 +8093,14 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, } } - if ( expandSelection ) + if ( kbdState.ShiftDown() ) { // TODO: Select the next block every time (not the same as now). - // And provide the keyboard state. if ( m_selection ) { if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, coords, - wxKeyboardState()) ) + kbdState) ) { // We want to show a line (a row or a column), not the end of // the selection block. And do it only if the selection block @@ -8090,7 +8121,7 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, bool wxGrid::MoveCursorUpBlock(bool expandSelection) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridRowOperations()) ); } @@ -8098,7 +8129,7 @@ bool wxGrid::MoveCursorUpBlock(bool expandSelection) bool wxGrid::MoveCursorDownBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridRowOperations()) ); } @@ -8106,7 +8137,7 @@ bool wxGrid::MoveCursorDownBlock( bool expandSelection ) bool wxGrid::MoveCursorLeftBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridColumnOperations()) ); } @@ -8114,7 +8145,7 @@ bool wxGrid::MoveCursorLeftBlock( bool expandSelection ) bool wxGrid::MoveCursorRightBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridColumnOperations()) ); } From b17d3ecb5278adea9c4ef19ffdef34ee53afd8b6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Apr 2020 17:38:08 +0200 Subject: [PATCH 29/65] Remove unused private BlockContain() function This was probably left over after wxGridBlockCoords::ContainsBlock() introduction. --- include/wx/generic/gridsel.h | 8 -------- src/generic/gridsel.cpp | 18 ------------------ 2 files changed, 26 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 5d57e5e447..d53e8815cd 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -92,14 +92,6 @@ public: wxVectorGridBlockCoords& GetBlocks() { return m_selection; } private: - int BlockContain( int topRow1, int leftCol1, - int bottomRow1, int rightCol1, - int topRow2, int leftCol2, - int bottomRow2, int rightCol2 ); - // returns 1, if Block1 contains Block2, - // -1, if Block2 contains Block1, - // 0, otherwise - void SelectBlockNoEvent(const wxGridBlockCoords& block) { SelectBlock(block.GetTopRow(), block.GetLeftCol(), diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index a7957bd777..f408b0b36c 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -697,24 +697,6 @@ wxArrayInt wxGridSelection::GetColSelection() const return result; } -int wxGridSelection::BlockContain( int topRow1, int leftCol1, - int bottomRow1, int rightCol1, - int topRow2, int leftCol2, - int bottomRow2, int rightCol2 ) -// returns 1, if Block1 contains Block2, -// -1, if Block2 contains Block1, -// 0, otherwise -{ - if ( topRow1 <= topRow2 && bottomRow2 <= bottomRow1 && - leftCol1 <= leftCol2 && rightCol2 <= rightCol1 ) - return 1; - else if ( topRow2 <= topRow1 && bottomRow1 <= bottomRow2 && - leftCol2 <= leftCol1 && rightCol1 <= rightCol2 ) - return -1; - - return 0; -} - void wxGridSelection::Select(const wxGridBlockCoords& block, const wxKeyboardState& kbd, bool sendEvent) From fe6d07d2d134a360039575f6230a8f0f5c310436 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 6 Apr 2020 02:26:02 +0200 Subject: [PATCH 30/65] Some minor comments wording changes in wxGridSelection Try to better explain the behaviour of the methods of this class. --- include/wx/generic/gridsel.h | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index d53e8815cd..9dd67a492a 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -61,14 +61,14 @@ public: void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); - // Extend (or shrink) the current selection block or select a new one. + // Extend (or shrink) the current selection block or create a new one. // blockStart and blockEnd specifies the opposite corners of the currently // edited selection block. In almost all cases blockStart equals to // wxGrid::m_currentCellCoords (the exception is when we scrolled out from // the top of the grid and select a column or scrolled right and select // a row: in this case the lowest visible row/column will be set as // current, not the first one). If the row or the column component of - // blockEnd parametr has value of -1, it means that the corresponding + // blockEnd parameter has value of -1, it means that the corresponding // component of the current block should not be changed. // Return true if the current block was actually changed or created. bool ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, @@ -103,15 +103,25 @@ private: void Select(const wxGridBlockCoords& block, const wxKeyboardState& kbd, bool sendEvent); - // If the new block containing any of the passed blocks, remove them. - // if a new block contained in the passed blockc, return. - // Otherwise add the new block to the blocks array. + // Ensure that the new "block" becomes part of "blocks", adding it to them + // if necessary and, if we do it, also removing any existing elements of + // "blocks" that become unnecessary because they're entirely contained in + // the new "block". However note that we may also not to have to add it at + // all, if it's already contained in one of the existing blocks. + // + // We don't currently check if the new block is contained by several + // existing blocks, as this would be more difficult and doesn't seem to be + // really needed in practice. void MergeOrAddBlock(wxVectorGridBlockCoords& blocks, const wxGridBlockCoords& block); - // The vector of selection blocks. We expect that the users select - // relatively small amount of blocks. K-D tree can be used to speed up - // searching speed. + // All currently selected blocks. We expect there to be a relatively small + // amount of them, even for very large grids, as each block must be + // selected by the user, so we store them unsorted. + // + // Selection may be empty, but if it isn't, the last block is special, as + // it is the current block, which is affected by operations such as + // extending the current selection from keyboard. wxVectorGridBlockCoords m_selection; wxGrid *m_grid; From 69a05b034007a0362c25b2419121bb655ce5736c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 6 Apr 2020 02:36:56 +0200 Subject: [PATCH 31/65] Use the same selection expansion logic for Shift-Ctrl-cursor Expanding the selection from keyboard with Ctrl pressed should move in the same way Ctrl-cursor does, but use the same selection anchor as Shift-cursor does instead of always using the current cell. This makes the expansion work much more intuitively in the grid, e.g. pressing Shift-Ctrl-Down in 1 2 3 4 grid when 1 and 2 are selected now selects all the cells instead of selecting 1 and 3 as it did before. --- include/wx/generic/grid.h | 6 +++++ src/generic/grid.cpp | 57 ++++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index e21baacd6b..946236d23c 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2767,6 +2767,12 @@ private: wxGridWindow *gridWindow) const; int PosToEdgeOfLine(int pos, const wxGridOperations& oper) const; + // Fill the coords with the cell coordinates to use for the movement + // extending the current selection. Return false if, for whatever reason, + // we can't expand the selection at all. + bool PrepareForSelectionExpansion(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper); + void DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); bool DoMoveCursor(const wxKeyboardState& kbdState, diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index bff2bbfd27..622795cfa7 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -7914,6 +7914,29 @@ wxKeyboardState DummyKeyboardState(bool expandSelection) } // anonymous namespace +bool +wxGrid::PrepareForSelectionExpansion(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper) +{ + coords.SetRow(m_selection->GetCurrentBlockCornerRow()); + coords.SetCol(m_selection->GetCurrentBlockCornerCol()); + + if ( coords == wxGridNoCellCoords ) + coords = m_currentCellCoords; + else if ( !diroper.IsValid(coords) ) + { + // The component of the current block corner in our direction + // is not valid. This means we can't change the selection block + // in this direction. + return false; + } + + if ( diroper.IsAtBoundary(coords) ) + return false; + + return true; +} + void wxGrid::DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) @@ -7937,20 +7960,8 @@ wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, if ( !m_selection ) return false; - wxGridCellCoords coords(m_selection->GetCurrentBlockCornerRow(), - m_selection->GetCurrentBlockCornerCol()); - - if ( coords == wxGridNoCellCoords ) - coords = m_currentCellCoords; - else if ( !diroper.IsValid(coords) ) - { - // The component of the current block corner in our direction - // is not valid. This means we can't change the selection block - // in this direction. - return false; - } - - if ( diroper.IsAtBoundary(coords) ) + wxGridCellCoords coords; + if ( !PrepareForSelectionExpansion(coords, diroper) ) return false; diroper.Advance(coords); @@ -8057,13 +8068,22 @@ bool wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) { - if ( !m_table || m_currentCellCoords == wxGridNoCellCoords ) + if ( !m_table ) return false; - if ( diroper.IsAtBoundary(m_currentCellCoords) ) - return false; + wxGridCellCoords coords; + + // Expand selection if Shift is pressed. + if ( kbdState.ShiftDown() ) + { + if ( !PrepareForSelectionExpansion(coords, diroper) ) + return false; + } + else + { + coords = m_currentCellCoords; + } - wxGridCellCoords coords(m_currentCellCoords); if ( m_table->IsEmpty(coords) ) { // we are in an empty cell: find the next block of non-empty cells @@ -8095,7 +8115,6 @@ wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, if ( kbdState.ShiftDown() ) { - // TODO: Select the next block every time (not the same as now). if ( m_selection ) { if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, From 7d0b3524858d55cf9a9207e11e2bd0418940492e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 6 Apr 2020 02:44:21 +0200 Subject: [PATCH 32/65] Add MSVS debug visualizers for wxGrid{Cell,Block}Coords This makes debugging wxGrid code much more pleasant. --- misc/msvc/wxWidgets.natvis | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/misc/msvc/wxWidgets.natvis b/misc/msvc/wxWidgets.natvis index b08b9d8449..ca15f7cdea 100644 --- a/misc/msvc/wxWidgets.natvis +++ b/misc/msvc/wxWidgets.natvis @@ -41,6 +41,14 @@ http://code.msdn.microsoft.com/windowsdesktop/Writing-type-visualizers-2eae77a2# {m_ll} + + R{m_row + 1}C{m_col + 1} + + + + R{m_topRow + 1}C{m_leftCol + 1}:R{m_bottomRow + 1}C{m_rightCol + 1} + + empty {m_pItems[0]} From f3ff89601f9a56f88c84f8e9e4e98fd5be86f5c4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 11 Apr 2020 15:04:32 +0200 Subject: [PATCH 33/65] Slightly clarify ExtendOrCreateCurrentBlock() code Don't use long-lived non-const "block" reference, this makes it difficult to see that it's never changed until the very end of the function, when it's updated. Compare "blockStart" with "block" itself instead of comparing it with "newBlock" which is initially its copy but gets modified later (however these modifications don't affect the comparisons involving it). Add a comment explaining why are we doing what we do here. --- src/generic/gridsel.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index f408b0b36c..e8cfdbf048 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -475,19 +475,23 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt return true; } - wxGridBlockCoords& block = *m_selection.rbegin(); + const wxGridBlockCoords& block = *m_selection.rbegin(); wxGridBlockCoords newBlock = block; bool editBlock = false; if ( blockEnd.GetRow() != -1 ) { - if ( newBlock.GetTopRow() == blockStart.GetRow() ) + // If the new block starts at the same top row as the current one, the + // end block coordinates must correspond to the new bottom row -- and + // vice versa, if the new block starts at the bottom, its other end + // must correspond to the top. + if ( blockStart.GetRow() == block.GetTopRow() ) { newBlock.SetBottomRow(blockEnd.GetRow()); editBlock = true; } - else if ( newBlock.GetBottomRow() == blockStart.GetRow() ) + else if ( blockStart.GetRow() == block.GetBottomRow() ) { newBlock.SetTopRow(blockEnd.GetRow()); editBlock = true; @@ -529,8 +533,8 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt } } - // Edit the current block. - block = newBlock; + // Update the current block in place. + *m_selection.rbegin() = newBlock; // Send Event. wxGridRangeSelectEvent gridEvt(m_grid->GetId(), From b095e67ac320d0a14be25a20b55e3ef75e4e84dd Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 11 Apr 2020 23:53:42 +0200 Subject: [PATCH 34/65] Show more detailed information about selection in the grid sample Show the selected cells/rows/columns/blocks instead of just showing their number, as we now have an efficient (i.e. taking time proportional to the number of selected blocks and not to the number of the individual cells, rows or columns) way of doing this. See #2576. --- samples/grid/griddemo.cpp | 78 ++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index dd0536bbdb..c115f5c2f1 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -1220,32 +1220,68 @@ void GridFrame::SetCornerLabelValue( wxCommandEvent& WXUNUSED(ev) ) void GridFrame::ShowSelection( wxCommandEvent& WXUNUSED(ev) ) { - switch ( grid->GetSelectionMode() ) + int count = 0; + wxString desc; + const wxGridSelectionRange& sel = grid->GetSelectionRange(); + for ( wxGridSelectionRange::iterator it = sel.begin(); + it != sel.end(); + ++it, ++count ) { - case wxGrid::wxGridSelectCells: - wxLogMessage("%zu individual cells and " - "%zu blocks of contiguous cells selected", - grid->GetSelectedCells().size(), - grid->GetSelectionBlockTopLeft().size()); - return; + const wxGridBlockCoords& b = *it; - case wxGrid::wxGridSelectRows: - case wxGrid::wxGridSelectColumns: - case wxGrid::wxGridSelectRowsOrColumns: - const wxArrayInt& rows = grid->GetSelectedRows(); - if ( !rows.empty() ) - wxLogMessage("%zu rows selected", rows.size()); + wxString blockDesc; + if ( b.GetLeftCol() == 0 && + b.GetRightCol() == grid->GetNumberCols() - 1 ) + { + if ( b.GetTopRow() == b.GetBottomRow() ) + blockDesc.Printf("row %d", b.GetTopRow() + 1); + else + blockDesc.Printf("rows %d..%d", + b.GetTopRow() + 1, b.GetBottomRow() + 1); + } + else if ( b.GetTopRow() == 0 && + b.GetBottomRow() == grid->GetNumberRows() - 1 ) + { + if ( b.GetLeftCol() == b.GetRightCol() ) + blockDesc.Printf("column %d", b.GetLeftCol() + 1); + else + blockDesc.Printf("columns %d..%d", + b.GetLeftCol() + 1, + b.GetRightCol() + 1); + } + else if ( b.GetTopRow() == b.GetBottomRow() && + b.GetLeftCol() == b.GetRightCol() ) + { + blockDesc.Printf("cell R%dC%d", + b.GetTopRow() + 1, b.GetLeftCol() + 1); + } + else + { + blockDesc.Printf("block R%dC%d - R%dC%d", + b.GetTopRow() + 1, + b.GetLeftCol() + 1, + b.GetBottomRow() + 1, + b.GetRightCol() + 1); + } - const wxArrayInt& cols = grid->GetSelectedCols(); - if ( !cols.empty() ) - wxLogMessage("%zu columns selected", rows.size()); - - if ( rows.empty() && cols.empty() ) - wxLogMessage("No selection"); - return; + if ( count ) + desc += "\n\t"; + desc += blockDesc; } - wxLogError("Unknown grid selection mode."); + switch ( count ) + { + case 0: + wxLogMessage("No selection"); + break; + + case 1: + wxLogMessage("Selection: %s", desc); + break; + + default: + wxLogMessage("%d selected blocks:\n\t%s", count, desc); + } } void GridFrame::SelectCells( wxCommandEvent& WXUNUSED(ev) ) From 1cb2ec06ee69fc0457c5afdab2019f03840671b7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 11 Apr 2020 23:59:44 +0200 Subject: [PATCH 35/65] Fix off-by-1 bugs in wxGrid::GetFirstFullyVisible{Row,Column}() These functions always returned the first partially visible line, not the first fully visible one as intended because IsXXXShown() always returned true when it applied to that line and we need to increment the index first, before calling it -- even if this requires writing the loop in a slightly uglier way. This fixes bugs in commits 0920a1646b (Make wxGrid row selecting more user friendly, 2020-03-04) and 89dd47edee (Make wxGrid column selecting more user friendly, 2020-03-04). --- src/generic/grid.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 622795cfa7..fe821cd87a 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -7848,9 +7848,13 @@ int wxGrid::GetFirstFullyVisibleRow() const if ( GetRowTop(row) - 2 < y ) { // Use the next visible row. - for ( ; row < m_numRows; ++row ) + for ( ;; ) { - if ( IsRowShown(row) ) + // But we can't go beyond the last row anyhow. + if ( row == m_numRows - 1 ) + break; + + if ( IsRowShown(++row) ) break; } } @@ -7882,9 +7886,12 @@ int wxGrid::GetFirstFullyVisibleColumn() const if ( GetColLeft(col) < x ) { // Use the next visible column. - for ( ; col < m_numCols; ++col ) + for ( ;; ) { - if ( IsColShown(GetColAt(col)) ) + if ( col == m_numCols - 1 ) + break; + + if ( IsColShown(GetColAt(++col)) ) break; } } From 7d5614699866a1fb5adfac865d56aef9a8d70d28 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 00:58:34 +0200 Subject: [PATCH 36/65] Add a comment for ClearSelection() No real changes, just mention that this function does more than just clearing the selection. --- include/wx/generic/gridsel.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 9dd67a492a..c6859d94d6 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -56,6 +56,8 @@ public: const wxKeyboardState& kbd = wxKeyboardState(), bool sendEvent = true ); + // Note that this method refreshes the previously selected blocks and sends + // an event about the selection change. void ClearSelection(); void UpdateRows( size_t pos, int numRows ); From 415bab551ce06c1e7fdc20290afa418a1fc6f2c1 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 01:06:42 +0200 Subject: [PATCH 37/65] Add wxGridSelection::SelectAll() and use it in wxGrid The difference between calling SelectAll() and SelectBlock() with a block covering the entire grid is that the former discards any previously selected blocks, which become clearly redundant. As a consequence, clicking on the grid corner 10 times in a row still results in a selection with a single block, not 10 (identical) blocks. --- include/wx/generic/gridsel.h | 4 ++++ src/generic/grid.cpp | 7 ++----- src/generic/gridsel.cpp | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index c6859d94d6..cb53078f2c 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -52,6 +52,10 @@ public: kbd, sendEvent); } + // This function replaces all the existing selected blocks (which become + // redundant) with a single block covering the entire grid. + void SelectAll(); + void DeselectBlock(const wxGridBlockCoords& block, const wxKeyboardState& kbd = wxKeyboardState(), bool sendEvent = true ); diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index fe821cd87a..56f1cfec6a 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10235,11 +10235,8 @@ void wxGrid::SelectBlock(int topRow, int leftCol, int bottomRow, int rightCol, void wxGrid::SelectAll() { - if ( m_numRows > 0 && m_numCols > 0 ) - { - if ( m_selection ) - m_selection->SelectBlock( 0, 0, m_numRows - 1, m_numCols - 1 ); - } + if ( m_selection ) + m_selection->SelectAll(); } // ---------------------------------------------------------------------------- diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index e8cfdbf048..cc361a0d97 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -194,6 +194,23 @@ void wxGridSelection::SelectBlock( int topRow, int leftCol, kbd, sendEvent); } +void +wxGridSelection::SelectAll() +{ + // There is no need to refresh anything, as Select() will do it anyhow, and + // no need to generate any events, so do not call ClearSelection() here. + m_selection.clear(); + + const int numRows = m_grid->GetNumberRows(); + const int numCols = m_grid->GetNumberCols(); + + if ( numRows && numCols ) + { + Select(wxGridBlockCoords(0, 0, numRows - 1, numCols - 1), + wxKeyboardState(), true /* send event */); + } +} + void wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, const wxKeyboardState& kbd, From c90bed6f8ed0bbebf36a15dab786e0cae9e8a4ef Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 01:36:53 +0200 Subject: [PATCH 38/65] Stop passing invalid coordinates to ExtendOrCreateCurrentBlock() This made the logic of this function unnecessarily more complicated. Instead, just fall back to the current cell coordinates in the only place where this could happen before. Doing this still preserves the correct behaviour of Shift-arrow selection when entire rows/columns are selected and the current cell is not the leftmost/topmost cell (due to scrolling), but the code is simpler. Remove the now always true condition check and assert that it's indeed always true. Note that the changes to gridsel.cpp in this commit are best viewed ignoring whitespace changes. --- include/wx/generic/gridsel.h | 7 +++-- src/generic/grid.cpp | 15 ++++++--- src/generic/gridsel.cpp | 60 ++++++++++++++++-------------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index cb53078f2c..1a64387c84 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -73,9 +73,10 @@ public: // wxGrid::m_currentCellCoords (the exception is when we scrolled out from // the top of the grid and select a column or scrolled right and select // a row: in this case the lowest visible row/column will be set as - // current, not the first one). If the row or the column component of - // blockEnd parameter has value of -1, it means that the corresponding - // component of the current block should not be changed. + // current, not the first one). + // + // Both components of both blockStart and blockEnd must be valid. + // // Return true if the current block was actually changed or created. bool ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, const wxGridCellCoords& blockEnd, diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 56f1cfec6a..e8b0734dfa 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -7925,12 +7925,17 @@ bool wxGrid::PrepareForSelectionExpansion(wxGridCellCoords& coords, const wxGridDirectionOperations& diroper) { - coords.SetRow(m_selection->GetCurrentBlockCornerRow()); - coords.SetCol(m_selection->GetCurrentBlockCornerCol()); + int row = m_selection->GetCurrentBlockCornerRow(); + if ( row == -1 ) + row = m_currentCellCoords.GetRow(); - if ( coords == wxGridNoCellCoords ) - coords = m_currentCellCoords; - else if ( !diroper.IsValid(coords) ) + int col = m_selection->GetCurrentBlockCornerCol(); + if ( col == -1 ) + col = m_currentCellCoords.GetCol(); + + coords.Set(row, col); + + if ( !diroper.IsValid(coords) ) { // The component of the current block corner in our direction // is not valid. This means we can't change the selection block diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index cc361a0d97..963717aac0 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -486,6 +486,9 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt const wxGridCellCoords& blockEnd, const wxKeyboardState& kbd) { + wxASSERT( blockStart.GetRow() != -1 && blockStart.GetCol() != -1 && + blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1 ); + if ( m_selection.empty() ) { SelectBlock(blockStart, blockEnd); @@ -497,42 +500,34 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt bool editBlock = false; - if ( blockEnd.GetRow() != -1 ) + // If the new block starts at the same top row as the current one, the + // end block coordinates must correspond to the new bottom row -- and + // vice versa, if the new block starts at the bottom, its other end + // must correspond to the top. + if ( blockStart.GetRow() == block.GetTopRow() ) { - // If the new block starts at the same top row as the current one, the - // end block coordinates must correspond to the new bottom row -- and - // vice versa, if the new block starts at the bottom, its other end - // must correspond to the top. - if ( blockStart.GetRow() == block.GetTopRow() ) - { - newBlock.SetBottomRow(blockEnd.GetRow()); - editBlock = true; - } - else if ( blockStart.GetRow() == block.GetBottomRow() ) - { - newBlock.SetTopRow(blockEnd.GetRow()); - editBlock = true; - } + newBlock.SetBottomRow(blockEnd.GetRow()); + editBlock = true; } - if ( blockEnd.GetCol() != -1 ) + else if ( blockStart.GetRow() == block.GetBottomRow() ) { - if ( newBlock.GetLeftCol() == blockStart.GetCol() ) - { - newBlock.SetRightCol(blockEnd.GetCol()); - editBlock = true; - } - else if ( newBlock.GetRightCol() == blockStart.GetCol() ) - { - newBlock.SetLeftCol(blockEnd.GetCol()); - editBlock = true; - } + newBlock.SetTopRow(blockEnd.GetRow()); + editBlock = true; + } + + if ( blockStart.GetCol() == block.GetLeftCol() ) + { + newBlock.SetRightCol(blockEnd.GetCol()); + editBlock = true; + } + else if ( blockStart.GetCol() == block.GetRightCol() ) + { + newBlock.SetLeftCol(blockEnd.GetCol()); + editBlock = true; } newBlock = newBlock.Canonicalize(); - const bool endCoordsSet = - blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1; - if ( editBlock ) { if ( newBlock == block ) @@ -562,15 +557,14 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt true, kbd); m_grid->GetEventHandler()->ProcessEvent(gridEvt); - return true; } - else if ( endCoordsSet ) + else { // Select the new one. SelectBlock(newBlock.GetTopLeft(), newBlock.GetBottomRight(), kbd); - return true; } - return false; + + return true; } int wxGridSelection::GetCurrentBlockCornerRow() const From 32bb5e91577480bacd6ab7542053a45ff9e8bd49 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 02:13:09 +0200 Subject: [PATCH 39/65] Fix Shift-click/arrow behaviour for column/row selection Extend the current block to the entire line when the corresponding header is Shift-clicked and, importantly, keep the full-line selection when using Shift-arrows later to make the selection behave in the expected way. --- include/wx/generic/gridsel.h | 12 +++-- src/generic/gridsel.cpp | 93 ++++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 1a64387c84..aeed559ecc 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -67,17 +67,19 @@ public: void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); - // Extend (or shrink) the current selection block or create a new one. - // blockStart and blockEnd specifies the opposite corners of the currently - // edited selection block. In almost all cases blockStart equals to - // wxGrid::m_currentCellCoords (the exception is when we scrolled out from + // Extend (or shrink) the current selection block to the one specified by + // the start and end coordinates of its opposite corners (which don't have + // to be in top/bottom left/right order). + // + // Note that blockStart is equal to wxGrid::m_currentCellCoords almost + // always, but not always (the exception is when we scrolled out from // the top of the grid and select a column or scrolled right and select // a row: in this case the lowest visible row/column will be set as // current, not the first one). // // Both components of both blockStart and blockEnd must be valid. // - // Return true if the current block was actually changed or created. + // Return true if the current block was actually changed. bool ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, const wxGridCellCoords& blockEnd, const wxKeyboardState& kbd); diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 963717aac0..6c67f263a9 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -498,8 +498,6 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt const wxGridBlockCoords& block = *m_selection.rbegin(); wxGridBlockCoords newBlock = block; - bool editBlock = false; - // If the new block starts at the same top row as the current one, the // end block coordinates must correspond to the new bottom row -- and // vice versa, if the new block starts at the bottom, its other end @@ -507,63 +505,84 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt if ( blockStart.GetRow() == block.GetTopRow() ) { newBlock.SetBottomRow(blockEnd.GetRow()); - editBlock = true; } else if ( blockStart.GetRow() == block.GetBottomRow() ) { newBlock.SetTopRow(blockEnd.GetRow()); - editBlock = true; + } + else // current and new block don't have common row boundary + { + // This can happen when mixing entire column and cell selection, e.g. + // by Shift-clicking on the column header. In this case, the right + // thing to do is to just expand the current block to the new one + // boundaries, extending the selection to the entire column height when + // a column is selected. However notice that we should not shrink the + // current block here, in order to allow Shift-Left/Right (which don't + // know anything about the column selection and so just use single row + // blocks) to keep the full column selection. + int top = blockStart.GetRow(), + bottom = blockEnd.GetRow(); + if ( top > bottom ) + wxSwap(top, bottom); + + if ( top < newBlock.GetTopRow() ) + newBlock.SetTopRow(top); + if ( bottom > newBlock.GetBottomRow() ) + newBlock.SetBottomRow(bottom); } + // Same as above but mirrored for columns. if ( blockStart.GetCol() == block.GetLeftCol() ) { newBlock.SetRightCol(blockEnd.GetCol()); - editBlock = true; } else if ( blockStart.GetCol() == block.GetRightCol() ) { newBlock.SetLeftCol(blockEnd.GetCol()); - editBlock = true; + } + else + { + int left = blockStart.GetCol(), + right = blockEnd.GetCol(); + if ( left > right ) + wxSwap(left, right); + + if ( left < newBlock.GetLeftCol() ) + newBlock.SetLeftCol(left); + if ( right > newBlock.GetRightCol() ) + newBlock.SetRightCol(right); } newBlock = newBlock.Canonicalize(); - if ( editBlock ) - { - if ( newBlock == block ) - return false; + if ( newBlock == block ) + return false; - // Update View. - if ( !m_grid->GetBatchCount() ) + // Update View. + if ( !m_grid->GetBatchCount() ) + { + wxGridBlockDiffResult refreshBlocks = block.SymDifference(newBlock); + for ( int i = 0; i < 4; ++i ) { - wxGridBlockDiffResult refreshBlocks = block.SymDifference(newBlock); - for ( int i = 0; i < 4; ++i ) - { - const wxGridBlockCoords& refreshBlock = refreshBlocks.m_parts[i]; - m_grid->RefreshBlock(refreshBlock.GetTopLeft(), - refreshBlock.GetBottomRight()); - } + const wxGridBlockCoords& refreshBlock = refreshBlocks.m_parts[i]; + m_grid->RefreshBlock(refreshBlock.GetTopLeft(), + refreshBlock.GetBottomRight()); } - - // Update the current block in place. - *m_selection.rbegin() = newBlock; - - // Send Event. - wxGridRangeSelectEvent gridEvt(m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - newBlock.GetTopLeft(), - newBlock.GetBottomRight(), - true, - kbd); - m_grid->GetEventHandler()->ProcessEvent(gridEvt); - } - else - { - // Select the new one. - SelectBlock(newBlock.GetTopLeft(), newBlock.GetBottomRight(), kbd); } + // Update the current block in place. + *m_selection.rbegin() = newBlock; + + // Send Event. + wxGridRangeSelectEvent gridEvt(m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + newBlock.GetTopLeft(), + newBlock.GetBottomRight(), + true, + kbd); + m_grid->GetEventHandler()->ProcessEvent(gridEvt); + return true; } From b10755e5530d7f1fa1a490a8873b20d47d343929 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 02:27:50 +0200 Subject: [PATCH 40/65] Fix selection expansion in row/column-only selection modes We should never adjust the fixed coordinate of the selection blocks in this mode, so ensure we never end up with a partially selected line in this case by preserving the -- correct by construction -- current block coordinates in this direction. --- src/generic/gridsel.cpp | 102 ++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 6c67f263a9..a277c69647 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -498,59 +498,69 @@ bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockSt const wxGridBlockCoords& block = *m_selection.rbegin(); wxGridBlockCoords newBlock = block; - // If the new block starts at the same top row as the current one, the - // end block coordinates must correspond to the new bottom row -- and - // vice versa, if the new block starts at the bottom, its other end - // must correspond to the top. - if ( blockStart.GetRow() == block.GetTopRow() ) + // Don't adjust the blocks rows at all in column selection mode as the + // top/bottom row are always fixed to the first/last grid row anyhow in + // this case and we shouldn't select only part of a column just because the + // user Shift-clicked somewhere in the middle of the grid. + if ( m_selectionMode != wxGrid::wxGridSelectColumns ) { - newBlock.SetBottomRow(blockEnd.GetRow()); - } - else if ( blockStart.GetRow() == block.GetBottomRow() ) - { - newBlock.SetTopRow(blockEnd.GetRow()); - } - else // current and new block don't have common row boundary - { - // This can happen when mixing entire column and cell selection, e.g. - // by Shift-clicking on the column header. In this case, the right - // thing to do is to just expand the current block to the new one - // boundaries, extending the selection to the entire column height when - // a column is selected. However notice that we should not shrink the - // current block here, in order to allow Shift-Left/Right (which don't - // know anything about the column selection and so just use single row - // blocks) to keep the full column selection. - int top = blockStart.GetRow(), - bottom = blockEnd.GetRow(); - if ( top > bottom ) - wxSwap(top, bottom); + // If the new block starts at the same top row as the current one, the + // end block coordinates must correspond to the new bottom row -- and + // vice versa, if the new block starts at the bottom, its other end + // must correspond to the top. + if ( blockStart.GetRow() == block.GetTopRow() ) + { + newBlock.SetBottomRow(blockEnd.GetRow()); + } + else if ( blockStart.GetRow() == block.GetBottomRow() ) + { + newBlock.SetTopRow(blockEnd.GetRow()); + } + else // current and new block don't have common row boundary + { + // This can happen when mixing entire column and cell selection, e.g. + // by Shift-clicking on the column header. In this case, the right + // thing to do is to just expand the current block to the new one + // boundaries, extending the selection to the entire column height when + // a column is selected. However notice that we should not shrink the + // current block here, in order to allow Shift-Left/Right (which don't + // know anything about the column selection and so just use single row + // blocks) to keep the full column selection. + int top = blockStart.GetRow(), + bottom = blockEnd.GetRow(); + if ( top > bottom ) + wxSwap(top, bottom); - if ( top < newBlock.GetTopRow() ) - newBlock.SetTopRow(top); - if ( bottom > newBlock.GetBottomRow() ) - newBlock.SetBottomRow(bottom); + if ( top < newBlock.GetTopRow() ) + newBlock.SetTopRow(top); + if ( bottom > newBlock.GetBottomRow() ) + newBlock.SetBottomRow(bottom); + } } // Same as above but mirrored for columns. - if ( blockStart.GetCol() == block.GetLeftCol() ) + if ( m_selectionMode != wxGrid::wxGridSelectRows ) { - newBlock.SetRightCol(blockEnd.GetCol()); - } - else if ( blockStart.GetCol() == block.GetRightCol() ) - { - newBlock.SetLeftCol(blockEnd.GetCol()); - } - else - { - int left = blockStart.GetCol(), - right = blockEnd.GetCol(); - if ( left > right ) - wxSwap(left, right); + if ( blockStart.GetCol() == block.GetLeftCol() ) + { + newBlock.SetRightCol(blockEnd.GetCol()); + } + else if ( blockStart.GetCol() == block.GetRightCol() ) + { + newBlock.SetLeftCol(blockEnd.GetCol()); + } + else + { + int left = blockStart.GetCol(), + right = blockEnd.GetCol(); + if ( left > right ) + wxSwap(left, right); - if ( left < newBlock.GetLeftCol() ) - newBlock.SetLeftCol(left); - if ( right > newBlock.GetRightCol() ) - newBlock.SetRightCol(right); + if ( left < newBlock.GetLeftCol() ) + newBlock.SetLeftCol(left); + if ( right > newBlock.GetRightCol() ) + newBlock.SetRightCol(right); + } } newBlock = newBlock.Canonicalize(); From 30eaa28de5a0d6a80ad685134cdbf9af8e29e523 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 02:36:52 +0200 Subject: [PATCH 41/65] Rename wxGrid::GetSelectionRange() to GetSelectedBlocks() This seems to be more consistent with the existing functions and doesn't create ambiguity with a grid range. Also rename wxGridSelectionRange to just wxGridBlocks as, in principle, this class could be used for iterating over any blocks, not just the selected ones. No changes in functionality, this is just a renaming. --- include/wx/generic/grid.h | 12 ++++++------ interface/wx/grid.h | 24 ++++++++++++------------ samples/grid/griddemo.cpp | 8 +++----- src/generic/grid.cpp | 6 +++--- tests/controls/gridtest.cpp | 6 +++--- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 946236d23c..cbd76d749c 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -876,10 +876,10 @@ struct wxGridBlockDiffResult }; // ---------------------------------------------------------------------------- -// wxGridSelectionRange: a range of grid blocks that can be iterated over +// wxGridBlocks: a range of grid blocks that can be iterated over // ---------------------------------------------------------------------------- -class wxGridSelectionRange +class wxGridBlocks { typedef wxVector::const_iterator iterator_impl; @@ -913,7 +913,7 @@ public: iterator_impl m_it; - friend class wxGridSelectionRange; + friend class wxGridBlocks; }; iterator begin() const @@ -927,13 +927,13 @@ public: } private: - wxGridSelectionRange() : + wxGridBlocks() : m_begin(), m_end() { } - wxGridSelectionRange(iterator_impl begin, iterator_impl end) : + wxGridBlocks(iterator_impl begin, iterator_impl end) : m_begin(begin), m_end(end) { @@ -1994,7 +1994,7 @@ public: bool IsInSelection( const wxGridCellCoords& coords ) const { return IsInSelection( coords.GetRow(), coords.GetCol() ); } - wxGridSelectionRange GetSelectionRange() const; + wxGridBlocks GetSelectedBlocks() const; wxGridCellCoordsArray GetSelectedCells() const; wxGridCellCoordsArray GetSelectionBlockTopLeft() const; wxGridCellCoordsArray GetSelectionBlockBottomRight() const; diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 6122f6b3fa..d05931fe4b 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -2004,7 +2004,7 @@ struct wxGridBlockDiffResult }; /** - Represents a range of grid blocks that can be iterated over. + Represents a collection of grid blocks that can be iterated over. This class provides read-only access to the blocks making up the grid selection in the most general case. @@ -2014,14 +2014,14 @@ struct wxGridBlockDiffResult The preferable way to iterate over it is using C++11 range-for loop: @code - for ( const auto block: grid->GetSelectionRange() ) { + for ( const auto& block: grid->GetSelectedBlocks() ) { ... do something with block ... } @endcode When not using C++11, iteration has to be done manually: @code - wxGridSelectionRange range = grid->GetSelectionRange(); - for ( wxGridSelectionRange::iterator it = range.begin(); + wxGridBlocks range = grid->GetSelectedBlocks(); + for ( wxGridBlocks::iterator it = range.begin(); it != range.end(); ++it ) { ... do something with *it ... @@ -2030,7 +2030,7 @@ struct wxGridBlockDiffResult @since 3.1.4 */ -class wxGridSelectionRange +class wxGridBlocks { public: /** @@ -4668,7 +4668,7 @@ public: The returned range can be iterated over, e.g. with C++11 range-for loop: @code - for ( const auto block: grid->GetSelectionRange() ) { + for ( const auto block: grid->GetSelectedBlocks() ) { if ( block.Intersects(myBlock) ) break; } @@ -4676,7 +4676,7 @@ public: @since 3.1.4 */ - wxGridSelectionRange GetSelectionRange() const; + wxGridBlocks GetSelectedBlocks() const; /** Returns an array of individually selected cells. @@ -4694,7 +4694,7 @@ public: a million of entries in this function, instead it returns an empty array and GetSelectedCols() returns an array containing one element). - The function can be slow for the big grids, use GetSelectionRange() + The function can be slow for the big grids, use GetSelectedBlocks() in the new code. */ wxGridCellCoordsArray GetSelectedCells() const; @@ -4708,7 +4708,7 @@ public: or being selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. - The function can be slow for the big grids, use GetSelectionRange() + The function can be slow for the big grids, use GetSelectedBlocks() in the new code. */ wxArrayInt GetSelectedCols() const; @@ -4722,7 +4722,7 @@ public: selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. - The function can be slow for the big grids, use GetSelectionRange() + The function can be slow for the big grids, use GetSelectedBlocks() in the new code. */ wxArrayInt GetSelectedRows() const; @@ -4739,7 +4739,7 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. - The function can be slow for the big grids, use GetSelectionRange() + The function can be slow for the big grids, use GetSelectedBlocks() in the new code. @see GetSelectionBlockTopLeft() @@ -4752,7 +4752,7 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. - The function can be slow for the big grids, use GetSelectionRange() + The function can be slow for the big grids, use GetSelectedBlocks() in the new code. @see GetSelectionBlockBottomRight() diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index c115f5c2f1..5e314e21b7 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -1222,10 +1222,8 @@ void GridFrame::ShowSelection( wxCommandEvent& WXUNUSED(ev) ) { int count = 0; wxString desc; - const wxGridSelectionRange& sel = grid->GetSelectionRange(); - for ( wxGridSelectionRange::iterator it = sel.begin(); - it != sel.end(); - ++it, ++count ) + const wxGridBlocks& sel = grid->GetSelectedBlocks(); + for ( wxGridBlocks::iterator it = sel.begin(); it != sel.end(); ++it ) { const wxGridBlockCoords& b = *it; @@ -1264,7 +1262,7 @@ void GridFrame::ShowSelection( wxCommandEvent& WXUNUSED(ev) ) b.GetRightCol() + 1); } - if ( count ) + if ( count++ ) desc += "\n\t"; desc += blockDesc; } diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index e8b0734dfa..6dfd5f9b72 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -10284,13 +10284,13 @@ bool wxGrid::IsInSelection( int row, int col ) const return m_selection && m_selection->IsInSelection(row, col); } -wxGridSelectionRange wxGrid::GetSelectionRange() const +wxGridBlocks wxGrid::GetSelectedBlocks() const { if ( !m_selection ) - return wxGridSelectionRange(); + return wxGridBlocks(); const wxVectorGridBlockCoords& blocks = m_selection->GetBlocks(); - return wxGridSelectionRange(blocks.begin(), blocks.end()); + return wxGridBlocks(blocks.begin(), blocks.end()); } wxGridCellCoordsArray wxGrid::GetSelectedCells() const diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 67892326eb..5aa431a623 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -526,19 +526,19 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") TEST_CASE_METHOD(GridTestCase, "Grid::SelectionRange", "[grid]") { - const wxGridSelectionRange empty = m_grid->GetSelectionRange(); + const wxGridBlocks empty = m_grid->GetSelectedBlocks(); CHECK( empty.begin() == empty.end() ); m_grid->SelectBlock(1, 0, 3, 1); - wxGridSelectionRange sel = m_grid->GetSelectionRange(); + wxGridBlocks sel = m_grid->GetSelectedBlocks(); REQUIRE( sel.begin() != sel.end() ); CHECK( *sel.begin() == wxGridBlockCoords(1, 0, 3, 1) ); #if __cplusplus >= 201103L || wxCHECK_VISUALC_VERSION(10) m_grid->SelectBlock(4, 0, 7, 1, true); int index = 0; - for ( const wxGridBlockCoords& block : m_grid->GetSelectionRange() ) + for ( const wxGridBlockCoords& block : m_grid->GetSelectedBlocks() ) { switch ( index ) { From b98f439686ea487a100956c013f887d133a51582 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 02:42:37 +0200 Subject: [PATCH 42/65] Also rename wxGridSelection::ExtendOrCreateCurrentBlock() This function finally doesn't ever create a new block, except for the trivial case when there is no current block, so rename it to a simpler and more clear name. No real changes. --- include/wx/generic/gridsel.h | 13 +++++++------ src/generic/grid.cpp | 28 +++++++++++++--------------- src/generic/gridsel.cpp | 6 +++--- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index aeed559ecc..3e078653e5 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -67,9 +67,10 @@ public: void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); - // Extend (or shrink) the current selection block to the one specified by - // the start and end coordinates of its opposite corners (which don't have - // to be in top/bottom left/right order). + // Extend (or shrink) the current selection block (creating it if + // necessary, i.e. if there is no selection at all currently) to the one + // specified by the start and end coordinates of its opposite corners + // (which don't have to be in top/bottom left/right order). // // Note that blockStart is equal to wxGrid::m_currentCellCoords almost // always, but not always (the exception is when we scrolled out from @@ -80,9 +81,9 @@ public: // Both components of both blockStart and blockEnd must be valid. // // Return true if the current block was actually changed. - bool ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, - const wxGridCellCoords& blockEnd, - const wxKeyboardState& kbd); + bool ExtendCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd); // Return the row of the current selection block if it exists and we can diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 6dfd5f9b72..b2f6fb9db2 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3675,7 +3675,7 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( (row = YToRow( pos.y )) >= 0 ) { - m_selection->ExtendOrCreateCurrentBlock( + m_selection->ExtendCurrentBlock( wxGridCellCoords(m_currentCellCoords.GetRow(), 0), wxGridCellCoords(row, GetNumberCols() - 1), event); @@ -3728,7 +3728,7 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo { // Continue editing the current selection and don't // move the grid cursor. - m_selection->ExtendOrCreateCurrentBlock + m_selection->ExtendCurrentBlock ( wxGridCellCoords(m_currentCellCoords.GetRow(), 0), wxGridCellCoords(row, GetNumberCols() - 1), @@ -4006,7 +4006,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) break; - m_selection->ExtendOrCreateCurrentBlock( + m_selection->ExtendCurrentBlock( wxGridCellCoords(0, m_currentCellCoords.GetCol()), wxGridCellCoords(GetNumberRows() - 1, col), event); @@ -4126,7 +4126,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo { // Continue editing the current selection and don't // move the grid cursor. - m_selection->ExtendOrCreateCurrentBlock + m_selection->ExtendCurrentBlock ( wxGridCellCoords(0, m_currentCellCoords.GetCol()), wxGridCellCoords(GetNumberRows() - 1, col), @@ -4490,7 +4490,7 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, // Edit the current selection block independently of the modifiers state. if ( m_selection ) - m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, coords, event); + m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); return performDefault; } @@ -4535,9 +4535,7 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, { if ( m_selection ) { - m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, - coords, - event); + m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); MakeCellVisible(coords); } } @@ -5789,7 +5787,7 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) if ( event.ShiftDown() ) { if ( m_selection ) - m_selection->ExtendOrCreateCurrentBlock( + m_selection->ExtendCurrentBlock( m_currentCellCoords, wxGridCellCoords(row, col), event); @@ -7978,9 +7976,9 @@ wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, diroper.Advance(coords); - if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, - coords, - kbdState) ) + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, + coords, + kbdState) ) { // We want to show a line (a row or a column), not the end of // the selection block. And do it only if the selection block @@ -8129,9 +8127,9 @@ wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, { if ( m_selection ) { - if ( m_selection->ExtendOrCreateCurrentBlock(m_currentCellCoords, - coords, - kbdState) ) + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, + coords, + kbdState) ) { // We want to show a line (a row or a column), not the end of // the selection block. And do it only if the selection block diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index a277c69647..bd1652f25d 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -482,9 +482,9 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) } } -bool wxGridSelection::ExtendOrCreateCurrentBlock(const wxGridCellCoords& blockStart, - const wxGridCellCoords& blockEnd, - const wxKeyboardState& kbd) +bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd) { wxASSERT( blockStart.GetRow() != -1 && blockStart.GetCol() != -1 && blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1 ); From 4cabffb9835b8aa2af4aad1e77cd6c175179255d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 18:29:26 +0200 Subject: [PATCH 43/65] Fix test for "almost visible" row in GetFirstFullyVisibleRow() The test didn't correspond to the comment and resulted in always considering the first row not visible instead of the intended effect. --- src/generic/grid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index b2f6fb9db2..e4b6117ba6 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -7843,7 +7843,7 @@ int wxGrid::GetFirstFullyVisibleRow() const // If the row is not fully visible (if only 2 pixels is hidden // the row still looks fully visible). - if ( GetRowTop(row) - 2 < y ) + if ( GetRowTop(row) + 2 < y ) { // Use the next visible row. for ( ;; ) From 4f7ed07f5eaeac695d16454701ae3ca0d3f7f947 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 18:39:34 +0200 Subject: [PATCH 44/65] Don't use row/column headers for selection incompatible with mode Mouse events in row/column headers should be just ignored when the current selection mode disallows row/column selection. This wasn't the case before, and while actually selecting the line corresponding to the header was disallowed further down the call chain, clicking it still unexpectedly cleared the existing selection and dragging mouse in the header window selected the entire grid. Fix this by just never entering the corresponding cursor mode in the first place if it's incompatible with the current selection mode. --- src/generic/grid.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index e4b6117ba6..754bca5f16 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3720,7 +3720,11 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( row >= 0 && !SendEvent( wxEVT_GRID_LABEL_LEFT_CLICK, row, -1, event ) ) { - if ( m_selection && m_numRows > 0 && m_numCols > 0 ) + // Check if row selection is possible and allowed, before doing + // anything else, including changing the cursor mode to "select + // row". + if ( m_selection && m_numRows > 0 && m_numCols > 0 && + m_selection->GetSelectionMode() != wxGridSelectColumns ) { bool selectNewRow = false; @@ -3756,9 +3760,9 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo SetCurrentCell(row, GetFirstFullyVisibleColumn()); } - } - ChangeCursorMode(WXGRID_CURSOR_SELECT_ROW, rowLabelWin); + ChangeCursorMode(WXGRID_CURSOR_SELECT_ROW, rowLabelWin); + } } } } @@ -4118,7 +4122,8 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo } else { - if ( m_selection && m_numRows > 0 && m_numCols > 0 ) + if ( m_selection && m_numRows > 0 && m_numCols > 0 && + m_selection->GetSelectionMode() != wxGridSelectRows ) { bool selectNewCol = false; @@ -4154,9 +4159,9 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo SetCurrentCell(GetFirstFullyVisibleRow(), col); } - } - ChangeCursorMode(WXGRID_CURSOR_SELECT_COL, colLabelWin); + ChangeCursorMode(WXGRID_CURSOR_SELECT_COL, colLabelWin); + } } } } From 9b031be8d15c87a6a39450d871b06c3f3bb7d01a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Apr 2020 18:57:59 +0200 Subject: [PATCH 45/65] Fix fatal bug in wxGridSelection::DeselectBlock() The selBlock reference could be used after the vector invalidation, which resulted in using the wrong vector element at best and could probably lead to a crash at worst. Don't use it after invalidating the vector any longer. This notably fixes a bug in row-or-column selection mode where the grid wasn't updated visually after modifying its selection by deselecting a single cell as soon as there were more than one selected row or column. --- src/generic/gridsel.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index bd1652f25d..2d473c0ede 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -290,14 +290,15 @@ wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, break; } - // remove the block + const wxGridBlockDiffResult result = + selBlock.Difference(canonicalizedBlock, splitOrientation); + + // remove the block (note that selBlock, being a reference, is + // invalidated here and can't be used any more below) m_selection.erase(m_selection.begin() + n); n--; count--; - wxGridBlockDiffResult result = - selBlock.Difference(canonicalizedBlock, splitOrientation); - for ( int i = 0; i < 2; ++i ) { const wxGridBlockCoords& part = result.m_parts[i]; From bc3c6fea707bbaf04537c8b4e10c0a65d5e5dd42 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 00:14:53 +0200 Subject: [PATCH 46/65] Fix Shift-Ctrl-arrows handling Extending the selection with Ctrl-arrows is different from all the other cases, as we need to combine both the selection anchor and the current cell coordinates when doing it. This means that we can't reuse the same PrepareForSelectionExpansion() helper for this case, so this function is not useful finally and this commit removes it entirely. It also replaces GetCurrentBlockCornerRow() and GetCurrentBlockCornerCol() functions with GetExtensionAnchor() which combines both of them. Finally, it adds wxGridDirectionOperations::TryToAdvance() helper to avoid repeating the IsAtBoundary() check which was previously part of PrepareForSelectionExpansion() in multiple places. And because the "extending" and normal parts of DoMoveCursorByBlock() are so different now, it also factors out AdvanceByBlock() helper which can be used to keep these parts well separate from each other instead of intermixing them together. With all these preparatory changes, it's finally possible to implement the "extending selection by block" logic relatively easily, with the bulk of this branch actually taken by comments explaining why do we have to do what we do. Add unit tests verifying that the functions used by Shift-Ctrl-arrow work as expected. --- include/wx/generic/grid.h | 8 +- include/wx/generic/gridsel.h | 12 +-- include/wx/generic/private/grid.h | 15 +++ src/generic/grid.cpp | 156 ++++++++++++++++-------------- src/generic/gridsel.cpp | 33 +++---- tests/controls/gridtest.cpp | 40 ++++++++ 6 files changed, 158 insertions(+), 106 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index cbd76d749c..e97d5a1f7d 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2767,12 +2767,6 @@ private: wxGridWindow *gridWindow) const; int PosToEdgeOfLine(int pos, const wxGridOperations& oper) const; - // Fill the coords with the cell coordinates to use for the movement - // extending the current selection. Return false if, for whatever reason, - // we can't expand the selection at all. - bool PrepareForSelectionExpansion(wxGridCellCoords& coords, - const wxGridDirectionOperations& diroper); - void DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); bool DoMoveCursor(const wxKeyboardState& kbdState, @@ -2782,6 +2776,8 @@ private: const wxGridDirectionOperations& diroper); void AdvanceToNextNonEmpty(wxGridCellCoords& coords, const wxGridDirectionOperations& diroper); + bool AdvanceByBlock(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper); // common part of {Insert,Delete}{Rows,Cols} bool DoModifyLines(bool (wxGridTableBase::*funcModify)(size_t, size_t), diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 3e078653e5..18377431ef 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -86,12 +86,12 @@ public: const wxKeyboardState& kbd); - // Return the row of the current selection block if it exists and we can - // edit the block vertically. Otherwise return -1. - int GetCurrentBlockCornerRow() const; - // Return the column of the current selection block if it exists and we can - // edit the block horizontally. Otherwise return -1. - int GetCurrentBlockCornerCol() const; + // Return the coordinates of the cell from which the selection should + // continue to be extended. This is normally the opposite corner of the + // last selected block from the current cell coordinates. + // + // If there is no selection, just returns the current cell coordinates. + wxGridCellCoords GetExtensionAnchor() const; wxGridCellCoordsArray GetCellSelection() const; wxGridCellCoordsArray GetBlockSelectionTopLeft() const; diff --git a/include/wx/generic/private/grid.h b/include/wx/generic/private/grid.h index 82703c4b9d..318e88293b 100644 --- a/include/wx/generic/private/grid.h +++ b/include/wx/generic/private/grid.h @@ -807,8 +807,23 @@ public: } // Increment the component of this point in our direction + // + // Note that this can't be called if IsAtBoundary() is true, use + // TryToAdvance() if this might be the case. virtual void Advance(wxGridCellCoords& coords) const = 0; + // Try to advance in our direction, return true if succeeded or false + // otherwise, i.e. if the coordinates are already at the grid boundary. + bool TryToAdvance(wxGridCellCoords& coords) const + { + if ( IsAtBoundary(coords) ) + return false; + + Advance(coords); + + return true; + } + // Find the line at the given distance, in pixels, away from this one // (this uses clipping, i.e. anything after the last line is counted as the // last one and anything before the first one as 0) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 754bca5f16..58e25b56fa 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5753,16 +5753,12 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) } else { - int currentBlockRow = -1; - if ( m_selection ) - currentBlockRow = m_selection->GetCurrentBlockCornerRow(); - - // If we're selecting, continue in the same row, which - // may well be different from the one in which we - // started selecting. - if ( event.ShiftDown() && currentBlockRow != -1 ) + // If we're extending the selection, try to continue in + // the same row, which may well be different from the + // one in which we started selecting. + if ( m_selection && event.ShiftDown() ) { - row = currentBlockRow; + row = m_selection->GetExtensionAnchor().GetRow(); } else // Just use the current row. { @@ -7924,34 +7920,6 @@ wxKeyboardState DummyKeyboardState(bool expandSelection) } // anonymous namespace -bool -wxGrid::PrepareForSelectionExpansion(wxGridCellCoords& coords, - const wxGridDirectionOperations& diroper) -{ - int row = m_selection->GetCurrentBlockCornerRow(); - if ( row == -1 ) - row = m_currentCellCoords.GetRow(); - - int col = m_selection->GetCurrentBlockCornerCol(); - if ( col == -1 ) - col = m_currentCellCoords.GetCol(); - - coords.Set(row, col); - - if ( !diroper.IsValid(coords) ) - { - // The component of the current block corner in our direction - // is not valid. This means we can't change the selection block - // in this direction. - return false; - } - - if ( diroper.IsAtBoundary(coords) ) - return false; - - return true; -} - void wxGrid::DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) @@ -7975,12 +7943,10 @@ wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, if ( !m_selection ) return false; - wxGridCellCoords coords; - if ( !PrepareForSelectionExpansion(coords, diroper) ) + wxGridCellCoords coords(m_selection->GetExtensionAnchor()); + if ( !diroper.TryToAdvance(coords) ) return false; - diroper.Advance(coords); - if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, kbdState) ) @@ -7995,11 +7961,9 @@ wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, { ClearSelection(); - if ( diroper.IsAtBoundary(m_currentCellCoords) ) - return false; - wxGridCellCoords coords = m_currentCellCoords; - diroper.Advance(coords); + if ( !diroper.TryToAdvance(coords) ) + return false; GoToCell(coords); } @@ -8080,25 +8044,9 @@ wxGrid::AdvanceToNextNonEmpty(wxGridCellCoords& coords, } bool -wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, - const wxGridDirectionOperations& diroper) +wxGrid::AdvanceByBlock(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper) { - if ( !m_table ) - return false; - - wxGridCellCoords coords; - - // Expand selection if Shift is pressed. - if ( kbdState.ShiftDown() ) - { - if ( !PrepareForSelectionExpansion(coords, diroper) ) - return false; - } - else - { - coords = m_currentCellCoords; - } - if ( m_table->IsEmpty(coords) ) { // we are in an empty cell: find the next block of non-empty cells @@ -8106,7 +8054,9 @@ wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, } else // current cell is not empty { - diroper.Advance(coords); + if ( !diroper.TryToAdvance(coords) ) + return false; + if ( m_table->IsEmpty(coords) ) { // we started at the end of a block, find the next one @@ -8128,23 +8078,81 @@ wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, } } + return true; +} + +bool +wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) +{ + if ( !m_table ) + return false; + + wxGridCellCoords coords(m_currentCellCoords); if ( kbdState.ShiftDown() ) { - if ( m_selection ) + if ( !m_selection ) + return false; + + // Extending selection by block is tricky for several reasons. First of + // all, we need to combine the coordinates of the current cell and the + // anchor point of selection to find our starting point. + // + // To explain why we need to do this, consider the example of moving by + // rows (i.e. vertically). In this case, it's the contents of column + // containing the current cell which should determine the destination + // of Shift-Ctrl-Up/Down movement, just as it does for Ctrl-Up/Down. In + // fact, the column containing the selection anchor might not even be + // visible at all, e.g. if a whole row is selected and that column is + // the last one, so we definitely don't want to use the contents of + // this column to determine the destination row. + // + // So instead of using the anchor itself here, use only its component + // component in "our" direction with the current cell component. + const wxGridCellCoords anchor = m_selection->GetExtensionAnchor(); + + // This is a really ugly hack that we use to check if we're moving by + // rows or columns here, but it's not worth adding a specific method + // just for this, so just check if MakeWholeLineCoords() fixes the + // column or the row as -1 to determine the direction we're moving in. + const bool byRow = diroper.MakeWholeLineCoords(coords).GetCol() == -1; + + if ( byRow ) + coords.SetRow(anchor.GetRow()); + else + coords.SetCol(anchor.GetCol()); + + if ( !AdvanceByBlock(coords, diroper) ) + return false; + + // Second, now that we've found the destination, we need to copy the + // other component, i.e. the one we didn't modify, from the anchor to + // do as if we started from the anchor originally. + // + // Again, to understand why this is necessary, consider the same + // example as above: if we didn't do this, we would lose the column of + // the original anchor completely and end up with a single column + // selection block even if we had started with the full row selection. + if ( byRow ) + coords.SetCol(anchor.GetCol()); + else + coords.SetRow(anchor.GetRow()); + + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, + coords, + kbdState) ) { - if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, - coords, - kbdState) ) - { - // We want to show a line (a row or a column), not the end of - // the selection block. And do it only if the selection block - // was actually changed. - MakeCellVisible(diroper.MakeWholeLineCoords(coords)); - } + // We want to show a line (a row or a column), not the end of + // the selection block. And do it only if the selection block + // was actually changed. + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); } } else { + if ( !AdvanceByBlock(coords, diroper) ) + return false; + ClearSelection(); GoToCell(coords); } diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 2d473c0ede..cf54fbb7c1 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -597,32 +597,25 @@ bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, return true; } -int wxGridSelection::GetCurrentBlockCornerRow() const +wxGridCellCoords wxGridSelection::GetExtensionAnchor() const { + wxGridCellCoords coords = m_grid->m_currentCellCoords; + if ( m_selection.empty() ) - return -1; + return coords; const wxGridBlockCoords& block = *m_selection.rbegin(); - if ( block.GetTopRow() == m_grid->m_currentCellCoords.GetRow() ) - return block.GetBottomRow(); - if ( block.GetBottomRow() == m_grid->m_currentCellCoords.GetRow() ) - return block.GetTopRow(); + if ( block.GetTopRow() == coords.GetRow() ) + coords.SetRow(block.GetBottomRow()); + else if ( block.GetBottomRow() == coords.GetRow() ) + coords.SetRow(block.GetTopRow()); - return -1; -} + if ( block.GetLeftCol() == coords.GetCol() ) + coords.SetCol(block.GetRightCol()); + else if ( block.GetRightCol() == coords.GetCol() ) + coords.SetCol(block.GetLeftCol()); -int wxGridSelection::GetCurrentBlockCornerCol() const -{ - if ( m_selection.empty() ) - return -1; - - const wxGridBlockCoords& block = *m_selection.rbegin(); - if ( block.GetLeftCol() == m_grid->m_currentCellCoords.GetCol() ) - return block.GetRightCol(); - if ( block.GetRightCol() == m_grid->m_currentCellCoords.GetCol() ) - return block.GetLeftCol(); - - return -1; + return coords; } wxGridCellCoordsArray wxGridSelection::GetCellSelection() const diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 5aa431a623..d61ba77d21 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -74,6 +74,17 @@ protected: void CheckFirstColAutoSize(int expected); + // Helper to check that the selection is equal to the specified block. + void CheckSelection(const wxGridBlockCoords& block) + { + const wxGridBlocks selected = m_grid->GetSelectedBlocks(); + wxGridBlocks::iterator it = selected.begin(); + + REQUIRE( it != selected.end() ); + CHECK( *it == block ); + CHECK( ++it == selected.end() ); + } + TestableGrid *m_grid; wxDECLARE_NO_COPY_CLASS(GridTestCase); @@ -490,6 +501,35 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Cursor", "[grid]") CHECK(m_grid->GetGridCursorRow() == 0); } +TEST_CASE_METHOD(GridTestCase, "Grid::KeyboardSelection", "[grid][selection]") +{ + m_grid->SetCellValue(1, 1, "R2C2"); + m_grid->SetCellValue(2, 0, "R3C1"); + m_grid->SetCellValue(3, 0, "R4C1"); + m_grid->SetCellValue(4, 0, "R5C1"); + m_grid->SetCellValue(7, 0, "R8C1"); + + CHECK(m_grid->GetGridCursorCoords() == wxGridCellCoords(0, 0)); + + m_grid->MoveCursorRight(true); + CheckSelection(wxGridBlockCoords(0, 0, 0, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 2, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 7, 1)); + + m_grid->MoveCursorUpBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 1)); + + m_grid->MoveCursorLeft(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 0)); +} + TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") { m_grid->SelectAll(); From b8c3c6031603dd425022251a3822a460241bb4f0 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 01:23:25 +0200 Subject: [PATCH 47/65] Allow extending selection using Shift-Page Up/Down keys Also make Page Up/Down themselves work consistently with the other cursor movement keys and clear current selection if they move the cursor. Even though DoMoveCursorByPage() is simpler than DoMoveCursorByBlock(), still factor out AdvanceByPage() for consistency with AdvanceByBlock() and because it still makes the code more clear. --- include/wx/generic/grid.h | 5 ++- src/generic/grid.cpp | 64 +++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index e97d5a1f7d..98f494f376 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -2771,7 +2771,10 @@ private: const wxGridDirectionOperations& diroper); bool DoMoveCursor(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); - bool DoMoveCursorByPage(const wxGridDirectionOperations& diroper); + bool DoMoveCursorByPage(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper); + bool AdvanceByPage(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper); bool DoMoveCursorByBlock(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); void AdvanceToNextNonEmpty(wxGridCellCoords& coords, diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 58e25b56fa..0ad120676e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5803,11 +5803,19 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) break; case WXK_PAGEUP: - MovePageUp(); + DoMoveCursorByPage + ( + event, + wxGridBackwardOperations(this, wxGridRowOperations()) + ); break; case WXK_PAGEDOWN: - MovePageDown(); + DoMoveCursorByPage + ( + event, + wxGridForwardOperations(this, wxGridRowOperations()) + ); break; case WXK_SPACE: @@ -7995,37 +8003,67 @@ bool wxGrid::MoveCursorRight(bool expandSelection) wxGridForwardOperations(this, wxGridColumnOperations())); } -bool wxGrid::DoMoveCursorByPage(const wxGridDirectionOperations& diroper) +bool +wxGrid::AdvanceByPage(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper) +{ + if ( diroper.IsAtBoundary(coords) ) + return false; + + const int oldRow = coords.GetRow(); + coords.SetRow(diroper.MoveByPixelDistance(oldRow, m_gridWin->GetClientSize().y)); + if ( coords.GetRow() == oldRow ) + diroper.Advance(coords); + + return true; +} + +bool +wxGrid::DoMoveCursorByPage(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) { if ( m_currentCellCoords == wxGridNoCellCoords ) return false; - if ( diroper.IsAtBoundary(m_currentCellCoords) ) + // We don't handle Ctrl-PageUp/Down, it's not really clear what are they + // supposed to do, so don't do anything for now. + if ( kbdState.ControlDown() ) return false; - const int oldRow = m_currentCellCoords.GetRow(); - int newRow = diroper.MoveByPixelDistance(oldRow, m_gridWin->GetClientSize().y); - if ( newRow == oldRow ) + if ( kbdState.ShiftDown() ) + { + if ( !m_selection ) + return false; + + wxGridCellCoords coords = m_selection->GetExtensionAnchor(); + if ( !AdvanceByPage(coords, diroper) ) + return false; + + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, kbdState) ) + MakeCellVisible(coords); + } + else { wxGridCellCoords coords(m_currentCellCoords); - diroper.Advance(coords); - newRow = coords.GetRow(); - } + if ( !AdvanceByPage(coords, diroper) ) + return false; - GoToCell(newRow, m_currentCellCoords.GetCol()); + ClearSelection(); + GoToCell(coords); + } return true; } bool wxGrid::MovePageUp() { - return DoMoveCursorByPage( + return DoMoveCursorByPage(DummyKeyboardState(false), wxGridBackwardOperations(this, wxGridRowOperations())); } bool wxGrid::MovePageDown() { - return DoMoveCursorByPage( + return DoMoveCursorByPage(DummyKeyboardState(false), wxGridForwardOperations(this, wxGridRowOperations())); } From ba2772b8108c8ada7f0f722e9617562e158ae2ee Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 01:50:44 +0200 Subject: [PATCH 48/65] Disallow shrinking rows/columns in row-or-column selection mode Fix the check for disallowing changing block rows/columns to take into account the row-or-column selection mode too. --- src/generic/gridsel.cpp | 54 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index cf54fbb7c1..25b7a82afd 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -499,11 +499,53 @@ bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, const wxGridBlockCoords& block = *m_selection.rbegin(); wxGridBlockCoords newBlock = block; - // Don't adjust the blocks rows at all in column selection mode as the - // top/bottom row are always fixed to the first/last grid row anyhow in - // this case and we shouldn't select only part of a column just because the - // user Shift-clicked somewhere in the middle of the grid. - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) + // Determine if we should try to extend the current block rows and/or + // columns at all. + bool canChangeRow = false, + canChangeCol = false; + + switch ( m_selectionMode ) + { + case wxGrid::wxGridSelectCells: + // Nothing prevents us from doing it in this case. + canChangeRow = + canChangeCol = true; + break; + + case wxGrid::wxGridSelectColumns: + // Rows are always fixed, so prevent us from ever selecting only + // part of a column in this case by leaving canChangeRow false. + canChangeCol = true; + break; + + case wxGrid::wxGridSelectRows: + // Same as above but mirrored. + canChangeRow = true; + break; + + case wxGrid::wxGridSelectRowsOrColumns: + // In this case we may only change component which is not fixed. + if ( block.GetTopRow() != 0 || + block.GetBottomRow() != m_grid->GetNumberRows() - 1 ) + { + // This is a row block, so we can extend it in row direction. + canChangeRow = true; + } + else if ( block.GetLeftCol() != 0 || + block.GetRightCol() != m_grid->GetNumberCols() - 1 ) + { + canChangeCol = true; + } + else // The entire grid is selected. + { + // In this case we can shrink it in either direction. + canChangeRow = + canChangeCol = true; + } + break; + } + + if ( canChangeRow ) { // If the new block starts at the same top row as the current one, the // end block coordinates must correspond to the new bottom row -- and @@ -540,7 +582,7 @@ bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, } // Same as above but mirrored for columns. - if ( m_selectionMode != wxGrid::wxGridSelectRows ) + if ( canChangeCol ) { if ( blockStart.GetCol() == block.GetLeftCol() ) { From 033f0067227754211566a9cc1e2a52a53a70fb6e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 14:18:11 +0200 Subject: [PATCH 49/65] Refresh the grid correctly when the selection mode changes Update the cells that had to be deselected due to the selection mode change. This used to work, but the refresh was lost during a previous refactoring. --- src/generic/gridsel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 25b7a82afd..0288f9a66a 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -128,7 +128,13 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) } if ( !valid ) + { + if ( !m_grid->GetBatchCount() ) + { + m_grid->RefreshBlock(block.GetTopLeft(), block.GetBottomRight()); + } m_selection.erase(m_selection.begin() + n); + } } m_selectionMode = selmode; From 62cb90b455ecf5ab6e2e7bf48aaff483afbb9a53 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 14:21:07 +0200 Subject: [PATCH 50/65] Avoid horizontal scrolling when pressing Shift-Page Up/Down Scroll the new end of selection into view only vertically, it would be unexpected for the vertical Page Up/Down movement to scroll the grid horizontally. --- src/generic/grid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 0ad120676e..d08969bc48 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -8040,7 +8040,7 @@ wxGrid::DoMoveCursorByPage(const wxKeyboardState& kbdState, return false; if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, kbdState) ) - MakeCellVisible(coords); + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); } else { From da84a253118d35cf4e83041624e6b571736e15c4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 14:47:44 +0200 Subject: [PATCH 51/65] Make the line whose header was Ctrl-clicked current This is more consistent with Ctrl-clicking cells. It also slightly improves behaviour when Ctrl-dragging mouse over the range of selected lines, although it's still somewhat surprising. --- src/generic/grid.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index d08969bc48..ce39521ed0 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3726,7 +3726,8 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( m_selection && m_numRows > 0 && m_numCols > 0 && m_selection->GetSelectionMode() != wxGridSelectColumns ) { - bool selectNewRow = false; + bool selectNewRow = false, + makeRowCurrent = false; if ( event.ShiftDown() && !event.CmdDown() ) { @@ -3743,23 +3744,28 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo else if ( event.CmdDown() && !event.ShiftDown() ) { if ( GetSelectedRows().Index(row) != wxNOT_FOUND ) + { DeselectRow(row); + makeRowCurrent = true; + } else + { + makeRowCurrent = selectNewRow = true; + } } else { ClearSelection(); + makeRowCurrent = selectNewRow = true; } if ( selectNewRow ) - { - // Select the new row. m_selection->SelectRow(row, event); + if ( makeRowCurrent ) SetCurrentCell(row, GetFirstFullyVisibleColumn()); - } ChangeCursorMode(WXGRID_CURSOR_SELECT_ROW, rowLabelWin); } @@ -4125,7 +4131,8 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo if ( m_selection && m_numRows > 0 && m_numCols > 0 && m_selection->GetSelectionMode() != wxGridSelectRows ) { - bool selectNewCol = false; + bool selectNewCol = false, + makeColCurrent = false; if ( event.ShiftDown() && !event.CmdDown() ) { @@ -4142,23 +4149,28 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo else if ( event.CmdDown() && !event.ShiftDown() ) { if ( GetSelectedCols().Index(col) != wxNOT_FOUND ) + { DeselectCol(col); + makeColCurrent = true; + } else + { + makeColCurrent = selectNewCol = true; + } } else { ClearSelection(); + makeColCurrent = selectNewCol = true; } - if (selectNewCol) - { - // Select the new column. + if ( selectNewCol ) m_selection->SelectCol(col, event); + if ( makeColCurrent ) SetCurrentCell(GetFirstFullyVisibleRow(), col); - } ChangeCursorMode(WXGRID_CURSOR_SELECT_COL, colLabelWin); } From 39d0c21a81b93534bec38c555bfacfeceb160df3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 17:48:15 +0200 Subject: [PATCH 52/65] Do make Ctrl-clicked cell current There isn't really any valid reason to ever set the current cell to the corner of a block remaining after breaking up the currently selected block after the deselection of the just clicked cell, so don't do it. Just set the current cell to the clicked cell, whether it's selected or not. --- src/generic/grid.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index ce39521ed0..69de77cada 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4561,10 +4561,6 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, DisableCellEditControl(); MakeCellVisible( coords ); - // Whether we should move the current grid cell to the corrner of the - // last selected block. - bool goToLastBlock = false; - if ( event.CmdDown() && !event.ShiftDown() ) { if ( m_selection ) @@ -4583,8 +4579,6 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, wxGridBlockCoords(coords.GetRow(), coords.GetCol(), coords.GetRow(), coords.GetCol()), event); - - goToLastBlock = true; } } } @@ -4619,15 +4613,7 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, coords != wxGridNoCellCoords; } - if ( goToLastBlock && m_selection->IsSelection() ) - { - wxGridBlockCoords& lastBlock = m_selection->GetBlocks().back(); - SetCurrentCell(lastBlock.GetTopLeft()); - } - else - { - SetCurrentCell(coords); - } + SetCurrentCell(coords); } } From abd9aaa4313822009adc2652b3822fc1b230b60a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 17:52:43 +0200 Subject: [PATCH 53/65] Stop setting current cell on first Ctrl-drag event This is not necessary any longer as the current cell is changed on Ctrl-click since the previous commit. This commit is best viewed ignoring whitespace. --- src/generic/grid.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 69de77cada..40bbaf678e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4483,26 +4483,19 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, SaveEditControlValue(); } - switch ( event.GetModifiers() ) + if ( !event.HasAnyModifiers() ) { - case wxMOD_CONTROL: + if ( CanDragCell() ) + { if ( isFirstDrag ) - SetGridCursor(coords); - break; - - case wxMOD_NONE: - if ( CanDragCell() ) { - if ( isFirstDrag ) - { - // if event is handled by user code, no further processing - if ( SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) != 0 ) - performDefault = false; + // if event is handled by user code, no further processing + if ( SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) != 0 ) + performDefault = false; - return performDefault; - } + return performDefault; } - break; + } } // Edit the current selection block independently of the modifiers state. From 6d4df74a03dcd0f9cba4421468d2c34308e4dbc9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 18:37:47 +0200 Subject: [PATCH 54/65] Simplify and improve wxGrid::DoGridCellDrag() return value logic Remove the "performDefault" variable which didn't really seem to help, as the actual meaning of the return value is whether we should start drag-selecting or not and the name of this variable didn't reflect this anyhow. Return false if we are over an invalid cell: this shouldn't change anything when we're already dragging, but would prevent us from starting to drag from an invalid cell which seems more correct, even if it's not clear if this can happen in practice. Also move hiding the edit control inside "isFirstDrag" check, there is no reason to do it on every drag. --- src/generic/grid.cpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 40bbaf678e..e6a95b51f2 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4471,29 +4471,24 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, const wxGridCellCoords& coords, bool isFirstDrag) { - bool performDefault = true ; - if ( coords == wxGridNoCellCoords ) - return performDefault; // we're outside any valid cell + return false; // we're outside any valid cell - // Hide the edit control, so it won't interfere with drag-shrinking. - if ( IsCellEditControlShown() ) + if ( isFirstDrag ) { - HideCellEditControl(); - SaveEditControlValue(); - } - - if ( !event.HasAnyModifiers() ) - { - if ( CanDragCell() ) + // Hide the edit control, so it won't interfere with drag-shrinking. + if ( IsCellEditControlShown() ) { - if ( isFirstDrag ) + HideCellEditControl(); + SaveEditControlValue(); + } + + if ( !event.HasAnyModifiers() ) + { + if ( CanDragCell() ) { // if event is handled by user code, no further processing - if ( SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) != 0 ) - performDefault = false; - - return performDefault; + return SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) == 0; } } } @@ -4502,7 +4497,7 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, if ( m_selection ) m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); - return performDefault; + return true; } bool wxGrid::DoGridDragEvent(wxMouseEvent& event, From 52d1b86bbd174eb790f812fa22611e2bfb9f57c1 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 18:46:54 +0200 Subject: [PATCH 55/65] Don't allow drag-extending selection from deselected cell Dragging in a grid with Ctrl key pressed starting from a previously selected cell behaved very counterintuitively if not downright wrongly, as the selection logic assumes that the selection anchor itself is always selected, which wasn't true in this case. Solve the problem by just not extending the selection at all when starting to drag from a deselected cell. This means that Ctrl-dragging doesn't do anything any more, but it's not a huge loss and to make it work well while still allowing to use Ctrl-click to toggle the cell selection, we'd need to implement a whole new and different drag-deselect mode, as is done in Microsoft Excel 2016 (note that the previous versions of Excel don't implement Ctrl-dragging neither). --- src/generic/grid.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index e6a95b51f2..8a10da4ec4 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4490,11 +4490,24 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, // if event is handled by user code, no further processing return SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) == 0; } + + // When Shift-dragging, we must have already selected the initial + // cell and when Ctrl-dragging we may have either selected or + // deselected it, depending on its previous state. But when + // dragging without any modifiers, we want it to start in the + // selected state even though it's not selected on a simple click. + if ( m_selection ) + m_selection->SelectBlock(m_currentCellCoords, coords, event); } } - // Edit the current selection block independently of the modifiers state. - if ( m_selection ) + // Extend the selection if possible: this is not the case if we're dragging + // from an unselected cell, as can be the case if the drag was started by + // Ctrl-clicking a previously selected cell (notice that the modifier of + // the current event is irrelevant, it's too late to change the behaviour + // by pressing or releasing Ctrl later, only its initial state, as + // indicated by the state of the starting cell, counts). + if ( m_selection && m_selection->IsInSelection(m_currentCellCoords) ) m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); return true; From e6186f73a6825898b97a4b34fe4847af9dfdaf70 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 21:33:55 +0200 Subject: [PATCH 56/65] Implement alternative solution to Ctrl-drag problem This commit doesn't change the behaviour compared to the previous one, but provides an alternative implementation of the same goal, which seems preferable: instead of not extending the selection while Ctrl-dragging, just don't enter dragging mode, i.e. don't capture the mouse and don't set m_isDragging to true, if we start it from a previously selected, and hence currently deselected, cell. --- src/generic/grid.cpp | 45 ++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 8a10da4ec4..1f7927614e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -4483,31 +4483,36 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, SaveEditControlValue(); } - if ( !event.HasAnyModifiers() ) + switch ( event.GetModifiers() ) { - if ( CanDragCell() ) - { - // if event is handled by user code, no further processing - return SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) == 0; - } + case wxMOD_CONTROL: + // Ctrl-dragging is special, because we could have started it + // by Ctrl-clicking a previously selected cell, which has the + // effect of deselecting it and in this case we can't start + // drag-selection from it because the selection anchor should + // always be selected itself. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + return false; + break; - // When Shift-dragging, we must have already selected the initial - // cell and when Ctrl-dragging we may have either selected or - // deselected it, depending on its previous state. But when - // dragging without any modifiers, we want it to start in the - // selected state even though it's not selected on a simple click. - if ( m_selection ) - m_selection->SelectBlock(m_currentCellCoords, coords, event); + case wxMOD_NONE: + if ( CanDragCell() ) + { + // if event is handled by user code, no further processing + return SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) == 0; + } + break; + + //default: In all the other cases, we don't have anything special + // to do and we'll just extend the selection below. } } - // Extend the selection if possible: this is not the case if we're dragging - // from an unselected cell, as can be the case if the drag was started by - // Ctrl-clicking a previously selected cell (notice that the modifier of - // the current event is irrelevant, it's too late to change the behaviour - // by pressing or releasing Ctrl later, only its initial state, as - // indicated by the state of the starting cell, counts). - if ( m_selection && m_selection->IsInSelection(m_currentCellCoords) ) + // Note that we don't need to check the modifiers here, it doesn't matter + // which keys are pressed for the current event, as pressing or releasing + // Ctrl later can't change the dragging behaviour. Only the initial state + // of the modifier keys matters. + if ( m_selection ) m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); return true; From ee0b70a3a9d5010bba589db71b973d7e13a0b581 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 21:41:40 +0200 Subject: [PATCH 57/65] Disable extending selection with Ctrl-drag for rows/columns too Don't extend the selection if the anchor line is not selected, as this doesn't work correctly because the entire selection logic supposes the anchor itself is selected and, moreover, it's not really clear how could it would otherwise. This commit does the same thing for rows/columns as the grandparent commit did to the cells. Unfortunately a somewhat cleaner solution of the parent commit can't be easily applied to the existing rows/columns code and it's arguably not worth changing it in depth just for this. --- src/generic/grid.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 1f7927614e..d2f7a68f1e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3673,6 +3673,11 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) break; + // We can't extend the selection from non-selected row, + // which may happen if we Ctrl-clicked it initially. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + break; + if ( (row = YToRow( pos.y )) >= 0 ) { m_selection->ExtendCurrentBlock( @@ -4016,6 +4021,12 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) break; + // We can't extend the selection from non-selected + // column which may happen if we Ctrl-clicked it + // initially. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + break; + m_selection->ExtendCurrentBlock( wxGridCellCoords(0, m_currentCellCoords.GetCol()), wxGridCellCoords(GetNumberRows() - 1, col), From 4cde93cc824ae961abd30d27ac2ea967bea4c5b7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 21:45:54 +0200 Subject: [PATCH 58/65] Optimize test for the row/column selection Use wxGridSelection::IsInSelection() instead of GetSelectedRows/Cols() which can be much slower as they need to produce an array containing indices of all the selected rows/columns. --- src/generic/grid.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index d2f7a68f1e..4365fe4170 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3748,7 +3748,7 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo } else if ( event.CmdDown() && !event.ShiftDown() ) { - if ( GetSelectedRows().Index(row) != wxNOT_FOUND ) + if ( m_selection->IsInSelection(row, 0) ) { DeselectRow(row); makeRowCurrent = true; @@ -4159,7 +4159,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo } else if ( event.CmdDown() && !event.ShiftDown() ) { - if ( GetSelectedCols().Index(col) != wxNOT_FOUND ) + if ( m_selection->IsInSelection(0, col) ) { DeselectCol(col); makeColCurrent = true; From ed767ed3245b8bd6b1cc9672f976526814ca641c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Apr 2020 21:59:44 +0200 Subject: [PATCH 59/65] Make Ctrl/Shift-Space apply to all cells of the current selection This provides a convenient way to select multiple lines and is consistent with the operation of these keys in spreadsheet programs. --- src/generic/grid.cpp | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 4365fe4170..8e64c9331b 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5834,33 +5834,45 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) break; case WXK_SPACE: - // Ctrl-Space selects the current column, Shift-Space -- the - // current row and Ctrl-Shift-Space -- everything - switch ( m_selection ? event.GetModifiers() : wxMOD_NONE ) + // Ctrl-Space selects the current column or, if there is a + // selection, all columns containing the selected cells, + // Shift-Space -- the current row (or all selection rows) and + // Ctrl-Shift-Space -- everything. { - case wxMOD_CONTROL: - m_selection->SelectCol(m_currentCellCoords.GetCol()); - break; - - case wxMOD_SHIFT: - m_selection->SelectRow(m_currentCellCoords.GetRow()); - break; - - case wxMOD_CONTROL | wxMOD_SHIFT: - m_selection->SelectBlock(0, 0, - m_numRows - 1, m_numCols - 1); - break; - - case wxMOD_NONE: - if ( !IsEditable() ) - { - MoveCursorRight(false); + wxGridCellCoords selStart, selEnd; + switch ( m_selection ? event.GetModifiers() : wxMOD_NONE ) + { + case wxMOD_CONTROL: + selStart.Set(0, m_currentCellCoords.GetCol()); + selEnd.Set(m_numRows - 1, + m_selection->GetExtensionAnchor().GetCol()); break; - } - wxFALLTHROUGH; - default: - event.Skip(); + case wxMOD_SHIFT: + selStart.Set(m_currentCellCoords.GetRow(), 0); + selEnd.Set(m_selection->GetExtensionAnchor().GetRow(), + m_numCols - 1); + break; + + case wxMOD_CONTROL | wxMOD_SHIFT: + selStart.Set(0, 0); + selEnd.Set(m_numRows - 1, m_numCols - 1); + break; + + case wxMOD_NONE: + if ( !IsEditable() ) + { + MoveCursorRight(false); + break; + } + wxFALLTHROUGH; + + default: + event.Skip(); + } + + if ( selStart != wxGridNoCellCoords ) + m_selection->SelectBlock(selStart, selEnd); } break; From 945718ef5f35a87a20cee579d9fc96d7a19a3dbb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 14 Apr 2020 18:07:47 +0200 Subject: [PATCH 60/65] Fix documented return type of wxGridBlockCoords::ContainsBlock() This should have been part of 791a9e68ae (Rename and simplify wxGridBlockCoords::ContainsBlock(), 2020-04-05). --- interface/wx/grid.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/wx/grid.h b/interface/wx/grid.h index d05931fe4b..5a48bab795 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1929,7 +1929,7 @@ public: @return @true if @a other is entirely contained within this block. */ - int ContainsBlock(const wxGridBlockCoords& other) const; + bool ContainsBlock(const wxGridBlockCoords& other) const; /** Calculates the result blocks by subtracting the other block from this From ddaa5b5e02f03404da03531edf7a3d410738d848 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 14 Apr 2020 18:50:06 +0200 Subject: [PATCH 61/65] Make wxGridSelection::IsInSelection() const There is really no reason for it not to be. --- include/wx/generic/gridsel.h | 4 ++-- src/generic/gridsel.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 18377431ef..d613283f66 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -28,8 +28,8 @@ public: wxGrid::wxGridSelectionModes sel = wxGrid::wxGridSelectCells); bool IsSelection(); - bool IsInSelection(int row, int col); - bool IsInSelection(const wxGridCellCoords& coords) + bool IsInSelection(int row, int col) const; + bool IsInSelection(const wxGridCellCoords& coords) const { return IsInSelection(coords.GetRow(), coords.GetCol()); } diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 0288f9a66a..d4953c59d2 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -55,7 +55,7 @@ bool wxGridSelection::IsSelection() return !m_selection.empty(); } -bool wxGridSelection::IsInSelection( int row, int col ) +bool wxGridSelection::IsInSelection( int row, int col ) const { // Check whether the given cell is contained in one of the selected blocks. // From 5d90688723c223f6992168f7e692e176970aa02b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 14 Apr 2020 18:51:23 +0200 Subject: [PATCH 62/65] Fix extending selection starting from unselected current cell Unselected current cell should always be considered as the current selection block to extend, as it doesn't make sense to extend any other block (perhaps selected on another side of the grid) when pressing Shift-arrow. This scenario could be achieved by selecting a block and Ctrl-clicking a cell (either inside or outside the selection) twice and then extending it using Shift-arrow keys. Previously, this behaved in a strange way, combining the corner of the selected block with the target of the movement, whereas now this just starts selecting a new block from the current cell as expected. --- include/wx/generic/gridsel.h | 8 +++++--- src/generic/gridsel.cpp | 11 +++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index d613283f66..08377e3b08 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -68,9 +68,11 @@ public: void UpdateCols( size_t pos, int numCols ); // Extend (or shrink) the current selection block (creating it if - // necessary, i.e. if there is no selection at all currently) to the one - // specified by the start and end coordinates of its opposite corners - // (which don't have to be in top/bottom left/right order). + // necessary, i.e. if there is no selection at all currently or if the + // current current cell isn't selected, as in this case a new block + // containing it is always added) to the one specified by the start and end + // coordinates of its opposite corners (which don't have to be in + // top/bottom left/right order). // // Note that blockStart is equal to wxGrid::m_currentCellCoords almost // always, but not always (the exception is when we scrolled out from diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index d4953c59d2..fc41b359bd 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -496,7 +496,11 @@ bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, wxASSERT( blockStart.GetRow() != -1 && blockStart.GetCol() != -1 && blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1 ); - if ( m_selection.empty() ) + // If selection doesn't contain the current cell (which also covers the + // special case of nothing being selected yet), we have to create a new + // block containing it because it doesn't make sense to extend any existing + // block to non-selected current cell. + if ( !IsInSelection(m_grid->GetGridCursorCoords()) ) { SelectBlock(blockStart, blockEnd); return true; @@ -649,7 +653,10 @@ wxGridCellCoords wxGridSelection::GetExtensionAnchor() const { wxGridCellCoords coords = m_grid->m_currentCellCoords; - if ( m_selection.empty() ) + // If the current cell isn't selected (which also covers the special case + // of nothing being selected yet), we have to use it as anchor as we need + // to ensure that it will get selected. + if ( !IsInSelection(coords) ) return coords; const wxGridBlockCoords& block = *m_selection.rbegin(); From 3ebc76eea5142b939a58b78d6ce9a6229d149d4d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Apr 2020 00:01:56 +0200 Subject: [PATCH 63/65] Always explicitly set split orientation in DeselectBlock() No real changes, just make the code more clear by always explicitly selecting either wxHORIZONTAL or wxVERTICAL instead of default for the former for no good reason (by symmetry, it is not a better choice than wxVERTICAL). Also list all wxGridSelection enum elements in the switch over selection mode to avoid warnings about not handling wxGridSelectRows. --- src/generic/gridsel.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index fc41b359bd..7f382f3715 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -269,33 +269,29 @@ wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, if ( !m_selection[n].Intersects(canonicalizedBlock) ) continue; - int splitOrientation = wxHORIZONTAL; + int splitOrientation = -1; switch ( m_selectionMode ) { - case wxGrid::wxGridSelectCells: - if ( selBlock.GetLeftCol() == 0 && - selBlock.GetRightCol() == m_grid->GetNumberCols() - 1 ) - break; - - if ( selBlock.GetTopRow() == 0 && - selBlock.GetBottomRow() == m_grid->GetNumberRows() - 1 ) - splitOrientation = wxVERTICAL; - + case wxGrid::wxGridSelectRows: + splitOrientation = wxHORIZONTAL; break; case wxGrid::wxGridSelectColumns: splitOrientation = wxVERTICAL; break; + case wxGrid::wxGridSelectCells: case wxGrid::wxGridSelectRowsOrColumns: if ( selBlock.GetLeftCol() == 0 && selBlock.GetRightCol() == m_grid->GetNumberCols() - 1 ) - break; - - splitOrientation = wxVERTICAL; + splitOrientation = wxHORIZONTAL; + else + splitOrientation = wxVERTICAL; break; } + wxASSERT_MSG( splitOrientation != -1, "unknown selection mode" ); + const wxGridBlockDiffResult result = selBlock.Difference(canonicalizedBlock, splitOrientation); From 51ea80b70190246bf3a619ea98383c7b784e74b5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Apr 2020 00:03:37 +0200 Subject: [PATCH 64/65] Allow selecting rows/columns in row-or-column selection mode Don't blankly forbid selecting any blocks at all in this mode, this didn't really make any sense. Moreover, SelectBlock() not doing anything prevented wxGrid code handling {Ctrl,Shift}-Space from doing anything in this mode and, worse, broke the logic of DeselectBlock() which relied on using SelectBlock() to select the remaining parts of the selection, so this commit fixes using Ctrl-click for deselecting rows or columns in this selection mode, which was previously completely broken. --- src/generic/gridsel.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 7f382f3715..97aeee4f50 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -186,9 +186,16 @@ void wxGridSelection::SelectBlock( int topRow, int leftCol, break; case wxGrid::wxGridSelectRowsOrColumns: - // block selection doesn't make sense for this mode, we could only - // select the entire grid but this wouldn't be useful - allowed = 0; + // Arbitrary block selection doesn't make sense for this mode, as + // we could only select the entire grid, which wouldn't be useful, + // but we do allow selecting blocks that are already composed of + // only rows or only columns. + if ( topRow == 0 && bottomRow == m_grid->GetNumberRows() - 1 ) + allowed = 1; + else if ( leftCol == 0 && rightCol == m_grid->GetNumberCols() - 1 ) + allowed = 1; + else + allowed = 0; break; } From 44c3e626b308003b402999a8e975daa931e0c2a7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Apr 2020 16:16:00 +0200 Subject: [PATCH 65/65] Extend current selection on {Ctrl,Shift}-Space Extend the existing selection instead of adding a new block, as there is no reason to keep the old selection as a separate block, when it's always a subblock of the new one, and doing it resulted in wrong behaviour when selecting 2 horizontally adjacent cells, pressing Ctrl-Space and then pressing Shift-Left deselected the rightmost selected column but still left its single cell, which was part of the originally selected block, selected, which was surprising and looked wrong. --- src/generic/grid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 8e64c9331b..adbf19cc61 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5872,7 +5872,7 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) } if ( selStart != wxGridNoCellCoords ) - m_selection->SelectBlock(selStart, selEnd); + m_selection->ExtendCurrentBlock(selStart, selEnd, event); } break;