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,10 +751,205 @@ 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 wxRect wxGridNoCellRect;
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 DoMoveCursor(const wxKeyboardState& kbdState,
const wxGridDirectionOperations& diroper);
bool DoMoveCursorByPage(const wxGridDirectionOperations& diroper);
bool DoMoveCursorByBlock(bool expandSelection,
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,
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);
}
// This function replaces all the existing selected blocks (which become
// redundant) with a single block covering the entire grid.
void SelectAll();
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 );
// 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)