Merge branch 'grid-selection-refactoring'

Completely overhauled selection handling in wxGrid.

Make various ways of extending selection (using Shift-arrow keys,
Ctrl-Shift-arrows, Shift-click etc) work as expected from the user point
of view instead of producing various bizarre results. Also improve
row/column header click selection as well as Ctrl/Shift-Space handling.

Internally, store selection as just a vector of blocks, independently of
the selection mode, and provide a simple API for iterating over it which
remains usable even with selections containing millions of cells (as
long as they're still composed of only a few blocks, which is the case
in practice).

Add more tests and add display of the current selection to the sample.

See https://github.com/wxWidgets/wxWidgets/pull/1772
This commit is contained in:
Vadim Zeitlin
2020-04-15 15:32:09 +02:00
9 changed files with 2322 additions and 1502 deletions

View File

@@ -19,6 +19,10 @@
#include "wx/scrolwin.h"
#if wxUSE_STD_CONTAINERS_COMPATIBLY
#include <iterator>
#endif
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
@@ -747,9 +751,204 @@ 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;
}
// 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;
}
// 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.
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];
};
// ----------------------------------------------------------------------------
// wxGridBlocks: a range of grid blocks that can be iterated over
// ----------------------------------------------------------------------------
class wxGridBlocks
{
typedef wxVector<wxGridBlockCoords>::const_iterator iterator_impl;
public:
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;
iterator() : m_it() { }
reference operator*() const { return *m_it; }
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 wxGridBlocks;
};
iterator begin() const
{
return m_begin;
}
iterator end() const
{
return m_end;
}
private:
wxGridBlocks() :
m_begin(),
m_end()
{
}
wxGridBlocks(iterator_impl begin, iterator_impl end) :
m_begin(begin),
m_end(end)
{
}
const iterator m_begin;
const iterator m_end;
friend class wxGrid;
};
// For comparisons...
//
extern WXDLLIMPEXP_CORE wxGridCellCoords wxGridNoCellCoords;
extern WXDLLIMPEXP_CORE wxGridBlockCoords wxGridNoBlockCoords;
extern WXDLLIMPEXP_CORE wxRect wxGridNoCellRect;
// An array of cell coords...
@@ -1343,6 +1542,10 @@ public:
void MakeCellVisible( const wxGridCellCoords& coords )
{ MakeCellVisible( coords.GetRow(), coords.GetCol() ); }
// 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
//
@@ -1791,6 +1994,7 @@ public:
bool IsInSelection( const wxGridCellCoords& coords ) const
{ return IsInSelection( coords.GetRow(), coords.GetCol() ); }
wxGridBlocks GetSelectedBlocks() const;
wxGridCellCoordsArray GetSelectedCells() const;
wxGridCellCoordsArray GetSelectionBlockTopLeft() const;
wxGridCellCoordsArray GetSelectionBlockBottomRight() const;
@@ -2332,8 +2536,6 @@ protected:
bool m_waitForSlowClick;
wxGridCellCoords m_selectionStart;
wxCursor m_rowResizeCursor;
wxCursor m_colResizeCursor;
@@ -2387,16 +2589,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; }
@@ -2566,7 +2758,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,
@@ -2576,13 +2767,20 @@ 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 DoMoveCursorByPage(const wxGridDirectionOperations& diroper);
bool DoMoveCursorByBlock(bool expandSelection,
bool DoMoveCursor(const wxKeyboardState& kbdState,
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,
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),

View File

@@ -17,6 +17,10 @@
#include "wx/grid.h"
#include "wx/vector.h"
typedef wxVector<wxGridBlockCoords> wxVectorGridBlockCoords;
class WXDLLIMPEXP_CORE wxGridSelection
{
public:
@@ -24,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());
}
@@ -48,66 +52,93 @@ public:
kbd, sendEvent);
}
void SelectCell(int row, int col,
// 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 );
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,
const wxKeyboardState& kbd = wxKeyboardState())
{
ToggleCellSelection(coords.GetRow(), coords.GetCol(), kbd);
}
// 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 );
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 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
// 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.
bool ExtendCurrentBlock(const wxGridCellCoords& blockStart,
const wxGridCellCoords& blockEnd,
const wxKeyboardState& kbd);
// 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;
wxGridCellCoordsArray GetBlockSelectionBottomRight() const;
wxArrayInt GetRowSelection() const;
wxArrayInt GetColSelection() const;
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
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);
// 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);
// 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;
wxGrid::wxGridSelectionModes m_selectionMode;
friend class WXDLLIMPEXP_FWD_CORE wxGrid;
wxDECLARE_NO_COPY_CLASS(wxGridSelection);
};

View File

@@ -793,9 +793,37 @@ 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
//
// 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)

View File

@@ -1816,6 +1816,250 @@ 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 intersect.
@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;
}
/**
Check whether this block contains the given cell.
@return
@true, if the block contains the cell, @false otherwise.
*/
bool ContainsCell(const wxGridCellCoords& cell) const;
/**
Check whether this block contains another one.
@return
@true if @a other is entirely contained within this block.
*/
bool ContainsBlock(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];
};
/**
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.
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->GetSelectedBlocks() ) {
... do something with block ...
}
@endcode
When not using C++11, iteration has to be done manually:
@code
wxGridBlocks range = grid->GetSelectedBlocks();
for ( wxGridBlocks::iterator it = range.begin();
it != range.end();
++it ) {
... do something with *it ...
}
@endcode
@since 3.1.4
*/
class wxGridBlocks
{
public:
/**
Read-only forward iterator type.
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.
*/
class iterator
{
iterator();
const wxGridBlockCoords& operator*() const;
iterator& operator++();
iterator operator++(int);
bool operator==(const iterator& it) const;
bool operator!=(const iterator& it) 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;
};
/**
@class wxGridTableBase
@@ -4419,6 +4663,21 @@ public:
*/
void DeselectCell( int row, int col );
/**
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->GetSelectedBlocks() ) {
if ( block.Intersects(myBlock) )
break;
}
@endcode
@since 3.1.4
*/
wxGridBlocks GetSelectedBlocks() const;
/**
Returns an array of individually selected cells.
@@ -4434,6 +4693,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 GetSelectedBlocks()
in the new code.
*/
wxGridCellCoordsArray GetSelectedCells() const;
@@ -4445,6 +4707,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 GetSelectedBlocks()
in the new code.
*/
wxArrayInt GetSelectedCols() const;
@@ -4456,6 +4721,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 GetSelectedBlocks()
in the new code.
*/
wxArrayInt GetSelectedRows() const;
@@ -4471,6 +4739,9 @@ public:
Please see GetSelectedCells() for more information about the selection
representation in wxGrid.
The function can be slow for the big grids, use GetSelectedBlocks()
in the new code.
@see GetSelectionBlockTopLeft()
*/
wxGridCellCoordsArray GetSelectionBlockBottomRight() const;
@@ -4481,6 +4752,9 @@ public:
Please see GetSelectedCells() for more information about the selection
representation in wxGrid.
The function can be slow for the big grids, use GetSelectedBlocks()
in the new code.
@see GetSelectionBlockBottomRight()
*/
wxGridCellCoordsArray GetSelectionBlockTopLeft() const;
@@ -4641,6 +4915,17 @@ 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;
/**
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.

View File

@@ -41,6 +41,14 @@ http://code.msdn.microsoft.com/windowsdesktop/Writing-type-visualizers-2eae77a2#
<DisplayString>{m_ll}</DisplayString>
</Type>
<Type Name="wxGridCellCoords">
<DisplayString>R{m_row + 1}C{m_col + 1}</DisplayString>
</Type>
<Type Name="wxGridBlockCoords">
<DisplayString>R{m_topRow + 1}C{m_leftCol + 1}:R{m_bottomRow + 1}C{m_rightCol + 1}</DisplayString>
</Type>
<Type Name="wxArrayString">
<DisplayString Condition="m_nCount==0">empty</DisplayString>
<DisplayString Condition="m_nCount==1">{m_pItems[0]}</DisplayString>

View File

@@ -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 );
@@ -1220,32 +1220,66 @@ void GridFrame::SetCornerLabelValue( wxCommandEvent& WXUNUSED(ev) )
void GridFrame::ShowSelection( wxCommandEvent& WXUNUSED(ev) )
{
switch ( grid->GetSelectionMode() )
int count = 0;
wxString desc;
const wxGridBlocks& sel = grid->GetSelectedBlocks();
for ( wxGridBlocks::iterator it = sel.begin(); it != sel.end(); ++it )
{
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());
const wxArrayInt& cols = grid->GetSelectedCols();
if ( !cols.empty() )
wxLogMessage("%zu columns selected", rows.size());
if ( rows.empty() && cols.empty() )
wxLogMessage("No selection");
return;
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);
}
wxLogError("Unknown grid selection mode.");
if ( count++ )
desc += "\n\t";
desc += blockDesc;
}
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) )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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();
@@ -524,24 +564,57 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]")
CHECK(!m_grid->IsInSelection(3, 0));
}
TEST_CASE_METHOD(GridTestCase, "Grid::SelectionRange", "[grid]")
{
const wxGridBlocks empty = m_grid->GetSelectedBlocks();
CHECK( empty.begin() == empty.end() );
m_grid->SelectBlock(1, 0, 3, 1);
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->GetSelectedBlocks() )
{
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]")
{
SECTION("Delete rows/columns")
for ( int i = 0; i < 2; ++i )
{
SECTION("No rows")
SECTION(i == 0 ? "No rows" : "No columns")
{
if ( i == 0 )
{
m_grid->DeleteRows(0, 10);
REQUIRE( m_grid->GetNumberRows() == 0 );
}
SECTION("No columns")
else
{
m_grid->DeleteCols(0, 2);
REQUIRE( m_grid->GetNumberCols() == 0 );
}
}
SECTION("Select")
{
SECTION("Move right")
{
m_grid->MoveCursorRight(true);
@@ -559,9 +632,13 @@ TEST_CASE_METHOD(GridTestCase, "Grid::SelectEmptyGrid", "[grid]")
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]")
@@ -580,12 +657,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]")
@@ -825,9 +902,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();
@@ -1259,4 +1347,193 @@ 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::ContainsCell", "[grid]")
{
// Inside.
CHECK(wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(2, 2)));
// Outside.
CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(5, 5)));
}
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.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]")
{
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